%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2001-2002, 2004-2006, 2008, 2010-2011 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% Authors: conway, zs.
%
% This module allows you build a description of a directed graph (represented
% as a set of arcs between nodes identified by dense small integers) and then
% find the strongly connected components (cliques) of that graph.
%---------------------------------------------------------------------------%

:- module cliques.

:- interface.

:- type graph.

:- import_module list.
:- import_module set.

%---------------------------------------------------------------------------%

    % Create a graph with no edges.
    %
:- pred init(graph::out) is det.

    % Add an arc from one node to another.
    %
:- pred add_arc(graph::in, int::in, int::in, graph::out) is det.

    % Perform a topological sort on the graph. Each set of integers in the
    % resulting list gives the ids of the nodes in a clique. The list contains
    % the cliques in top-down order: if there is an arc from node A to node B
    % and the two nodes are not in the same clique, then the clique containing
    % node A will be before the clique containing node B.
    %
:- pred topological_sort(graph::in, list(set(int))::out) is det.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

:- import_module array_util.
:- import_module dense_bitset.

:- import_module array.
:- import_module int.
:- import_module io.
:- import_module string.

%---------------------------------------------------------------------------%

:- type graph
    --->    graph(
                int,
                array(set(int))
            ).

:- type visit == dense_bitset.

%---------------------------------------------------------------------------%

init(graph(1, Array)) :-
    % The initial array size doesn't really matter.
    array.init(16, set.init, Array).

add_arc(graph(Size0, Array0), From, To, Graph) :-
    ( if array.in_bounds(Array0, From) then
        array.lookup(Array0, From, Tos0),
        set.insert(To, Tos0, Tos),
        array.set(From, Tos, u(Array0), Array),
        Size = int.max(int.max(From, To), Size0),
        Graph = graph(Size, Array)
    else
        array.size(Array0, Size),
        array.resize(Size * 2, init, u(Array0), Array1),
        add_arc(graph(Size0, Array1), From, To, Graph)
    ).

:- pred successors(graph::in, int::in, set(int)::out) is det.

successors(graph(_Size, Array), From, Tos) :-
    ( if array.in_bounds(Array, From) then
        array.lookup(Array, From, Tos)
    else
        Tos = set.init
    ).

:- pred mklist(int::in, list(int)::in, list(int)::out) is det.

mklist(N, Acc0, Acc) :-
    ( if N < 0 then
        Acc = Acc0
    else
        Acc1 = [N | Acc0],
        mklist(N - 1, Acc1, Acc)
    ).

topological_sort(Graph, Cliques) :-
    trace [compiletime(flag("tsort")), io(!IO)] (
        io.output_stream(OutputStream, !IO),
        io.write_string(OutputStream, "\nthe graph:\n", !IO),
        write_graph(OutputStream, Graph, !IO),
        io.nl(OutputStream, !IO)
    ),

    dfs_graph(Graph, Dfs),

    trace [compiletime(flag("tsort")), io(!IO)] (
        io.output_stream(OutputStream, !IO),
        io.write_string(OutputStream, "\nthe dfs:\n", !IO),
        write_dfs(OutputStream, Dfs, !IO),
        io.nl(OutputStream, !IO)
    ),

    inverse(Graph, InvGraph),

    trace [compiletime(flag("tsort")), io(!IO)] (
        io.output_stream(OutputStream, !IO),
        io.write_string(OutputStream, "\nthe inverse graph:\n", !IO),
        write_graph(OutputStream, InvGraph, !IO),
        io.nl(OutputStream, !IO)
    ),

    Visit = dense_bitset.init,
    tsort(Dfs, InvGraph, Visit, [], Cliques0),
    reverse(Cliques0, [], Cliques),

    trace [compiletime(flag("tsort")), io(!IO)] (
        io.output_stream(OutputStream, !IO),
        io.write_string(OutputStream, "\nthe cliques:\n", !IO),
        write_cliques(OutputStream, Cliques, !IO),
        io.nl(OutputStream, !IO)
    ).

    % This is a copy of list.reverse_2, we copy it here so that it can be
    % compiled with --trace minimum even when the version in the standard
    % library is compiled with --trace deep.
    %
