#include "getobject.h"

#include "maps/wikimap/mapspro/services/editor/src/acl_utils.h"
#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/check_permissions.h"
#include "maps/wikimap/mapspro/services/editor/src/configs/config.h"
#include "maps/wikimap/mapspro/services/editor/src/objects_cache.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/point_object.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/linear_element.h"

#include <yandex/maps/wiki/configs/editor/categories.h>

namespace maps {
namespace wiki {

GetObject::GetObject(const Request& request)
    : controller::BaseController<GetObject>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
}

GetObject::~GetObject()
{}

std::string
GetObject::printRequest() const
{
    std::stringstream ss;

    ss << " oid: " << request_.objectId;
    ss << " user: " << request_.uid;
    ss << " token: " << request_.dbToken;
    ss << " branch: " << request_.branchId;
    return ss.str();
}

namespace
{

ObjectPtr
substitutionOfCenterWithObject(const ObjectPtr& obj, ObjectsCache& cache)
{
    if (!is<PointObject>(obj.get())) {
        return ObjectPtr();
    }
    const auto& contourObjDefs = cfg()->editor()->contourObjectsDefs();
    const auto& catId = obj->categoryId();
    if (contourObjDefs.partType(catId) !=
        ContourObjectsDefs::PartType::Center) {
        return ObjectPtr();
    }
    const auto& contourDef = contourObjDefs.contourDef(catId);
    auto masters = obj->masterRelations().range(contourDef.center->roleId);
    return masters.size() != 1
        ? ObjectPtr()
        : cache.getExisting(masters.begin()->id());
}

ObjectPtr
substitutionOfLinearElementWithSimpleObject(const ObjectPtr& obj, ObjectsCache& cache)
{
    if (!is<LinearElement>(obj.get())) {
        return ObjectPtr();
    }
    const auto& contourObjDefs = cfg()->editor()->contourObjectsDefs();
    const auto& catId = obj->categoryId();
    if (contourObjDefs.partType(catId) !=
        ContourObjectsDefs::PartType::LinearElement) {
        return ObjectPtr();
    }
    const auto& contourDef = contourObjDefs.contourDef(catId);
    const RelativesLimit limit { 1, RelativesLimit::PerRole};
    auto contours =
        obj->masterRelations().range(contourDef.contour.linearElement.roleId, limit);
    if (!contours || contours->size() != 1) {
        return ObjectPtr();
    }
    const GeoObject* contour = contours->begin()->relative();
    auto contourObjs = contour->masterRelations().range(contourDef.contour.roleId, limit);
    if (!contourObjs || contourObjs->size() != 1) {
        return cache.getExisting(contour->id());
    }
    const GeoObject* contourObj = contourObjs->begin()->relative();
    return cache.getExisting(contourObj->id());
}

ObjectPtr
trySubstituteObject(ObjectPtr obj, ObjectsCache& cache)
{
    ObjectPtr substitutionObj = substitutionOfCenterWithObject(obj, cache);
    if (!substitutionObj) {
        substitutionOfLinearElementWithSimpleObject(obj, cache).swap(substitutionObj);
    }
    return substitutionObj ? substitutionObj : obj;
}

std::string
partRoleToSkip(const ObjectPtr& object)
{
    const auto& contourObjDefs = cfg()->editor()->contourObjectsDefs();
    const auto& catId = object->categoryId();
    if (contourObjDefs.partType(catId) != ContourObjectsDefs::PartType::Contour) {
        return s_emptyString;
    }
    const auto& contourDef = contourObjDefs.contourDef(catId);
    const RelativesLimit limit { 2, RelativesLimit::PerRole};
    auto elements =
        object->slaveRelations().range(contourDef.contour.linearElement.roleId, limit);
    ASSERT(elements);
    if (elements->size() > 1) {
        return contourDef.contour.roleId;
    }
    return s_emptyString;
}
}//namespace

void
GetObject::control()
{
    auto branchContext = BranchContextFacade::acquireRead(request_.branchId, request_.dbToken);
    getUser(branchContext, request_.uid).checkNotDeletedStatus();

    result_->cache.reset(
        new ObjectsCache(
            branchContext,
            boost::none));
    auto object = result_->cache->getExisting(request_.objectId);
    result_->actualObjectRevisionId = object->revision();
    result_->userId = request_.uid;
    bool objectDeleted = object->isDeleted();
    if (objectDeleted && object->revision().commitId() > 1) {
        result_->cache.reset(
            new ObjectsCache(
                branchContext,
                object->revision().commitId() - 1));
        object = result_->cache->getExisting(request_.objectId);
    }
    if (request_.substitutionPolicy == SubstitutionPolicy::Allow) {
        trySubstituteObject(object, *result_->cache).swap(object);
    }
    object->primaryEdit(true);
    CheckPermissions(
        request_.uid,
        result_->cache->workCore(),
        CheckPermissions::BannedPolicy::Allow).checkPermissionsToViewObject(object.get());
    if (object->category().system()) {
        THROW_WIKI_LOGIC_ERROR(
                ERR_MISSING_OBJECT,
                " Attempt to read system object with UI id:" << object->id());
    }
    if (object->isPrivate()) {
        auto commit = revision::Commit::load(
                result_->cache->workCore(), object->revision().commitId());
        WIKI_REQUIRE(
                commit.createdBy() == request_.uid,
                ERR_MISSING_OBJECT,
                "Attempt by: " << request_.uid
                << " to read private object id: " << object->id()
                << " owned by: " << commit.createdBy());
    }
    if (request_.partsPolicy == PartsOfComplexContourObjectsLoadPolicy::Skip) {
        result_->noLoadSlavesRoleId = partRoleToSkip(object);
    }
    result_->collection.add(object);
    if (objectDeleted && object->id() == request_.objectId) {
       result_->collection.getById(request_.objectId)->setState(GeoObject::State::Deleted);
    }
}

} // namespace wiki
} // namespace maps
