#include "save_objects.h"

#include "save_edge.h"
#include "move_node.h"
#include "save_edge_helpers.h"
#include "preload_objects.h"
#include "process_events.h"
#include "../geom_tools/intersector.h"
#include "../index/spatial_index.h"
#include "../events_data.h"
#include "../editor_impl.h"
#include "../cache_impl.h"
#include "../graph.h"

#include <yandex/maps/wiki/topo/storage.h>
#include <yandex/maps/wiki/topo/events.h>
#include <yandex/maps/wiki/topo/exception.h>

#include <maps/libs/geolib/include/intersection.h>
#include <maps/libs/geolib/include/distance.h>
#include <maps/libs/geolib/include/vector.h>

#include <vector>
#include <algorithm>


namespace maps {
namespace wiki {
namespace topo {

namespace {

void
checkUpdateData(const Editor::TopologyData& data, const CacheImpl& /*cache*/)
{
    for (const auto& edgeData : data.edgesData) {
        if (!edgeData.splitPoints.empty()) {
            throw GeomProcessingError(ErrorCode::Unsupported)
                << "Split points are not currently supported for multiple editing";
        }
    }
}

} // namespace

SaveObjectsOperation::SaveObjectsOperation(
        const Callbacks& callbacks,
        CacheImpl& cache,
        const Editor::TopologyData& data,
        const TopologyRestrictions& restrictions)
    : Operation(callbacks, cache)
    , data_(data)
    , restrictions_(restrictions)
{
    for (auto& data : data_.edgesData) {
        data.geom = prepareGeometry(data.geom);
    }
}

void SaveObjectsOperation::operator () ()
{
    preloadObjects(cache_, TopologyUpdateData(data_), restrictions_);
    checkUpdateData(data_, cache_);

    std::map<EdgeID, IncidentNodes> incidentNodes;
    for (const auto& data : data_.edgesData) {
        if (data.id.exists()) {
            const Edge& edge = cache_.graph().edge(data.id.id());
            incidentNodes.insert(
                {data.id.id(), IncidentNodes{edge.startNode(), edge.endNode()}}
            );
        }
    }

    EdgeIDSet incidentEdges;
    for (const auto& data: data_.nodesData) {
        for (const auto edgePtr: cache_.graph().incidentEdges(data.id)) {
            incidentEdges.insert(edgePtr->id());
        }
    }

    for (const auto& data: data_.nodesData) {
        MoveNodeOperation moveNode(callbacks_, cache_, data, restrictions_);
        moveNode.moveNodeOnly();
    }

    for (const auto& data : data_.edgesData) {
        if (data.id.exists()) {
            cache_.graph().deleteEdge(data.id.id());
        }
    }

    for (const auto id: incidentEdges) {
        if (!incidentNodes.count(id)) {
            const Edge& edge = cache_.graph().edge(id);
            saveEdge(
                SourceEdgeID{id, true},
                edge.geom(),
                IncidentNodes{edge.startNode(), edge.endNode()}
            );
        }
    }

    for (const auto& data : data_.edgesData) {
        if (data.id.exists()) {
            saveEdge(
                data.id,
                data.geom,
                incidentNodes.at(data.id.id())
            );
        }
    }

    for (const auto& data : data_.edgesData) {
        if (!data.id.exists()) {
            saveEdge(data.id, data.geom, boost::none);
        }
    }
}

void
SaveObjectsOperation::saveEdge(
    SourceEdgeID srcEdgeId,
    const geolib3::Polyline2& geom,
    boost::optional<IncidentNodes> oldIncidentNodes)
{

    geom::Intersector intersector(
        cache_.graph(), restrictions_,
        [&] (NodeID /*nodeId*/) {
            return false;
        }
    );

    geom::Intersector::Events events = intersector(srcEdgeId, geom, {});
    const SplitDataPtr& event = events.editedEdgeEvent;

    if (srcEdgeId.exists()) {
        REQUIRE(
            oldIncidentNodes,
            "Incidences not set for existing edge " << srcEdgeId.id()
        );
    }

    event->oldIncidentNodes = oldIncidentNodes;

    REQUIRE(
        event->sourceId == srcEdgeId,
        "Editing edge source id error, source id "
        << event->sourceId.id()
    );

    processEdgesSaving(cache_, callbacks_, events);
}

} // namespace topo
} // namespace wiki
} // namespace maps
