#include "relation_object.h"
#include <maps/wikimap/mapspro/services/editor/src/exception.h>
#include <maps/wikimap/mapspro/services/editor/src/collection.h>
#include <maps/wikimap/mapspro/services/editor/src/objectvisitor.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include <maps/wikimap/mapspro/services/editor/src/objects_cache.h>

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

namespace maps {
namespace wiki {

RelationObject::RelationObject(const TRevisionId& id, ObjectsCache& cache)
    : GeoObject(id, cache)
    , masterId_(0)
    , slaveId_(0)
{}

void
RelationObject::generalize(Transaction&)
{}

void
RelationObject::init(const revision::ObjectRevision& objectRev)
{
    const auto& revData = objectRev.data();
    if (!revData.relationData) {
        THROW_WIKI_INTERNAL_ERROR("Missing relation data in: " << objectRev.id());
    }
    masterId_ = revData.relationData->masterObjectId();
    slaveId_ = revData.relationData->slaveObjectId();
    GeoObject::init(objectRev);

    //!Throw if neither of related categories exist
    const auto& categories = cfg()->editor()->categories();
    categories[attributes().value(ATTR_REL_MASTER)];
    categories[attributes().value(ATTR_REL_SLAVE)];
}

bool
RelationObject::syncView() const
{
    if (!SlaveRole::isSyncViewRole(role())) {
        return false;
    }
    const auto& categories = cfg()->editor()->categories();
    return categories.defined(slaveCategoryId()) &&
           categories[slaveCategoryId()].syncView();
}

void
RelationObject::applyVisitor(ObjectVisitor& visitor) const
{
    visitor.visitRelationObject(this);
}

void
RelationObject::applyProcessor(ObjectProcessor& processor)
{
    processor.processRelationObject(this);
}

size_t
RelationObject::seqNum() const
{
    const auto& value = attributes().value(ATTR_REL_SEQ_NUM);
    return value.empty()
        ? 0
        : boost::lexical_cast<size_t>(value);
}

void
RelationObject::setSeqNum(size_t seqNum)
{
    auto newValue = std::to_string(seqNum);
    if (attributes().value(ATTR_REL_SEQ_NUM) != newValue) {
        attributes().setValue(ATTR_REL_SEQ_NUM, newValue);
        setModifiedAttr();
    }
}

void
RelationObject::setRelation(TOid masterId,
    const std::string& masterCat,
    TOid slaveId,
    const std::string& slaveCat,
    const std::string& roleId,
    size_t seqNum)
{
    const auto& slaveRole =
        cfg()->editor()->categories()[masterCat].slaveRole(roleId);
    WIKI_REQUIRE(slaveRole.allowSelfReference() || masterId != slaveId, ERR_SELF_REFERENCE,
        "Self reference not allowed, object " << masterId);
    masterId_ = masterId;
    slaveId_ = slaveId;
    attributes().setValue(ATTR_REL_MASTER, masterCat);
    attributes().setValue(ATTR_REL_SLAVE, slaveCat);
    attributes().setValue(ATTR_REL_ROLE, roleId);
    attributes().setValue(
        ATTR_REL_SEQ_NUM,
        slaveRole.sequential()
            ? std::to_string(seqNum)
            : s_emptyString);
}

void
RelationObject::calcModified()
{
    calcModifiedAttributes();
    calcModifiedSystemAttributes();
    DEBUG() << BOOST_CURRENT_FUNCTION << " id: " << id()
        << " modifiedAttr: " << modifiedAttr_
        << " modifiedSysAttr: " << modifiedSysAttr_
        << " modifiedRelation: " << isModifiedRelation();
}

bool
RelationObject::isModified() const
{
    return modifiedAttr_
        || modifiedSysAttr_
        || isModifiedRelation()
        || isModifiedState();
}

bool
RelationObject::isModifiedRelation() const
{
    if (!hasExistingOriginal()) {
        return true;
    }

    const RelationObject* originalPtr = as<RelationObject>(original());

    return originalPtr->masterId() != masterId_ ||
           originalPtr->slaveId()  != slaveId_;
}

} // namespace wiki
} // namespace maps
