/*
   igraph library.
   Copyright (C) 2024  The igraph development team <igraph@igraph.org>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "igraph_reachability.h"

#include "igraph_adjlist.h"
#include "igraph_bitset_list.h"
#include "igraph_components.h"
#include "igraph_constructors.h"
#include "igraph_interface.h"


/**
 * \ingroup structural
 * \function igraph_reachability
 * \brief Calculates which vertices are reachable from each vertex in the graph.
 *
 * The resulting list will contain one bitset for each strongly connected component.
 * The bitset for component i will have its j-th bit set, if vertex j is reachable
 * from some vertex in component i in 0 or more steps.
 * In particular, a vertex is always reachable from itself.
 *
 * \param graph The graph object to analyze.
 * \param membership Pointer to an integer vector. For every vertex,
 *    the ID of its component is given. The vector will be resized as needed.
 *    This parameter must not be \c NULL.
 * \param csize Pointer to an integer vector or \c NULL. For every component, it
 *    gives its size (vertex count), the order being defined by the component
 *    IDs. The vector will be resized as needed.
 * \param no_of_components Pointer to an integer or \c NULL. The number of
 *    components will be stored here.
 * \param reach A list of bitsets representing the result. It will be resized
 *    as needed. <code>reach[membership[u]][v]</code> is set to \c true if
 *    vertex \c v is reachable from vertex \c u.
 * \param mode In directed graphs, controls the treatment of edge directions.
 *    Ignored in undirected graphs. With \c IGRAPH_OUT, reachability is computed
 *    by traversing edges along their direction. With \c IGRAPH_IN, edges are
 *    traversed opposite to their direction. With \c IGRAPH_ALL, edge directions
 *    are ignored and the graph is treated as undirected.
 * \return Error code:
 *         \c IGRAPH_ENOMEM if there is not enough memory
 *         to perform the operation.
 *
 * \sa \ref igraph_connected_components() to find the connnected components
 * of a graph; \ref igraph_count_reachable() to count how many vertices
 * are reachable from each vertex; \ref igraph_subcomponent() to find
 * which vertices are rechable from a single vertex.
 *
 * Time complexity: O(|C||V|/w + |V| + |E|), where
 * |C| is the number of strongly connected components (at most |V|),
 * |V| is the number of vertices, and
 * |E| is the number of edges respectively,
 * and w is the bit width of \type igraph_int_t, typically the
 * word size of the machine (32 or 64).
 */

igraph_error_t igraph_reachability(
        const igraph_t *graph,
        igraph_vector_int_t *membership,
        igraph_vector_int_t *csize,
        igraph_int_t *no_of_components,
        igraph_bitset_list_t *reach,
        igraph_neimode_t mode) {

    const igraph_int_t no_of_nodes = igraph_vcount(graph);
    igraph_int_t no_of_comps;
    igraph_adjlist_t adjlist, dag;

    if (mode != IGRAPH_ALL && mode != IGRAPH_OUT && mode != IGRAPH_IN) {
        IGRAPH_ERROR("Invalid mode for reachability.", IGRAPH_EINVMODE);
    }

    if (! igraph_is_directed(graph)) {
        mode = IGRAPH_ALL;
    }

    IGRAPH_CHECK(igraph_connected_components(graph,
                                             membership, csize, &no_of_comps,
                                             mode == IGRAPH_ALL ? IGRAPH_WEAK : IGRAPH_STRONG));

    if (no_of_components) {
        *no_of_components = no_of_comps;
    }

    IGRAPH_CHECK(igraph_bitset_list_resize(reach, no_of_comps));

    for (igraph_int_t comp = 0; comp < no_of_comps; comp++) {
        IGRAPH_CHECK(igraph_bitset_resize(igraph_bitset_list_get_ptr(reach, comp), no_of_nodes));
    }
    for (igraph_int_t v = 0; v < no_of_nodes; v++) {
        IGRAPH_BIT_SET(*igraph_bitset_list_get_ptr(reach, VECTOR(*membership)[v]), v);
    }

    if (mode == IGRAPH_ALL) {
        return IGRAPH_SUCCESS;
    }

    IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
    IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist);

    IGRAPH_CHECK(igraph_adjlist_init_empty(&dag, no_of_comps));
    IGRAPH_FINALLY(igraph_adjlist_destroy, &dag);

    for (igraph_int_t v = 0; v < no_of_nodes; v++) {
        const igraph_vector_int_t *neighbours = igraph_adjlist_get(&adjlist, v);
        igraph_vector_int_t *dag_neighbours = igraph_adjlist_get(&dag, VECTOR(*membership)[v]);
        const igraph_int_t n = igraph_vector_int_size(neighbours);
        for (igraph_int_t i = 0; i < n; i++) {
            igraph_int_t w = VECTOR(*neighbours)[i];
            if (VECTOR(*membership)[v] != VECTOR(*membership)[w]) {
                IGRAPH_CHECK(igraph_vector_int_push_back(dag_neighbours, VECTOR(*membership)[w]));
            }
        }
    }

    /* Iterate through strongly connected components in reverser topological order,
     * exploiting the fact that they are indexed in topological order. */
    for (igraph_int_t i = 0; i < no_of_comps; i++) {
        const igraph_int_t comp = mode == IGRAPH_IN ? i : no_of_comps - i - 1;
        const igraph_vector_int_t *dag_neighbours = igraph_adjlist_get(&dag, comp);
        igraph_bitset_t *from_bitset = igraph_bitset_list_get_ptr(reach, comp);
        const igraph_int_t n = igraph_vector_int_size(dag_neighbours);
        for (igraph_int_t j = 0; j < n; j++) {
            const igraph_bitset_t *to_bitset = igraph_bitset_list_get_ptr(reach, VECTOR(*dag_neighbours)[j]);
            igraph_bitset_or(from_bitset, from_bitset, to_bitset);
        }
    }

    igraph_adjlist_destroy(&adjlist);
    igraph_adjlist_destroy(&dag);
    IGRAPH_FINALLY_CLEAN(2);

    return IGRAPH_SUCCESS;
}