:- pred reverse(list(T)::in, list(T)::in, list(T)::out) is det.

reverse([], L, L).
reverse([X | Xs], L0, L) :-
    reverse(Xs, [X | L0], L).

:- pred tsort(list(int)::in, graph::in, visit::array_di, list(set(int))::in,
    list(set(int))::out) is det.

tsort([], _InvGraph, _Visit, !Cliques).
tsort([Node | Nodes], InvGraph, !.Visited, !Cliques) :-
    trace [compiletime(flag("tsort_loop")), io(!IO)] (
        io.output_stream(OutputStream, !IO),
        io.format(OutputStream, "tsort check %d\n", [i(Node)], !IO)
    ),

    ( if dense_bitset.member(Node, !.Visited) then
        trace [compiletime(flag("tsort_old")), io(!IO)] (
            io.output_stream(OutputStream, !IO),
            io.format(OutputStream, "tsort old %d\n", [i(Node)], !IO)
        )
    else
        trace [compiletime(flag("tsort_new")), io(!IO)] (
            io.output_stream(OutputStream, !IO),
            io.format(OutputStream, "tsort new %d\n", [i(Node)], !IO)
        ),

        dfs([Node], InvGraph, !.Visited, [], !:Visited, CliqueList),
        set.list_to_set(CliqueList, Clique),

        trace [compiletime(flag("tsort_clique")), io(!IO)] (
            io.output_stream(OutputStream, !IO),
            io.format(OutputStream, "tsort clique %d -> ", [i(Node)], !IO),
            write_clique_nl(OutputStream, Clique, !IO)
        ),

        !:Cliques = [Clique | !.Cliques]
    ),
    tsort(Nodes, InvGraph, !.Visited, !Cliques).

    % Return a list containing all the nodes of the graph. The list is
    % effectively computed by randomly breaking all cycles, doing a pre-order
    % traversal of the resulting trees, and concatenating the resulting lists
    % in a random order.
    %
:- pred dfs_graph(graph::in, list(int)::out) is det.

dfs_graph(Graph, Dfs) :-
    Graph = graph(Size, _Array),
    mklist(Size, [], NodeList),
    Visit = dense_bitset.init,
    dfs_graph_2(NodeList, Graph, Visit, [], Dfs).

:- pred dfs_graph_2(list(int)::in, graph::in, visit::array_di,
    list(int)::in, list(int)::out) is det.

dfs_graph_2([], _Graph, _Visit, Dfs, Dfs).
dfs_graph_2([Node | Nodes], Graph, Visit0, Dfs0, Dfs) :-
    dfs([Node], Graph, Visit0, Dfs0, Visit, Dfs1),
    dfs_graph_2(Nodes, Graph, Visit, Dfs1, Dfs).

    % dfs(NodeList, Graph, Visit0, Dfs0, Visit, Dfs):
    % For every node in NodeList, add the node and all its successors to the
    % front of Dfs0, giving Dfs. The descendants of a node will in general be
    % after that node in Dfs. The only situation where that may not be the
    % case is when two nodes are descendants of each other. We detect such
    % situations by passing along the set of nodes that have been visited
    % already.
    %
:- pred dfs(list(int)::in, graph::in, visit::array_di, list(int)::in,
    visit::array_uo, list(int)::out) is det.

dfs([], _Graph, Visit, Dfs, Visit, Dfs).
dfs([Node | Nodes], Graph, Visit0, Dfs0, Visit, Dfs) :-
    ( if dense_bitset.member(Node, Visit0) then
        trace [compiletime(flag("dfs_old")), io(!IO)] (
            io.output_stream(OutputStream, !IO),
            io.format(OutputStream, "dfs old %d\n", [i(Node)], !IO)
        ),

        dfs(Nodes, Graph, Visit0, Dfs0, Visit, Dfs)
    else
        trace [compiletime(flag("dfs_new")), io(!IO)] (
            io.output_stream(OutputStream, !IO),
            io.format(OutputStream, "dfs new %d\n", [i(Node)], !IO)
        ),

        dense_bitset.insert(Node, Visit0, Visit1),
        successors(Graph, Node, Succ),
        set.to_sorted_list(Succ, SuccList),
        dfs(SuccList, Graph, Visit1, Dfs0, Visit2, Dfs1),
        Dfs2 = [Node | Dfs1],
        dfs(Nodes, Graph, Visit2, Dfs2, Visit, Dfs)
    ).

