#include "factory.h"
#include "exception.h"
#include "objects_cache.h"

#include "configs/config.h"
#include "configs/categories_strings.h"

#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/wiki/revision/objectrevision.h>
#include <yandex/maps/wiki/configs/editor/categories.h>
#include <yandex/maps/wiki/configs/editor/category_template.h>
#include <yandex/maps/wiki/configs/editor/topology_groups.h>

namespace maps::wiki {

namespace {

void
resetNonCopyableAttributes(Attributes& attrs)
{
    for (const auto& attrDef : attrs.definitions()) {
        if (!attrDef->copyable()) {
            attrs.setValue(attrDef->id(), attrDef->defaultValue());
        }
    }
}

} // namespace

GeoObjectFactory::GeoObjectFactory(ObjectsCache& cache)
    : cache_(cache)
{}

ObjectPtr
GeoObjectFactory::create(const ObjectsClassInfo& objectClass, TRevisionId revisionId) const
{
    DEBUG() << BOOST_CURRENT_FUNCTION << " Creating class: " << objectClass.className;
    ConstructorPtr constructor = objectClass.creator;
    ObjectPtr newObject = constructor(revisionId, cache_);
    cache_.add(newObject);
    return newObject;
}

ObjectPtr
GeoObjectFactory::createNewObject(
    const ObjectsClassInfo& objectClass,
    const std::string& category) const
{
    ObjectPtr ret = create(objectClass, cache_.newObjectId());
    ret->setCategory(category);
    ret->initalizeAttributes();
    ret->setAllModified();
    return ret;
}

ObjectPtr
GeoObjectFactory::cloneObject(const ObjectPtr& original) const
{
    auto clone = original->clone();
    resetNonCopyableAttributes(clone->attributes());
    clone->revision_ = cache_.newObjectId();
    cache_.add(clone);
    clone->masterRelationsHolder_.setContainerId(clone->id());
    clone->masterRelationsHolder_.setCategory(clone->categoryId());
    clone->slaveRelationsHolder_.setContainerId(clone->id());
    clone->slaveRelationsHolder_.setCategory(clone->categoryId());
    clone->setAllModified();
    clone->original_.reset();
    return clone;
}

ObjectPtr
GeoObjectFactory::createObject(
    const revision::ObjectRevision& objectRev,
    const ObjectsClassInfo& objectClass) const
{
    ObjectPtr ret = create(objectClass, objectRev.id());
    ASSERT(ret);
    ret->init(objectRev);
    ret->keepOriginal();
    return ret;
}

const ObjectsClassInfo&
GeoObjectFactory::objectClass(const std::string& categoryId)
{
    const auto& categories = cfg()->editor()->categories();
    if (!categories.defined(categoryId)) {
        throw UnknownObjectCategoryException()
            << "Undefined category " << categoryId;
    }

    if (categoryId == CATEGORY_MODEL3D) {
        return ObjectsClassInfos::model3dClassInfo;
    }
    if (categoryId == CATEGORY_RELATION) {
        return ObjectsClassInfos::relationClassInfo;
    }

    const auto& category = categories[categoryId];
    if (category.complex()) {
        return ObjectsClassInfos::complexClassInfo;
    }

    const auto& categoryTemplate = category.categoryTemplate();
    const auto& geomType = categoryTemplate.geometryType();

    if (geomType == Geom::geomTypeNamePolygon) {
        return ObjectsClassInfos::arealClassInfo;
    }
    if (geomType == Geom::geomTypeNameLine) {
        return cfg()->editor()->topologyGroups().findGroup(categoryId)
            ? ObjectsClassInfos::linearElementClassInfo
            : ObjectsClassInfos::lineClassInfo;
    }
    if (geomType == Geom::geomTypeNamePoint) {
        return cfg()->editor()->topologyGroups().findGroup(categoryId)
            ? ObjectsClassInfos::junctionClassInfo
            : ObjectsClassInfos::pointClassInfo;
    }
    if (geomType.empty()) {
        return ObjectsClassInfos::attributesClassInfo;
    }
    throw UnknownObjectCategoryException()
        << "Object class unknown category: " << categoryId
        << " geomType: " << geomType;
}

} // namespace maps::wiki
