#include "objects_update_snap.h"

#include "maps/wikimap/mapspro/services/editor/src/edit_options.h"
#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/collection.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/junction.h"
#include "maps/wikimap/mapspro/services/editor/src/commit.h"
#include "maps/wikimap/mapspro/services/editor/src/topo_storage.h"
#include "maps/wikimap/mapspro/services/editor/src/topological/topological_callbacks.h"
#include "maps/wikimap/mapspro/services/editor/src/topological/topo_edit_context.h"
#include "maps/wikimap/mapspro/services/editor/src/relations_processor.h"
#include "maps/wikimap/mapspro/services/editor/src/objects_cache.h"
#include "maps/wikimap/mapspro/services/editor/src/context.h"
#include "maps/wikimap/mapspro/services/editor/src/serialize/json_parser.h"
#include "maps/wikimap/mapspro/services/editor/src/serialize/xml_parser.h"
#include "maps/wikimap/mapspro/services/editor/src/serialize/request_data.h"

#include <yandex/maps/wiki/topo/editor.h>
#include <yandex/maps/wiki/topo/cache.h>
#include <yandex/maps/wiki/topo/common.h>

namespace maps {
namespace wiki {

namespace {

const std::string TASK_METHOD_NAME = "ObjectsUpdateSnap";
const std::string ACTION_ID_SNAP = "snap";

class ObjectsUpdateSnapRequestData : public RequestData
{
public:
    ObjectsUpdateSnapRequestData(common::FormatType format, const std::string& body, ObjectsCache& cache)
    {
        parse(format, body, cache);
    }

    const ObjectEditContext& editContext() const { ASSERT(editContext_); return *editContext_; }

protected:
    void parseContextDataJson(const json::Value& contextObject, ObjectsCache&) override
    {
        editContext_ = JsonParser::readObjectEditContext(contextObject);
        WIKI_REQUIRE(
            editContext_,
            ERR_BAD_REQUEST,
            "Edit context missing for snap");
    }

    void parseContextDataXml(const maps::xml3::Node& contextNode, ObjectsCache&) override
    {
        xml3::Node snapContextNode = contextNode.node("wmp:snap");
        editContext_ = XmlParser::readObjectEditContext(snapContextNode);
        WIKI_REQUIRE(
            editContext_,
            ERR_BAD_REQUEST,
            "Edit context missing for group snap");
    }

private:
    ObjectEditContextPtr editContext_;
};

} // namespace

ObjectsUpdateSnap::ObjectsUpdateSnap(
        const ObserverCollection& observers,
        const Request& request,
        taskutils::TaskID asyncTaskID)
    : controller::BaseController<ObjectsUpdateSnap>(BOOST_CURRENT_FUNCTION, asyncTaskID)
    , observers_(observers)
    , request_(request)
{}

std::string
ObjectsUpdateSnap::printRequest() const
{
    return request_.dump();
}

std::string
ObjectsUpdateSnap::Request::dump() const
{
    std::stringstream ss;
    ss << " user: " << userId();
    ss << " branch: " << branchId;
    ss << " body: " << body;
    return ss.str();
}

void
ObjectsUpdateSnap::control()
{
    ObjectsCache cache(
        BranchContextFacade::acquireWrite(request_.branchId, request_.userId()),
        boost::none);
    ObjectsUpdateSnapRequestData requestData(request_.format, request_.body, cache);
    TOIds oids = TOIds(requestData.oids().begin(), requestData.oids().end());

    WIKI_REQUIRE(
        !oids.empty(),
        ERR_BAD_REQUEST,
        "Empty objects set");

    auto objects = cache.get(oids);
    WIKI_REQUIRE(
        objects.size() == oids.size(),
        ERR_NOT_FOUND,
        "Some objects were not loaded");

    cfg()->editor()->objectsUpdateRestrictions().checkObjects(ACTION_ID_SNAP, objects);

    const auto& categoryId = objects.front()->categoryId();
    auto topoGroup = cfg()->editor()->topologyGroups().findGroup(categoryId);
    REQUIRE(topoGroup, "No topology group set for category " << categoryId);

    NewLinearElementsDataMap newElementsData;
    TopologyEditContext topoContext(
        *topoGroup, cache, newElementsData, MergeJunctionsPolicy::Allow);
    topo::TopologyRestrictions restrictions =
        topoContext.getRestrictions(requestData.editContext().viewZoom());

    topo::NodeIDSet nodeIds;
    for (auto oid : oids) {
        nodeIds.insert(oid);
        cache.getExisting(oid)->primaryEdit(true);
    }

    topoContext.editor().snapNodes(nodeIds, restrictions);

    saveAndNotify(cache);
}

void
ObjectsUpdateSnap::saveAndNotify(ObjectsCache& cache)
{
    cache.save(request_.userId(), common::COMMIT_PROPVAL_GROUP_SNAPPED);
    result_->taskName = taskName();
    result_->token = observers_.doCommit(cache, request_.userContext);
    result_->commit = cache.savedCommit();
}

const std::string&
ObjectsUpdateSnap::taskName()
{
    return TASK_METHOD_NAME;
}

} // namespace wiki
} // namespace maps
