/******************************************************************************
 *
 * Project:  GDAL
 * Purpose:  Implementation of topologic sorting over a directed acyclic graph
 * Author:   Even Rouault
 *
 ******************************************************************************
 * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys dot com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include <algorithm>
#include <list>
#include <map>
#include <set>
#include <stack>
#include <string>
#include <vector>

#include <cassert>

namespace gdal
{

// See https://en.wikipedia.org/wiki/Directed_acyclic_graph
template <class T, class V = std::string> class DirectedAcyclicGraph
{
    std::set<T> nodes;
    std::map<T, std::set<T>>
        incomingNodes;  // incomingNodes[j][i] means an edge from i to j
    std::map<T, std::set<T>>
        outgoingNodes;  // outgoingNodes[i][j] means an edge from i to j
    std::map<T, V> names;

  public:
    DirectedAcyclicGraph() = default;

    void clear()
    {
        nodes.clear();
        incomingNodes.clear();
        outgoingNodes.clear();
        names.clear();
    }
    void addNode(const T &i, const V &s)
    {
        nodes.insert(i);
        names[i] = s;
    }
    void removeNode(const T &i);
    const char *addEdge(const T &i, const T &j);
    const char *removeEdge(const T &i, const T &j);
    bool isTherePathFromTo(const T &i, const T &j) const;
    std::vector<T> findStartingNodes() const;
    std::vector<T> getTopologicalOrdering();
};

template <class T, class V>
void DirectedAcyclicGraph<T, V>::removeNode(const T &i)
{
    nodes.erase(i);
    names.erase(i);

    {
        auto incomingIter = incomingNodes.find(i);
        if (incomingIter != incomingNodes.end())
        {
            for (const T &j : incomingIter->second)
            {
                auto outgoingIter = outgoingNodes.find(j);
                assert(outgoingIter != outgoingNodes.end());
                auto iterJI = outgoingIter->second.find(i);
                assert(iterJI != outgoingIter->second.end());
                outgoingIter->second.erase(iterJI);
                if (outgoingIter->second.empty())
                    outgoingNodes.erase(outgoingIter);
            }
            incomingNodes.erase(incomingIter);
        }
    }

    {
        auto outgoingIter = outgoingNodes.find(i);
        if (outgoingIter != outgoingNodes.end())
        {
            for (const T &j : outgoingIter->second)
            {
                auto incomingIter = incomingNodes.find(j);
                assert(incomingIter != incomingNodes.end());
                auto iterJI = incomingIter->second.find(i);
                assert(iterJI != incomingIter->second.end());
                incomingIter->second.erase(iterJI);
                if (incomingIter->second.empty())
                    incomingNodes.erase(incomingIter);
            }
            outgoingNodes.erase(outgoingIter);
        }
    }
}

template <class T, class V>
const char *DirectedAcyclicGraph<T, V>::addEdge(const T &i, const T &j)
{
    if (i == j)
    {
        return "self cycle";
    }
    const auto iterI = outgoingNodes.find(i);
    if (iterI != outgoingNodes.end() &&
        iterI->second.find(j) != iterI->second.end())
    {
        return "already inserted edge";
    }

    if (nodes.find(i) == nodes.end())
    {
        return "node i unknown";
    }
    if (nodes.find(j) == nodes.end())
    {
        return "node j unknown";
    }

    if (isTherePathFromTo(j, i))
    {
        return "can't add edge: this would cause a cycle";
    }

    outgoingNodes[i].insert(j);
    incomingNodes[j].insert(i);
    return nullptr;
}

template <class T, class V>
const char *DirectedAcyclicGraph<T, V>::removeEdge(const T &i, const T &j)
{
    auto iterI = outgoingNodes.find(i);
    if (iterI == outgoingNodes.end())
        return "no outgoing nodes from i";
    auto iterIJ = iterI->second.find(j);
    if (iterIJ == iterI->second.end())
        return "no outgoing node from i to j";
    iterI->second.erase(iterIJ);
    if (iterI->second.empty())
        outgoingNodes.erase(iterI);

    auto iterJ = incomingNodes.find(j);
    assert(iterJ != incomingNodes.end());
    auto iterJI = iterJ->second.find(i);
    assert(iterJI != iterJ->second.end());
    iterJ->second.erase(iterJI);
    if (iterJ->second.empty())
        incomingNodes.erase(iterJ);

    return nullptr;
}

template <class T, class V>
bool DirectedAcyclicGraph<T, V>::isTherePathFromTo(const T &i, const T &j) const
{
    std::set<T> plannedForVisit;
    std::stack<T> toVisit;
    toVisit.push(i);
    plannedForVisit.insert(i);
    while (!toVisit.empty())
    {
        const T n = toVisit.top();
        toVisit.pop();
        if (n == j)
            return true;
        const auto iter = outgoingNodes.find(n);
        if (iter != outgoingNodes.end())
        {
            for (const T &k : iter->second)
            {
                if (plannedForVisit.find(k) == plannedForVisit.end())
                {
                    plannedForVisit.insert(k);
                    toVisit.push(k);
                }
            }
        }
    }
    return false;
}

template <class T, class V>
std::vector<T> DirectedAcyclicGraph<T, V>::findStartingNodes() const
{
    std::vector<T> ret;
    for (const auto &i : nodes)
    {
        if (incomingNodes.find(i) == incomingNodes.end())
            ret.emplace_back(i);
    }
    return ret;
}

// Kahn's algorithm:
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
template <class T, class V>
std::vector<T> DirectedAcyclicGraph<T, V>::getTopologicalOrdering()
{
    std::vector<T> ret;
    ret.reserve(nodes.size());

    const auto cmp = [this](const T &a, const T &b)
    { return names.find(a)->second < names.find(b)->second; };
    std::set<T, decltype(cmp)> S(cmp);

    const auto sn = findStartingNodes();
    for (const auto &i : sn)
        S.insert(i);

    while (true)
    {
        auto iterFirst = S.begin();
        if (iterFirst == S.end())
            break;
        const auto n = *iterFirst;
        S.erase(iterFirst);
        ret.emplace_back(n);

        const auto iter = outgoingNodes.find(n);
        if (iter != outgoingNodes.end())
        {
            // Need to take a copy as we remove edges during iteration
            const auto myOutgoingNodes = iter->second;
            for (const T &m : myOutgoingNodes)
            {
                const char *retRemoveEdge = removeEdge(n, m);
                (void)retRemoveEdge;
                assert(retRemoveEdge == nullptr);
                if (incomingNodes.find(m) == incomingNodes.end())
                {
                    S.insert(m);
                }
            }
        }
    }

    // Should not happen for a direct acyclic graph
    assert(incomingNodes.empty());
    assert(outgoingNodes.empty());

    return ret;
}

}  // namespace gdal
