#pragma once

#include "extended_road_network.h"

namespace maps::mrc::gen_targets {

using EdgesWithoutOutputTargets = std::set<EdgeId>;
using EdgesWithoutInputTargets = std::set<EdgeId>;
using OverheadStartsSet = std::unordered_set<EdgeId>;
using OverheadEndsSet = std::unordered_set<EdgeId>;

// Adds target edges(with the minimal sum of the edges time penalties)
// to make the graph to fit the property: each crossroad has equal
// number of input and output targets, each target edge has at least one target
// input and at least one target output.
// Each added edge is a dublicate of some existing edge.
//
// Algorithm https://wiki.yandex-team.ru/jandekskarty/projects/catalog/ek/ek-nk/projects/automnyak/main/Algoritm-generatora-marshrutov-obezda-gorodov/#optimizacijagrafadobavlenieminimalnogopereprobega
class OptimalOverheadCreator {
public:
    // Adds minimal overhead edges to the roadNetwork
    OptimalOverheadCreator(ExtendedRoadNetwork& roadNetwork);

    // returns id of the source edge for each edge copy
    EdgeId getOriginalId(EdgeId edgeId);

private:
    // Adds to graph a copy of the source edge. The new edge will be a target.
    void addEdgeCopy(EdgeId sourceEdgeId, EdgeId newEdgeId);

    // Finds all the crossroads with overhead starts or overhead
    // ends. Adds corresponding overhead start and overhead end edges
    // to the roadNetwork_. Returns Ids of the added start end end edges.
    std::pair<OverheadStartsSet, OverheadEndsSet> createOverheadStartAndEndPoints();

    // Finds all the target edges that don't have input or output
    // target edges
    std::pair<EdgesWithoutOutputTargets, EdgesWithoutInputTargets> findDeadEndTargetEdges();

    // Finds nodes where the number of the input targets is not equal
    // to the number of the output targets. Adds corresponding
    // overhead start and overhead end edges
    // to the roadNetwork_. Returns Ids of the added edges.
    std::pair<OverheadStartsSet, OverheadEndsSet> handleUnbalancedNodes(
        const EdgesWithoutOutputTargets& edgesWithoutOutputTargets,
        const EdgesWithoutInputTargets& edgesWithoutInputTargets);

    // Handle a node where the number of the input targets is not equal
    // to the number of the output targets. Adds corresponding
    // overhead start and overhead end edges
    // to the roadNetwork_. Returns Ids of the added edges.
    std::pair<OverheadStartsSet, OverheadEndsSet> handleUnbalancedNode(
        const Node& node, int nodeInTargets, int nodeOutTargets);

    // Used when target edge doesn't have output target edges
    // Creates a temporaty edge, that has all the output edges of the
    // original edge.
    // The new edge will be used as a starting edge for overhead path search
    void createOverheadStartFromEdge(EdgeId sourceEdgeId, EdgeId newEdgeId);

    // Used when target edge doesn't have input target edges
    // Creates a temporaty edge, that has all the input edges of the
    // original edge.
    // The new edge will be used as a finish edge for overhead path search
    void createOverheadEndFromEdge(EdgeId sourceEdgeId, EdgeId newEdgeId);

    // Used when node has more input target edges than output target edges
    // Takes all the output edges, which can be copied and
    // creates a temporary input edge that has all that output edges.
    // The new edge will be used as a starting edge for overhead path search
    void createOverheadStartFromNode(const Node& node, EdgeId newEdgeId);

    // Used when node has more output target edges than input target edges
    // Takes all the input edges, which can be copied and
    // creates a temporary output edge that has all that input edges.
    // The new edge will be used as a finish edge for overhead path search
    void createOverheadEndFromNode(const Node& node, EdgeId newEdgeId);

    // Connects each overhead start edge with some overhead end edges with the
    // minimal connections length sum.
    // Return result pairs <overheadStart, overheadEnd>
    std::set<std::pair<EdgeId, EdgeId>> getOptimalStartAndEndPairs(
        const OverheadStartsSet& overheadStartsSet,
        const OverheadEndsSet& overheadEndsSet);

    // Adds optimal overhead edges to the graph(adds edges of the
    // shortest path from start to end for each start and end pair)
    void addNecessaryEdgesCopies(
        std::set<std::pair<EdgeId, EdgeId>>& startAndEndPairs);

private:
    // overhead edges and temporary edges will be added to roadNetwork_ in constructor
    ExtendedRoadNetwork& roadNetwork_;

    // ExtendedRoadNetwork can create copies of edges(with new edgeId). This map
    // contains id of the source edge for each created edge (and edgeId
    // for each old edge).
    std::unordered_map<EdgeId, EdgeId> originalId_;
};

} // namespace maps::mrc::gen_targets
