#include "gen_doc/common_layers_helpers.h"

#include "gen_doc/styles.h"

namespace maps {
namespace wiki {
namespace topo {
namespace doc {

void
buildCommonResultEdgesLayers(
    const test::MockStorage& result,
    GeomLayerBuilder& builder, const test::EdgesDiffData& edgesDiff)
{
    auto storageEdgesAdaptor = test::MockStorageEdgeRangeAdaptor(result);

    const auto& editedDiff = edgesDiff.editedEdgesDiff;
    const auto& affectedDiff = edgesDiff.affectedEdgesDiff;

    builder.add(
        {"all edges after", style::ALL_EDGES},
        storageEdgesAdaptor,
        [&editedDiff, &affectedDiff] (const test::Edge& e)
        {
            return
                !editedDiff.newIds.count(e.id) &&
                !editedDiff.movedIds.count(e.id) &&
                !editedDiff.changedGeomIds.count(e.id) &&
                !affectedDiff.newIds.count(e.id) &&
                !affectedDiff.movedIds.count(e.id) &&
                !affectedDiff.changedGeomIds.count(e.id);
        });

    builder.add(
        {"edited edges new parts after", style::EDITED_EDGES_NEW_PARTS_AFTER},
        storageEdgesAdaptor,
        [&editedDiff] (const test::Edge& e) { return editedDiff.newIds.count(e.id); });

    builder.add(
        {"edited edges moved parts after", style::EDITED_EDGES_MOVED_PARTS_AFTER},
        storageEdgesAdaptor,
        [&editedDiff] (const test::Edge& e) { return editedDiff.movedIds.count(e.id); });

    builder.add(
        {"edited edges changed parts after", style::EDITED_EDGES_CHANGED_PARTS_AFTER},
        storageEdgesAdaptor,
        [&editedDiff] (const test::Edge& e) { return editedDiff.changedGeomIds.count(e.id); });

    builder.add(
        {"affected edges new parts after", style::AFFECTED_EDGES_NEW_PARTS_AFTER},
        storageEdgesAdaptor,
        [&affectedDiff] (const test::Edge& e) { return affectedDiff.newIds.count(e.id); });

    builder.add(
        {"affected edges moved parts after", style::AFFECTED_EDGES_MOVED_PARTS_AFTER},
        storageEdgesAdaptor,
        [&affectedDiff] (const test::Edge& e) { return affectedDiff.movedIds.count(e.id); });

    builder.add(
        {"affected edges changed parts after", style::AFFECTED_EDGES_CHANGED_PARTS_AFTER},
        storageEdgesAdaptor,
        [&affectedDiff] (const test::Edge& e) { return affectedDiff.changedGeomIds.count(e.id); });
}

void
buildCommonResultNodesLayers(
    const test::MockStorage& result,
    GeomLayerBuilder& builder, const test::NodesDiff& nodesDiff)
{
    auto storageNodesAdaptor = test::MockStorageNodeRangeAdaptor(result);

    builder.add(
        {"all nodes after", style::ALL_NODES},
        storageNodesAdaptor,
        [&] (const test::Node& n)
        {
            return
                !nodesDiff.newIds.count(n.id) &&
                !nodesDiff.changedGeomIds.count(n.id) &&
                !nodesDiff.changedIncidencesIds.count(n.id);
        });

    builder.add(
        {"created nodes after", style::CREATED_NODES_AFTER},
        storageNodesAdaptor,
        [&] (const test::Node& n) { return nodesDiff.newIds.count(n.id); });

    builder.add(
        {"moved nodes after", style::MOVED_NODES_AFTER},
        storageNodesAdaptor,
        [&] (const test::Node& n) { return nodesDiff.changedGeomIds.count(n.id); });

    builder.add(
        {"nodes with changed incidences after", style::AFFECTED_NODES_AFTER},
        storageNodesAdaptor,
        [&] (const test::Node& n) { return nodesDiff.changedIncidencesIds.count(n.id); });
}

std::list<geolib3::BoundingBox>
customGeomViewRects(
    const test::CommonTestData& testData, double poiRadius)
{
    std::list<geolib3::BoundingBox> rects;
    if (!testData.printInfo()) {
        return {};
    }
    for (const auto& point : testData.printInfo()->pointsOfInterest) {
        rects.emplace_back(point, 2.5 * poiRadius, 2.5 * poiRadius);
    }
    return rects;
}


std::list<geotex::LayerPtr>
highlightLayers(
    const test::PrintInfo& printInfo,
    const TopologyRestrictions& restrictions,
    const test::MockStorage& storage,
    const geolib3::Polyline2* newEdgeGeom)
{
    std::list<geotex::LayerPtr> layers;

    geolib3::PolylinesVector toleranceGeoms;
    geolib3::PolylinesVector vertexGravityGeoms;
    geolib3::PolylinesVector junctionGravityGeoms;

    GravityGeotexConvertor tolerancePrinter(restrictions.tolerance());
    LayerBuilder<GravityGeotexConvertor> toleranceBuilder(tolerancePrinter, layers);

    GravityGeotexConvertor vertexGravityPrinter(restrictions.vertexGravity());
    LayerBuilder<GravityGeotexConvertor> vertexGravityBuilder(vertexGravityPrinter, layers);

    GravityGeotexConvertor junctionGravityPrinter(restrictions.junctionGravity());
    LayerBuilder<GravityGeotexConvertor> junctionGravityBuilder(junctionGravityPrinter, layers);

    for (const auto& edgeGravities : printInfo.edgesGravity) {
        const auto& geom = storage.testEdge(edgeGravities.first).geom;
        if (edgeGravities.second.count(test::GravityType::Tolerance)) {
            toleranceGeoms.push_back(geom);
        }
        if (edgeGravities.second.count(test::GravityType::VertexGravity)) {
            vertexGravityGeoms.push_back(geom);
        }
        if (edgeGravities.second.count(test::GravityType::JunctionGravity)) {
            junctionGravityGeoms.push_back(geom);
        }
    }
    if (newEdgeGeom && printInfo.newGeomGravities.count(test::GravityType::Tolerance)) {
        toleranceGeoms.push_back(*newEdgeGeom);
    }
    if (newEdgeGeom && printInfo.newGeomGravities.count(test::GravityType::VertexGravity)) {
        vertexGravityGeoms.push_back(*newEdgeGeom);
    }
    if (newEdgeGeom && printInfo.newGeomGravities.count(test::GravityType::JunctionGravity)) {
        junctionGravityGeoms.push_back(*newEdgeGeom);
    }

    std::vector<geolib3::Segment2> toleranceSegments;
    for (const auto& poly : toleranceGeoms) {
        for (size_t i = 0; i < poly.segmentsNumber(); ++i) {
            toleranceSegments.push_back(poly.segmentAt(i));
        }
    }

    std::vector<geolib3::Segment2> vertexGravitySegments;
    for (const auto& poly : vertexGravityGeoms) {
        for (size_t i = 0; i < poly.segmentsNumber(); ++i) {
            vertexGravitySegments.push_back(poly.segmentAt(i));
        }
    }

    std::vector<geolib3::Segment2> junctionGravitySegments;
    for (const auto& poly : junctionGravityGeoms) {
        for (size_t i = 0; i < poly.segmentsNumber(); ++i) {
            junctionGravitySegments.push_back(poly.segmentAt(i));
        }
    }

    toleranceBuilder.add(
        {"edge tolerance gravities", style::EDGE_TOLERANCE},
        toleranceSegments
    );

    vertexGravityBuilder.add(
        {"edge vertex gravities", style::EDGE_VERTEX_GRAVITY},
        vertexGravitySegments
    );

    junctionGravityBuilder.add(
        {"edge junction gravities", style::EDGE_JUNCTION_GRAVITY},
        junctionGravitySegments
    );

    geolib3::PointsVector tolerancePoints;
    geolib3::PointsVector vertexGravityPoints;
    geolib3::PointsVector junctionGravityPoints;
    for (const auto& point : printInfo.pointsGravity) {
        if (point.gravityType == test::GravityType::Tolerance) {
            tolerancePoints.push_back(point.point);
        }
        if (point.gravityType == test::GravityType::VertexGravity) {
            vertexGravityPoints.push_back(point.point);
        }
        if (point.gravityType == test::GravityType::JunctionGravity) {
            junctionGravityPoints.push_back(point.point);
        }
    }

    toleranceBuilder.add(
        {"point tolerance gravities", style::POINT_TOLERANCE},
        tolerancePoints
    );

    vertexGravityBuilder.add(
        {"point vertex gravities", style::POINT_VERTEX_GRAVITY},
        vertexGravityPoints
    );

    junctionGravityBuilder.add(
        {"point junction gravities", style::POINT_JUNCTION_GRAVITY},
        junctionGravityPoints
    );

    return layers;
}

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