#include "draw_optimization.h"

#include <maps/libs/log8/include/log8.h>

namespace maps::mrc::gen_targets {

using Point = std::pair<double, double>;
using Segment = std::pair<Point, Point>;

// @brief Deletes all the non target edges from a task and return the
// result task
Path filterTargetEdges(const Path& task)
{
    Path result;
    for (auto& pathEdge : task) {
        if (pathEdge.isUsefulAsTarget) {
            result.push_back(pathEdge);
        }
    }
    return result;
}

std::vector<Segment>getEdgeSegments(const Edge& edge) {

    std::vector<Segment> result;

    for (size_t i = 0; i < edge.geom.segmentsNumber(); i++) {
        geolib3::Segment2 segment = edge.geom.segmentAt(i);
        Segment segmentPoints;
        segmentPoints = std::make_pair(
            std::make_pair(segment.start().x(),segment.start().y()),
            std::make_pair(segment.end().x(), segment.end().y()));
        result.push_back(segmentPoints);
    }

    return result;
}

void checkTargetsOptimization(const Path& originalTask,
                                         const PathWithData& optimizedTask,
                                         const RoadNetworkData& roadNetwork)
{
    std::set<Segment> originalSegments;
    for (auto& pathEdge : originalTask) {
        if (!pathEdge.isUsefulAsTarget) {
            continue;
        }
        const Edge& edge = roadNetwork.edge(pathEdge.edgeId);
        auto segments = getEdgeSegments(edge);
        for (auto& segment : segments) {
            originalSegments.insert(segment);
        }
    }

    std::set<Segment> optimizedSegments;
    for (auto& pathEdge : optimizedTask) {
        if (!pathEdge.isUsefulAsTarget) {
            continue;
        }
        const Edge& edge = pathEdge.edge;
        auto segments = getEdgeSegments(edge);
        for (auto& segment : segments) {
            optimizedSegments.insert(segment);
        }
    }

    REQUIRE(originalSegments.size() == optimizedSegments.size(),
            "Number of original segments and number of optimized segments"
            " are not equal " << originalSegments.size() << " " <<  optimizedSegments.size());
    for (auto& segment : originalSegments) {
        REQUIRE(optimizedSegments.count(segment),
                "Optimized task doesn't contain some of original segments");
    }
}

PathWithData optimizeTargertsForDrawing(const Path& task,
                                        const RoadNetworkData& roadNetwork) {
    std::unordered_map<EdgeId, Edge> edges;
    std::vector<EdgeId> edgeIds;
    for (auto& taskEdge : task) {
        if (taskEdge.isUsefulAsTarget) {
            edges[taskEdge.edgeId] = roadNetwork.edge(taskEdge.edgeId);
            edgeIds.push_back(taskEdge.edgeId);
        }
    }

    for (auto id : edgeIds) {
        if (!edges.count(id)) {
            continue;
        }
        bool edgeWasChanged = true;

        while(edgeWasChanged) {
            edgeWasChanged = false;
            int outTargets = 0;
            for (EdgeId outEdgeId : edges[id].outEdges) {
                if (edges.count(outEdgeId)) {
                    outTargets++;
                }
            }

            for (EdgeId outEdgeId : edges[id].outEdges) {
                if (!edges.count(outEdgeId) || outEdgeId == edges[id].id) {
                    continue;
                }
                const Edge& outEdge = edges[outEdgeId];
                if (outTargets > 1
                    && turnIsUTurn(edges[id].outgoingDirection(),
                                   outEdge.incomingDirection())) {
                    continue;
                }

                edges[id].length += outEdge.length;
                edges[id].geom.extend(outEdge.geom, geolib3::MergeEqualPoints);
                edges[id].outEdges = outEdge.outEdges;
                edges.erase(outEdgeId);
                edgeWasChanged = true;
                break;
            }
        }
    }

    PathWithData optimizedTask;
    for (auto& it : edges) {
        optimizedTask.push_back({it.second, true});
    }
    INFO() << "optimized task: " << task.size() << " -> " << optimizedTask.size();
    checkTargetsOptimization(task, optimizedTask, roadNetwork);
    return optimizedTask;
}

} // namespace maps::mrc::gen_targets
