#pragma once

#include <maps/libs/common/include/exception.h>

#include <iostream>
#include <vector>
#include <set>
#include <map>

namespace maps {
namespace wiki {
namespace autocart {

constexpr int INDEX_BEFORE_ZERO = -1;
constexpr int INVALID_INDEX = -1;


typedef std::vector<int>  VerticesPath;
typedef std::vector<int>  EdgesPath;

template <typename VertexData, typename EdgeData>
class Graph {
public:
    template <typename T>
    struct VertexImpl {
        int index;
        std::set<int> edges;
        T data;
    };

    template <typename T>
    struct EdgeImpl {
        int index;
        int vertex1, vertex2;
        int getOverVertex(int vertex) const
        {
            if (vertex == vertex1)
                return vertex2;
            else if (vertex == vertex2)
                return vertex1;
            return INVALID_INDEX;
        }
        T data;
    };

    using Vertex        = VertexImpl<VertexData>;
    using Edge          = EdgeImpl<EdgeData>;
    using GraphIndices  = Graph<int, int>;

    using VerticesMap   = std::map<int, Vertex>;
    using EdgesMap      = std::map<int, Edge>;
public:
    Graph()
        : lastVertexIdx_(INDEX_BEFORE_ZERO)
        , lastEdgeIdx_(INDEX_BEFORE_ZERO)
    {}

    ~Graph() = default;

    void clear()
    {
        lastVertexIdx_ = INDEX_BEFORE_ZERO;
        lastEdgeIdx_ = INDEX_BEFORE_ZERO;

        vertices_.clear();
        edges_.clear();
    }

    ////////////////////////////////////
    // methods for work with vertices //
    ////////////////////////////////////
    int addVertex(const VertexData &data)
    {
        lastVertexIdx_++;
        Vertex vertex;
        vertex.index = lastVertexIdx_;
        vertex.data = data;
        vertices_.emplace(lastVertexIdx_, vertex);
        return lastVertexIdx_;
    }

    void eraseVertex(int vertex)
    {
        auto it = vertices_.find(vertex);
        REQUIRE (it != vertices_.end(), "Unable to erase vertex with index:" << vertex);

        for (auto edge : it->second.edges)
        {
            auto ite = edges_.find(edge);
            if (ite == edges_.end())
                continue;

            const int otherVert = ite->second.getOverVertex(vertex);
            auto itv = vertices_.find(otherVert);
            if (itv != vertices_.end())
                itv->second.edges.erase(edge);
            edges_.erase(ite);
        }
        vertices_.erase(it);
    }

    auto beginVertices()
    {
        return vertices_.begin();
    }

    auto beginVertices() const
    {
        return vertices_.begin();
    }

    auto endVertices()
    {
        return vertices_.end();
    }

    auto endVertices() const
    {
        return vertices_.end();
    }

    auto findVertex(int vertex) const
    {
        return vertices_.find(vertex);
    }

    size_t verticesSize() const
    {
        return vertices_.size();
    }

    /////////////////////////////////
    // methods for work with edges //
    /////////////////////////////////
    int addEdge(int vertex1, int vertex2, EdgeData data)
    {
        auto it1 = vertices_.find(vertex1);
        auto it2 = vertices_.find(vertex2);
        REQUIRE ((it1 != vertices_.end() && it2 != vertices_.end()),
            "Unable to create edge because invalid vertices index: " << vertex1 << ", " << vertex2);

        lastEdgeIdx_++;
        Edge edge;
        edge.index = lastEdgeIdx_;
        edge.vertex1 = vertex1;
        edge.vertex2 = vertex2;
        edge.data = data;
        it1->second.edges.insert(lastEdgeIdx_);
        it2->second.edges.insert(lastEdgeIdx_);
        edges_.emplace(lastEdgeIdx_, edge);
        return lastEdgeIdx_;
    }

    void eraseEdge(int edge)
    {
        auto it = edges_.find(edge);
        REQUIRE (it != edges_.end(), "Unable to erase edge with index: " << edge);
        eraseEdge(it);
    }

    void eraseEdge(int vertex1, int vertex2)
    {
        //TODO OPT
        int edge = findCommonEdge(vertex1, vertex2);
        if (INVALID_INDEX == edge)
            return;

        auto it = edges_.find(edge);
        REQUIRE (it != edges_.end(), "Unable to erase edge with index:" << edge);

        eraseEdgeInVertex(edge, it->second.vertex1);
        eraseEdgeInVertex(edge, it->second.vertex2);

        edges_.erase(it);
    }

    auto eraseEdge(typename EdgesMap::iterator it)
    {
        eraseEdgeInVertex(it->first, it->second.vertex1);
        eraseEdgeInVertex(it->first, it->second.vertex2);

        return edges_.erase(it);
    }

    auto beginEdges()
    {
        return edges_.begin();
    }

