/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmComputeComponentGraph.h"

#include <algorithm>

#include <assert.h>

cmComputeComponentGraph::cmComputeComponentGraph(Graph const& input)
: InputGraph(input)
{
    // Identify components.
    this->Tarjan();

    // Compute the component graph.
    this->ComponentGraph.resize(0);
    this->ComponentGraph.resize(this->Components.size());
    this->TransferEdges();
}

cmComputeComponentGraph::~cmComputeComponentGraph() {}

void
cmComputeComponentGraph::Tarjan()
{
    int         n     = static_cast<int>(this->InputGraph.size());
    TarjanEntry entry = { 0, 0 };
    this->TarjanEntries.resize(0);
    this->TarjanEntries.resize(n, entry);
    this->TarjanComponents.resize(0);
    this->TarjanComponents.resize(n, -1);
    this->TarjanWalkId = 0;
    this->TarjanVisited.resize(0);
    this->TarjanVisited.resize(n, 0);
    for(int i = 0; i < n; ++i)
    {
        // Start a new DFS from this node if it has never been visited.
        if(!this->TarjanVisited[i])
        {
            assert(this->TarjanStack.empty());
            ++this->TarjanWalkId;
            this->TarjanIndex = 0;
            this->TarjanVisit(i);
        }
    }
}

void
cmComputeComponentGraph::TarjanVisit(int i)
{
    // We are now visiting this node.
    this->TarjanVisited[i] = this->TarjanWalkId;

    // Initialize the entry.
    this->TarjanEntries[i].Root       = i;
    this->TarjanComponents[i]         = -1;
    this->TarjanEntries[i].VisitIndex = ++this->TarjanIndex;
    this->TarjanStack.push(i);

    // Follow outgoing edges.
    EdgeList const& nl = this->InputGraph[i];
    for(cmGraphEdge const& ni : nl)
    {
        int j = ni;

        // Ignore edges to nodes that have been reached by a previous DFS
        // walk.  Since we did not reach the current node from that walk
        // it must not belong to the same component and it has already
        // been assigned to a component.
        if(this->TarjanVisited[j] > 0 &&
           this->TarjanVisited[j] < this->TarjanWalkId)
        {
            continue;
        }

        // Visit the destination if it has not yet been visited.
        if(!this->TarjanVisited[j])
        {
            this->TarjanVisit(j);
        }

        // If the destination has not yet been assigned to a component,
        // check if it has a better root for the current object.
        if(this->TarjanComponents[j] < 0)
        {
            if(this->TarjanEntries[this->TarjanEntries[j].Root].VisitIndex <
               this->TarjanEntries[this->TarjanEntries[i].Root].VisitIndex)
            {
                this->TarjanEntries[i].Root = this->TarjanEntries[j].Root;
            }
        }
    }

    // Check if we have found a component.
    if(this->TarjanEntries[i].Root == i)
    {
        // Yes.  Create it.
        int c = static_cast<int>(this->Components.size());
        this->Components.emplace_back();
        NodeList& component = this->Components[c];

        // Populate the component list.
        int j;
        do
        {
            // Get the next member of the component.
            j = this->TarjanStack.top();
            this->TarjanStack.pop();

            // Assign the member to the component.
            this->TarjanComponents[j]   = c;
            this->TarjanEntries[j].Root = i;

            // Store the node in its component.
            component.push_back(j);
        } while(j != i);

        // Sort the component members for clarity.
        std::sort(component.begin(), component.end());
    }
}

void
cmComputeComponentGraph::TransferEdges()
{
    // Map inter-component edges in the original graph to edges in the
    // component graph.
    int n = static_cast<int>(this->InputGraph.size());
    for(int i = 0; i < n; ++i)
    {
        int             i_component = this->TarjanComponents[i];
        EdgeList const& nl          = this->InputGraph[i];
        for(cmGraphEdge const& ni : nl)
        {
            int j           = ni;
            int j_component = this->TarjanComponents[j];
            if(i_component != j_component)
            {
                // We do not attempt to combine duplicate edges, but instead
                // store the inter-component edges with suitable multiplicity.
                this->ComponentGraph[i_component].emplace_back(j_component,
                                                               ni.IsStrong());
            }
        }
    }
}
