#include "make_junction.h"

#include "maps/wikimap/mapspro/services/editor/src/collection.h"
#include "maps/wikimap/mapspro/services/editor/src/commit.h"
#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/objects_cache.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/objects/junction.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/linear_element.h"


namespace maps {
namespace wiki {

namespace
{
const std::string TASK_METHOD_NAME = "ObjectsQueryJunction";
}

ObjectsQueryJunction::Request::Request(
            const std::string& ptJson,
            TZoom zoom,
            TOid linearElementId,
            UserContext userContext,
            TBranchId branchId,
            boost::optional<TId> feedbackTaskId)
    : point(createGeomFromJsonStr(ptJson))
    , zoom(zoom)
    , linearElementId(linearElementId)
    , userContext(std::move(userContext))
    , branchId(branchId)
    , feedbackTaskId(std::move(feedbackTaskId))
{
    WIKI_REQUIRE(linearElementId,
        ERR_BAD_REQUEST, "Linear element id is required.");
    WIKI_REQUIRE(!point.isNull() && point->getGeometryTypeId() == geos::geom::GEOS_POINT,
        ERR_BAD_REQUEST, "Point geometry required as POST body.");
    WIKI_REQUIRE(zoom,
        ERR_BAD_REQUEST, "Valid zoom required.");
}

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

}

std::string
ObjectsQueryJunction::Request::dump() const
{
    std::stringstream ss;
    ss << " point: " << point.dump();
    ss << " z: " << zoom;
    ss << " linearElementId: " << linearElementId;
    ss << " uid: " << userId();
    ss << " branch: " << branchId;
    if (feedbackTaskId) {
        ss << " feedback-task-id: " << *feedbackTaskId;
    }
    return ss.str();
}

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

void
ObjectsQueryJunction::control()
{
    auto branchCtx = BranchContextFacade::acquireWrite(request_.branchId, request_.userId());
    make_unique<ObjectsCache>(branchCtx, boost::none).swap(result_->cache);
    const auto editorCfg = cfg()->editor();
    auto& cache = *result_->cache;
    auto linearElement = cache.getExisting(request_.linearElementId);
    auto topoGroup = editorCfg->topologyGroups().findGroup(linearElement->categoryId());
    WIKI_REQUIRE(topoGroup &&
        topoGroup->linearElementsCategories().count(linearElement->categoryId()),
        ERR_BAD_REQUEST,
        "Not a linear element within any topology group: " << request_.linearElementId);
    const auto& coord = request_.point->getCoordinate();
    auto pointOnElement = linearElement->geom().closestPoint(coord->x, coord->y);
    topo::Editor::EdgeData edgeData {
            topo::SourceEdgeID{request_.linearElementId, true},
            {geomToPoint(pointOnElement)},
            geomToPolyline(linearElement->geom())
        };
    NewLinearElementsDataMap newElementsData;
    auto topoContext = make_unique<TopologyEditContext>(*topoGroup, cache,
        newElementsData, MergeJunctionsPolicy::Allow);
    topo::TopologyRestrictions restrictions = topoContext->getRestrictions(request_.zoom);
    topoContext->editor().saveEdge(edgeData, restrictions);
    auto newJunctions = cache.find([](const GeoObject* obj) {
        return is<Junction>(obj) && obj->isCreated();
    });
    REQUIRE(newJunctions.size() < 2, "One new junction expected.");
    WIKI_REQUIRE(newJunctions.size() != 0, ERR_JUNCTION_ALREADY_EXISTS,
        "Hit existing junction");
    result_->junctionId = newJunctions.front()->id();
    saveAndNotify();
}

void
ObjectsQueryJunction::saveAndNotify()
{
    result_->cache->save(request_.userId(), common::COMMIT_PROPVAL_ACTION_SERVICE);
    result_->taskName = taskName();
    CommitContext commitContext{request_.feedbackTaskId};
    result_->token = observers_.doCommit(*result_->cache, request_.userContext, commitContext);
}

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

} // namespace wiki
} // namespace maps