    auto beginEdges() const
    {
        return edges_.begin();
    }

    auto endEdges()
    {
        return edges_.end();
    }

    auto endEdges() const
    {
        return edges_.end();
    }

    auto findEdge(int edge) const
    {
        return edges_.find(edge);
    }

    size_t edgesSize() const
    {
        return edges_.size();
    }

    void clearEdges()
    {
        edges_.clear();
        lastEdgeIdx_ = -1;
        for (typename VerticesMap::iterator it = vertices_.begin();
                                   it != vertices_.end();
                                   it++) {
            it->second.edges.clear();
        }
    }
public:
    /////////////////////////////////
    // complex methods             //
    /////////////////////////////////
    void eraseLeaves()
    {
        bool doit = true;
        while (doit)
        {
            doit = false;
            for (auto it : vertices_)
            {
                if (it.second.edges.size() <= 1)
                {
                    eraseVertex(it.first);
                    doit = true;
                    break;
                }
            }
        }
    }

    //forest[i] - граф у которого к вершине в виде данных привязан индекс вершины
    //            в данном графе, к ребру в виде данных привязан индекс ребра в
    //            данном графе
    std::vector<GraphIndices> extractSpanningForest() const
    {
        std::vector<GraphIndices> forest;
        std::map<int, int> verts;
        //verts - index of vertex (in this graph) map to the index of the vertex in the tree,
        //        which contain this vertex


        for (auto it = vertices_.begin(); it != vertices_.end(); it++)
        {
            verts[it->first] = INVALID_INDEX;
        }

        int restVerts = (int)vertices_.size();

        while (0 < restVerts)
        {
            std::vector<int> lastVerts;
            for (auto it = verts.begin(); it != verts.end(); it++)
            {
                if (INVALID_INDEX == it->second)
                {
                    lastVerts.push_back(it->first);
                    break;
                }
            }

            GraphIndices graph;
            while (!lastVerts.empty())
            {
                int vertId = lastVerts.back(); lastVerts.pop_back();
                const Vertex &vert = vertices_.find(vertId)->second;

                if (INVALID_INDEX == verts[vertId])
                {
                    verts[vertId] = graph.addVertex(vertId);
                    restVerts--;
                }

                for (auto it = vert.edges.begin(); it != vert.edges.end(); it++)
                {
                    const Edge &edge = edges_.find(*it)->second;
                    int vertOver = edge.getOverVertex(vertId);
                    if (INVALID_INDEX != verts[vertOver])
                        continue;
                    int temp = graph.addVertex(vertOver);
                    verts[vertOver] = temp;
                    restVerts--;

                    graph.addEdge(verts[vertId], temp, *it);
                    lastVerts.push_back(vertOver);
                }
            }
            if (0 < graph.edgesSize())
                forest.emplace_back(graph);
        }
        return forest;
    }

    //cycles[i] - contain fundamental cycles related to i-th connected component of graph
    std::vector<std::vector<EdgesPath>> extractFundamentalCyclesEdges() const
    {
        std::vector<std::vector<EdgesPath>> cycles;

        std::vector<GraphIndices> forest = extractSpanningForest();
        cycles.resize(forest.size());

        std::set<int> edgesMark;
        std::map<int, std::pair<int, int> > vertsMap;
        //vertsMap - index of vertex (in this graph) map to the pair:
        //                1. index of the forest, which contain this vertex
        //                2. index of the vertex in the forest

        for (int i = 0; i < (int)forest.size(); i++)
        {
            const auto &graph = forest[i];
            for (auto it = graph.beginEdges(); it != graph.endEdges(); ++it)
            {
                edgesMark.insert(it->second.data);
            }
            for (auto it = graph.beginVertices(); it != graph.endVertices(); ++it)
            {
                vertsMap[it->second.data] = std::pair<int, int>(i, it->first);
            }
        }

        for (auto it = edges_.begin(); it != edges_.end(); ++it)
        {
            if (edgesMark.find(it->first) != edgesMark.end())
                continue;

            const auto itv1 = vertsMap.find(it->second.vertex1);
            REQUIRE (itv1 != vertsMap.end(), "Invalid forest of the vertex");
            const auto itv2 = vertsMap.find(it->second.vertex2);
            REQUIRE (itv2 != vertsMap.end(), "Invalid forest of the vertex");
            REQUIRE (itv1->second.first == itv2->second.first,
                "The edge connects vertices from different spanning forests");

            const int forestIdx = itv1->second.first;
            EdgesPath path;
            auto &graph = forest[forestIdx];
            graph.findPathEdges(itv1->second.second, itv2->second.second, path);
            for (int i = 0; i < (int)path.size(); i++)
            {
                path[i] = graph.findEdge(path[i])->second.data;
            }
            path.push_back(it->first);
            cycles[forestIdx].emplace_back(path);
        }
        return cycles;
    }

