#include "edges_merger.h"

#include "../common.h"

namespace maps {
namespace wiki {
namespace topology_fixer {

void
EdgesMerger::operator () (TopologyData& data) const
{
    for (auto nodeId : data.nodeIds()) {
        if (!data.nodeExists(nodeId)) {
            continue;
        }
        const Node& node = data.node(nodeId);
        if (node.edgeIds().size() != 2) {
            continue;
        }
        const EdgeId edgeId1 = *node.edgeIds().begin();
        const Edge& edge1 = data.edge(edgeId1);
        const EdgeId edgeId2 = *std::next(node.edgeIds().begin());
        const Edge& edge2 = data.edge(edgeId2);
        if (edge1.fnodeId() == edge1.tnodeId() || edge2.fnodeId() == edge2.tnodeId()) {
            continue;
        }
        const auto& points1 = edge1.linestring().points();
        const auto& points2 = edge2.linestring().points();
        if (points1.size() + points2.size() > maxVerticesCount_ + 1) {
            continue;
        }
        if (!(edge1.fZlev() == edge1.tZlev() && edge1.fZlev() == edge2.fZlev() &&
            edge1.fZlev() == edge2.tZlev()))
        {
            continue;
        }
        const auto& masterIds1 = edge1.masterIds();
        const auto& masterIds2 = edge2.masterIds();
        const auto& faceIds1 = edge1.faceIds();
        const auto& faceIds2 = edge2.faceIds();
        if (!idsSymDiff(masterIds1, masterIds2).empty() ||
            !idsSymDiff(faceIds1, faceIds2).empty())
        {
            continue;
        }
        mergeEdges(data, edge1, edge2, nodeId);
        INFO() << "Removed node " << nodeId << ", merged edges " << edgeId1 << ", " << edgeId2;
    }
}

void
EdgesMerger::mergeEdges(TopologyData& data,
    const Edge& edge1, const Edge& edge2, NodeId commonNodeId) const
{
    const EdgeId edgeId1 = edge1.id();
    const EdgeId edgeId2 = edge2.id();
    const NodeId fnodeId = edge1.fnodeId() == commonNodeId
        ? edge1.tnodeId()
        : edge1.fnodeId();
    const NodeId tnodeId = edge2.fnodeId() == commonNodeId
        ? edge2.tnodeId()
        : edge2.fnodeId();
    const auto& points1 = edge1.linestring().points();
    REQUIRE(points1.size() >= 2, "Too few points in edge " << edgeId1);
    const auto& points2 = edge2.linestring().points();
    REQUIRE(points2.size() >= 2, "Too few points in edge " << edgeId2);
    geolib3::PointsVector newPoints;
    newPoints.reserve(points1.size() + points2.size() - 1);
    if (edge1.fnodeId() == commonNodeId) {
        std::copy(points1.rbegin(), points1.rend(), std::back_inserter(newPoints));
    } else {
        std::copy(points1.begin(), points1.end(), std::back_inserter(newPoints));
    }
    if (edge2.fnodeId() == commonNodeId) {
        std::copy(std::next(points2.begin()), points2.end(),
            std::back_inserter(newPoints));
    } else {
        std::copy(std::next(points2.rbegin()), points2.rend(),
            std::back_inserter(newPoints));
    }
    auto newEdgeId = data.addEdge(
        geolib3::Polyline2(std::move(newPoints)), fnodeId, tnodeId, edge1.fZlev(), edge1.fZlev());
    auto masterIds = edge1.masterIds();
    for (auto masterId : masterIds) {
        data.addMasterEdgeRel(masterId, newEdgeId);
        data.removeMasterEdgeRel(masterId, edge1.id());
        data.removeMasterEdgeRel(masterId, edge2.id());
    }
    auto faceIds = edge1.faceIds();
    for (auto faceId : faceIds) {
        data.addFaceEdge(faceId, newEdgeId);
        data.removeFaceEdgeRel(faceId, edge1.id());
        data.removeFaceEdgeRel(faceId, edge2.id());
    }
    data.removeEdge(edge1.id(), DeletionMode::Cascade);
    data.removeEdge(edge2.id(), DeletionMode::Cascade);
}

} // namespace topology_fixer
} // namespace wiki
} // namespace maps