:- pred inverse(graph::in, graph::out) is det.

inverse(Graph, InvGraph) :-
    init(InvGraph0),
    Graph = graph(Size, _Array),
    inverse_2(Size, Graph, InvGraph0, InvGraph).

:- pred inverse_2(int::in, graph::in, graph::in, graph::out) is det.

inverse_2(To, Graph, InvGraph0, InvGraph) :-
    ( if To >= 0 then
        successors(Graph, To, Froms),
        set.to_sorted_list(Froms, FromList),
        add_arcs_to(FromList, To, InvGraph0, InvGraph1),
        inverse_2(To - 1, Graph, InvGraph1, InvGraph)
    else
        InvGraph = InvGraph0
    ).

:- pred add_arcs_to(list(int)::in, int::in, graph::in, graph::out) is det.

add_arcs_to([], _, Graph, Graph).
add_arcs_to([From | FromList], To, Graph0, Graph) :-
    add_arc(Graph0, From, To, Graph1),
    add_arcs_to(FromList, To, Graph1, Graph).

%---------------------------------------------------------------------------%

% Predicates to use in debugging.

:- pred write_graph(io.text_output_stream::in, graph::in,
    io::di, io::uo) is det.

write_graph(OutputStream, Graph, !IO) :-
    Graph = graph(Size, Array),
    io.format(OutputStream, "graph size: %d\n", [i(Size)], !IO),
    write_graph_nodes(OutputStream, 0, Size, Array, !IO).

:- pred write_graph_nodes(io.text_output_stream::in, int::in, int::in,
    array(set(int))::in, io::di, io::uo) is det.

write_graph_nodes(OutputStream, Cur, Max, Array, !IO) :-
    ( if Cur =< Max then
        array.lookup(Array, Cur, SuccSet),
        set.to_sorted_list(SuccSet, Succs),
        io.format(OutputStream, "%d -> ", [i(Cur)], !IO),
        io.write_line(OutputStream, Succs, !IO),
        write_graph_nodes(OutputStream, Cur + 1, Max, Array, !IO)
    else
        true
    ).

:- pred write_dfs(io.text_output_stream::in, list(int)::in,
    io::di, io::uo) is det.

write_dfs(OutputStream, Dfs, !IO) :-
    io.write_string(OutputStream, "dfs(\n", !IO),
    list.foldl(io.write_line(OutputStream), Dfs, !IO),
    io.write_string(OutputStream, ")\n", !IO).

:- pred write_cliques(io.text_output_stream::in, list(set(int))::in,
    io::di, io::uo) is det.

write_cliques(OutputStream, Cliques, !IO) :-
    list.foldl(write_clique_nl(OutputStream), Cliques, !IO).

:- pred write_clique(io.text_output_stream::in, set(int)::in,
    io::di, io::uo) is det.

write_clique(OutputStream, Nodes, !IO) :-
    list.map(string.int_to_string, set.to_sorted_list(Nodes), NodeStrs),
    NodesStr = string.join_list(", ", NodeStrs),
    io.format(OutputStream, "clique(%s)", [s(NodesStr)], !IO).

:- pred write_clique_nl(io.text_output_stream::in, set(int)::in,
    io::di, io::uo) is det.

write_clique_nl(OutputStream, Nodes, !IO) :-
    write_clique(OutputStream, Nodes, !IO),
    io.nl(OutputStream, !IO).

%---------------------------------------------------------------------------%
:- end_module cliques.
%---------------------------------------------------------------------------%