    void findPathVerts(int vertex1, int vertex2, VerticesPath &path) const
    {
        std::map<int, int> prevVert;
        prevVert[vertex1] = INVALID_INDEX;

        std::vector<int> lastVerts(1, vertex1);
        while (!lastVerts.empty())
        {
            int vertId = lastVerts.back(); lastVerts.pop_back();
            const Vertex &vert = vertices_.find(vertId)->second;
            for (auto it = vert.edges.begin(); it != vert.edges.end(); it++)
            {
                const Edge &edge = edges_.find(*it)->second;
                int vertOver = edge.getOverVertex(vertId);
                if (vertOver == vertex2)
                {
                    path.push_back(vertex2);
                    while (vertId != vertex1)
                    {
                        path.push_back(vertId);
                        vertId = prevVert[vertId];
                    }
                    path.push_back(vertex1);
                    return;
                }
                if (prevVert.find(vertOver) == prevVert.end())
                {
                    prevVert[vertOver] = vertId;
                    lastVerts.push_back(vertOver);
                }
            }
        }
    }

    void findPathEdges(int vertex1, int vertex2, EdgesPath &path) const
    {
        //std::pair<int, int> - first is id of prev vertex, second is id of edge to prev vert
        std::map<int, std::pair<int, int>> prevVert;
        prevVert[vertex1] = std::pair<int, int>(INVALID_INDEX, INVALID_INDEX);

        std::vector<int> lastVerts(1, vertex1);
        while (!lastVerts.empty())
        {
            int vertId = lastVerts.back(); lastVerts.pop_back();
            const Vertex &vert = vertices_.find(vertId)->second;
            for (auto it = vert.edges.begin(); it != vert.edges.end(); it++)
            {
                const Edge &edge = edges_.find(*it)->second;
                int vertOver = edge.getOverVertex(vertId);
                if (vertOver == vertex2)
                {
                    path.push_back(edge.index);
                    while (vertId != vertex1)
                    {
                        path.push_back(prevVert[vertId].second);
                        vertId = prevVert[vertId].first;
                    }
                    return;
                }
                if (prevVert.find(vertOver) == prevVert.end())
                {
                    prevVert[vertOver] = std::pair<int, int>(vertId, edge.index);
                    lastVerts.push_back(vertOver);
                }
            }
        }
    }

    // if woEdge != INVALID_INDEX , then edge with this ID will not used for connectivity check
    bool isVertsConnected(int vertex1, int vertex2, int woEdge = INVALID_INDEX) const
    {
        std::map<int, int> prevVert;
        prevVert[vertex1] = INVALID_INDEX;

        std::vector<int> lastVerts(1, vertex1);
        while (!lastVerts.empty())
        {
            int vertId = lastVerts.back(); lastVerts.pop_back();
            const Vertex &vert = vertices_.find(vertId)->second;
            for (auto it = vert.edges.begin(); it != vert.edges.end(); it++)
            {
                if (woEdge == (*it))
                    continue;
                const Edge &edge = edges_.find(*it)->second;
                int vertOver = edge.getOverVertex(vertId);
                if (vertOver == vertex2)
                    return true;
                if (prevVert.find(vertOver) == prevVert.end())
                {
                    prevVert[vertOver] = vertId;
                    lastVerts.push_back(vertOver);
                }
            }
        }
        return false;
    }

    static bool hasCommonEdge(const Vertex &v1, const Vertex &v2)
    {
        std::set<int> intersect;
        std::set_intersection(v1.edges.begin(), v1.edges.end(), v2.edges.begin(), v2.edges.end(), std::inserter(intersect, intersect.begin()));
        return (!intersect.empty());
    }
private:
    int lastVertexIdx_;
    int lastEdgeIdx_;

    VerticesMap vertices_;
    EdgesMap edges_;

    void eraseEdgeInVertex(int edge, int vertex)
    {
        auto it = vertices_.find(vertex);
        REQUIRE (it != vertices_.end(),
            "Unable to erase edge with index: " << edge << " in vertex with index: " << vertex);
        it->second.edges.erase(edge);
    }

    int findCommonEdge(int vertex1, int vertex2) const
    {
        auto it1 = vertices_.find(vertex1);
        auto it2 = vertices_.find(vertex2);
        REQUIRE (it1 != vertices_.end() && it2 != vertices_.end(),
            "Unable to find edge because invalid vertices index: " << vertex1 << ", " << vertex2);

        std::set<int> intersect;
        std::set_intersection(it1->second.edges.begin(), it1->second.edges.end(), it2->second.edges.begin(), it2->second.edges.end(), std::inserter(intersect, intersect.begin()));

        if (intersect.empty())
            return INVALID_INDEX;
        return *(intersect.begin());
    }
};

} //namespace autocart
} //namespace wiki
} //namespace maps