/**
 * \ingroup structural
 * \function igraph_count_reachable
 * \brief The number of vertices reachable from each vertex in the graph.
 *
 * \param graph The graph object to analyze.
 * \param counts Integer vector. <code>counts[v]</code> will store the number
 *    of vertices reachable from vertex \c v, including \c v itself.
 * \param mode In directed graphs, controls the treatment of edge directions.
 *    Ignored in undirected graphs. With \c IGRAPH_OUT, reachability is computed
 *    by traversing edges along their direction. With \c IGRAPH_IN, edges are
 *    traversed opposite to their direction. With \c IGRAPH_ALL, edge directions
 *    are ignored and the graph is treated as undirected.
 * \return Error code:
 *         \c IGRAPH_ENOMEM if there is not enough memory
 *         to perform the operation.
 *
 * \sa \ref igraph_connected_components(), \ref igraph_transitive_closure()
 *
 * Time complexity: O(|C||V|/w + |V| + |E|), where
 * |C| is the number of strongly connected components (at most |V|),
 * |V| is the number of vertices, and
 * |E| is the number of edges respectively,
 * and w is the bit width of \type igraph_int_t, typically the
 * word size of the machine (32 or 64).
 */

igraph_error_t igraph_count_reachable(const igraph_t *graph,
                                      igraph_vector_int_t *counts,
                                      igraph_neimode_t mode) {

    igraph_vector_int_t membership;
    igraph_int_t no_of_nodes = igraph_vcount(graph);
    igraph_bitset_list_t reach;

    IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0);
    IGRAPH_BITSET_LIST_INIT_FINALLY(&reach, 0);

    IGRAPH_CHECK(igraph_reachability(graph, &membership, NULL, NULL, &reach, mode));

    IGRAPH_CHECK(igraph_vector_int_resize(counts, igraph_vcount(graph)));
    for (igraph_int_t i = 0; i < no_of_nodes; i++) {
        VECTOR(*counts)[i] = igraph_bitset_popcount(igraph_bitset_list_get_ptr(&reach, VECTOR(membership)[i]));
    }

    igraph_bitset_list_destroy(&reach);
    igraph_vector_int_destroy(&membership);
    IGRAPH_FINALLY_CLEAN(2);

    return IGRAPH_SUCCESS;
}


/**
 * \ingroup structural
 * \function igraph_transitive_closure
 * \brief Computes the transitive closure of a graph.
 *
 * The resulting graph will have an edge from vertex \c i to vertex \c j
 * if \c j is reachable from \c i.
 *
 * \param graph The graph object to analyze.
 * \param closure The resulting graph representing the transitive closure.
 * \return Error code:
 *         \c IGRAPH_ENOMEM if there is not enough memory
 *         to perform the operation.
 *
 * \sa \ref igraph_connected_components(), \ref igraph_count_reachable()
 *
 * Time complexity: O(|V|^2 + |E|), where
 * |V| is the number of vertices, and
 * |E| is the number of edges, respectively.
 */
igraph_error_t igraph_transitive_closure(const igraph_t *graph, igraph_t *closure) {
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
    const igraph_bool_t directed = igraph_is_directed(graph);
    igraph_vector_int_t membership, edges;
    igraph_bitset_list_t reach;

    IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0);
    IGRAPH_BITSET_LIST_INIT_FINALLY(&reach, 0);

    IGRAPH_CHECK(igraph_reachability(graph, &membership, NULL, NULL, &reach, IGRAPH_OUT));

    IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
    for (igraph_int_t u = 0; u < no_of_nodes; u++) {
        const igraph_bitset_t *row = igraph_bitset_list_get_ptr(&reach, VECTOR(membership)[u]);
        for (igraph_int_t v = directed ? 0 : u + 1; v < no_of_nodes; v++) {
            if (u != v && IGRAPH_BIT_TEST(*row, v)) {
                IGRAPH_CHECK(igraph_vector_int_push_back(&edges, u));
                IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v));
            }
        }
    }

    igraph_bitset_list_destroy(&reach);
    igraph_vector_int_destroy(&membership);
    IGRAPH_FINALLY_CLEAN(2);

    IGRAPH_CHECK(igraph_create(closure, &edges, no_of_nodes, directed));

    igraph_vector_int_destroy(&edges);
    IGRAPH_FINALLY_CLEAN(1);

    return IGRAPH_SUCCESS;
}
