/* lp_graph.c
 * Neil Moore <neil@cs.uky.edu>
 * 29 Apr 1999
 *
 * Part of liblp.  See COPYRIGHT for copyright information.
 */
#include <stdlib.h>    /* For NULL */
#include <stddef.h>    /* For ptrdiff_t */
#include <lp.h>
#include <lp_lists.h>
#include <lp_util.h>
#include <lp_graph.h>

/* We should eventually make a single function for stratification, and make
 * the rest static (and define lp_vtx, lp_graph, etc. in this file rather
 * than the header).
 */

/* Graph colours */
#define C_WHITE 0
#define C_GREY  1
#define C_BLACK 2

static struct lp_graph *construct_transpose(struct lp_graph *orig);

static void unvisit_vertex(struct lp_vtx *v);
static void init_vertex(struct lp_vtx *v, void *data, int t,
			struct lp_vlist *edges);
static int dfsearch(struct lp_vtx **list, int num, struct lp_vlist **);
static int dfs_visit(struct lp_vtx *v, int time, struct lp_vlist **);
static struct lp_vlist **order_comps(struct lp_graph *g, struct lp_vlist **comps,
				       int *componentof, int ncomps);
static struct lp_vtx **topo_sort(struct lp_graph *graph);
static struct lp_vtx *rootof(struct lp_vtx *v);
static int v_pos(struct lp_graph *g, struct lp_vtx *v);


/* Given a graph g and a vertex v, return the position of v within p.  For
 * example, v_pos(&g, &g.vertices[3]) should return 3.  If the vertex is not
 * within bounds, v_pos aborts the program (if LP_DEBUG is defined) or fails
 * silently (if LP_DEBUG is not defined).
 */
int v_pos(struct lp_graph *g, struct lp_vtx *v) 
{
	int pos;
	lp_assert(g != NULL);
	lp_assert(v != NULL);
	
	pos = v - g->vertices;
	
	lp_assert(pos >= 0 && pos < g->nvertices);
	
	return pos;
}


/* Mark a vertex as unvisited by DFS.  All DFS-generated data are set to
 * their defaults values.
 */
void unvisit_vertex(struct lp_vtx *v) 
{
	lp_assert(v != NULL);
	
	v->colour = C_WHITE;
	v->pred = NULL;
	v->discover_time = -1;
	v->finish_time = -1;
}


/* Initialise a vertex.  The data pointer, data type, and edge list are set
 * to the specified values.  The vertex is also marked as unvisited.
 *
 */
void init_vertex(struct lp_vtx *v, void *data, int type, struct lp_vlist *edges) 
{
	lp_assert(v != NULL);
	lp_assert(type == LP_DT_OTHER || type == LP_DT_RULE || type == LP_DT_ATOM);
		
	v->data = data;
	v->type = type;
	v->edges = edges;
	unvisit_vertex(v);
}


/* Build a dependency digraph (with rules and atoms as vertices) from a
 * logic program.  This function constructs a graph (allocating memory with
 * lp_malloc()) based on prog (the current logic program).  Each vertex
 * represents a rule or an atom; there is an edge from vertex A to vertex B
 * if:
 *    A is an atom, B is a rule, and A appears in B's body.
 *    A is a rule, B is an atom, and B appears in A's head.
 *
 * lp_construct_depend_graph() returns a pointer to the digraph.
 *
 * This function is O(N), where N is the size of the program.
 *
 */
struct lp_graph *lp_construct_depend_graph(struct lp *prog) 
{
	int i, j, nrules=0;
	struct lp_vtx *v;
	struct lp_graph *graph;
	struct lp_vlist *depends;

	lp_assert(prog != NULL);

	nrules = prog->nrules;
	
	graph = lp_malloc(sizeof(struct lp_graph));
	graph->nvertices = nrules + prog->natoms;
	graph->vertices = lp_malloc(sizeof(struct lp_vtx) * graph->nvertices);

	/* Rules come first */
	for(i=0; i < prog->natoms; i++)
		init_vertex(&graph->vertices[i + nrules], &prog->atoms[i], LP_DT_ATOM, NULL);
	
	
	for(i=0; i<prog->nrules; i++) {
		depends = NULL;
		lp_vl_add(&depends, &graph->vertices[lp_apos(prog, prog->rules[i].head) + nrules]);

		/* diff */
		for(j=0; j<prog->rules[i].nneg; j++) {
			v = &graph->vertices[lp_apos(prog, prog->rules[i].neg[j]) + nrules];
			lp_vl_add(&v->edges, &graph->vertices[i]);
		}
		for(j=0; j<prog->rules[i].npos; j++) {
			v = &graph->vertices[lp_apos(prog, prog->rules[i].pos[j]) + nrules];
			lp_vl_add(&v->edges, &graph->vertices[i]);
		}

		init_vertex(&graph->vertices[i], &prog->rules[i], LP_DT_RULE, depends);
	}

	return graph;
}


/* Build a dependency digraph (with rules and atoms as vertices) from a
 * logic program.  This function constructs a graph (allocating memory with
 * lp_malloc()) based on prog (the current logic program).  Each vertex
 * represents a rule or an atom; there is an edge from vertex A to vertex B
 * if:
 *    A is an atom, B is a rule, and A appears in B's body.
 *    A is a rule, B is an atom, and B appears in A's head.
 *    A is an atom, B is a rule, and A appears in B's head.
 *
 * The last case ensures that all rules with the same head appear in the
 * same stratum.
 *
 * lp_construct_strat_graph() returns a pointer to the digraph.
 *
 * This function is O(N), where N is the size of the program.
 *
 */
struct lp_graph *lp_construct_strat_graph(struct lp *prog) 
{
	int i, j, nrules=0;
	struct lp_vtx *v;
	struct lp_graph *graph;
	struct lp_vlist *depends;

	lp_assert(prog != NULL);

	nrules = prog->nrules;
	
	graph = lp_malloc(sizeof(struct lp_graph));
	graph->nvertices = nrules + prog->natoms;
	graph->vertices = lp_malloc(sizeof(struct lp_vtx) * graph->nvertices);
	
	/* Rules come first */
	for(i=0; i < prog->natoms; i++)
		init_vertex(&graph->vertices[i + nrules], &prog->atoms[i], LP_DT_ATOM, NULL);
	
	for(i=0; i<prog->nrules; i++) {
		depends = NULL;
		lp_vl_add(&depends, &graph->vertices[lp_apos(prog, prog->rules[i].head) + nrules]);

		/* diff */
		for(j=0; j<prog->rules[i].nneg; j++) {
			v = &graph->vertices[lp_apos(prog, prog->rules[i].neg[j]) + nrules];
			lp_vl_add(&v->edges, &graph->vertices[i]);
		}
		for(j=0; j<prog->rules[i].npos; j++) {
			v = &graph->vertices[lp_apos(prog, prog->rules[i].pos[j]) + nrules];
			lp_vl_add(&v->edges, &graph->vertices[i]);
		}

		/* 1 Feb 1999 */
		v = &graph->vertices[lp_apos(prog, prog->rules[i].head) + nrules];
		lp_vl_add(&v->edges, &graph->vertices[i]);
		/* end */

		init_vertex(&graph->vertices[i], &prog->rules[i], LP_DT_RULE, depends);
	}

	return graph;
}


/* Construct the transpose of a digraph.  The transpose of a digraph X is
 * the digraph Y such that (a,b) exists in edges(Y) iff (b,a) exists in
 * edges(X).  Memory for the transpose is allocated with lp_malloc().  This
 * function returns a pointer to the transpose.
 *
 * This function is O(N) on the size of the graph.
 *
 */
struct lp_graph *construct_transpose(struct lp_graph *orig) 
{
	int i;
	struct lp_vtx *v;
	struct lp_vlist *j;
	struct lp_graph *result;
	ptrdiff_t diff; /* save many calls to v_pos() */
	
	result = lp_malloc(sizeof(struct lp_graph));
	result->nvertices = orig->nvertices;
	result->vertices = lp_malloc(sizeof(struct lp_vtx) * result->nvertices);
	diff = result->vertices - orig->vertices;

	/* Clear each vertex.  This cannot be combined with the loop below,
	 * because all edge lists must be NULL before that loop can be
	 * executed.
	 */
	for(i=0; i<result->nvertices; i++) {
		v = &orig->vertices[i];
		init_vertex(&result->vertices[i], v->data, v->type, NULL);
	}

	/* Create edges.  If an edge from vertex i to vertex j exists,
	 * vertex i is added to the front of j's edge list.
	 */
	for(i=0; i<result->nvertices; i++) {
		for(j=orig->vertices[i].edges; j!=NULL; j=j->next) {
			v = j->vertex + diff;
			lp_vl_add(&v->edges, &result->vertices[i]);
		}
	}

	return result;
}

/* Performs a depth-first search of the vertices listed in list[].  Each
 * vertex has its pred element set to be a pointer its predecessor (the
 * vertex above it in the depth-first forest, or NULL if the vertex is the
 * root of a tree in the depth-first forest).  Also, each vertex's
 * discover_time and finish_time are set appropriately.  Finally, vertices
 * are added to the beginning of *sorted as they are finished (if sorted
 * is non-NULL)
 *
 * This function takes an array of pointers to vertices rather than a graph
 * structure.  This is necessary because our algorithm for finding strongly-
 * connected components requires that vertices be considered in a particular
 * order.
 *
 * This function implements the DFS algorithm described in [CLR 478].
 *
 * This function is O(N) on the size of the graph.
 */
int dfsearch(struct lp_vtx **list, int num, struct lp_vlist **sorted)
{
	int i, time=0;
	int ntrees = 0;

	for(i=0; i<num; i++)
		unvisit_vertex(list[i]);

	for(i=0; i<num; i++) {
		if(list[i]->colour == C_WHITE) {
			time = dfs_visit(list[i], time, sorted);
			ntrees++;
		}
	}

	return ntrees;
}


/* Perform a recursive search on v, setting the pred, colour, discover_time,
 * and finish_time fields of all reachable vertices.  When this function has
 * finished, it inserts v at the beginning of *sorted (if sorted is not
 * NULL).
 *
 * This function implements the DFS-Visit algorithm described in [CLR 478].
 * It is intended to be called only from dfsearch().
 */
int dfs_visit(struct lp_vtx *v, int time, struct lp_vlist **sorted) 
{
	struct lp_vlist *j;
	
	v->colour = C_GREY;
	v->discover_time = time++;

	for(j=v->edges; j!=NULL; j=j->next) {
		if(j->vertex->colour == C_WHITE) {
			j->vertex->pred = v;
			time = dfs_visit(j->vertex, time, sorted);
		}
	}

	v->colour = C_BLACK;
	v->finish_time = time++;
	if(sorted != NULL)
		lp_vl_add(sorted, v);
	
	return time;
}


/* Find the strongly connected components of a graph.  This function sets
 * comps to point to an array of linked lists of vertices from the graph.
 * Each element of the array represents a strongly connected component of
 * the graph; the vertices in that list are the vertices contained within
 * that component.  The array is in topologically sorted order (i.e., if
 * x < y, there are no edges from any of the vertices in the yth component
 * to any of the vertices in the xth component).  The recurse parameter
 * indicates whether to use a recursive solution method.
 *
 * A strongly connected component S of a digraph G is a maximal set of
 * vertices from G such that, for every u and v in S, there is a path in G
 * from u to v and from v to u.  Since our graph represents dependencies
 * in a logic program, each component represents a stratum of rules from
 * that program; the topologically-sorted set of all such components
 * corresponds to a stratification.
 *
 * This function implements the Strongly-Connected-Components algorithm
 * described in [CLR 489], with some modifications.
 *
 * Note that rootof() will, across all calls, iterate up to O(V^2) times
 * (where V is the number of vertices).  However, the worst case is not
 * common, and rootof() consists of such simple operations that it would
 * require a very large graph for it to dominate.  Ignoring rootof(), this
 * function is O(N) on the size of the graph.
 */
int lp_find_scc(struct lp_graph *graph, struct lp_vlist ***comps)
{
	struct lp_graph *transpose;
	struct lp_vtx **sorted;
	struct lp_vlist **components;
	int i, num, ncomponents=0, k=0;
	int *componentof;

	/* Although this does not compute a true topological sorting (because
	 * the graph may be cyclic), our SCC algorithm uses a process which
	 * corresponds to topological sorting.
	 */
	sorted = topo_sort(graph);
	transpose = construct_transpose(graph);

	/* Convert sorted from an array of vertices of graph to an array of vertices
	 * of transpose.
	 */
	for(i=0; i<graph->nvertices; i++) {
		sorted[i] = transpose->vertices + v_pos(graph, sorted[i]);
	}
	
	ncomponents = dfsearch(sorted, graph->nvertices, NULL);
	free(sorted);

	componentof = lp_malloc(sizeof(int) * graph->nvertices);
	components = lp_malloc(sizeof(struct lp_vlist *) * ncomponents);
	
	for(i=0; i<ncomponents; i++)
		components[i] = NULL;

	/* Build the array of components.  For each vertex, we find the root
	 * of its tree in the depth-first forest generated by the transpose,
	 * and place it into that vertex's component (component membership is
	 * determined by the componentof array).
	 */
	for(i=0; i<transpose->nvertices; i++) {
		if(transpose->vertices[i].pred == NULL) {
			lp_vl_add(&components[k], &graph->vertices[i]);
			componentof[i] = k++;
		}
	}
	
	for(i=0; i<transpose->nvertices; i++) {
		if(transpose->vertices[i].pred == NULL)
			continue;
		
		num = componentof[v_pos(transpose, rootof(&transpose->vertices[i]))];
		lp_vl_add(&components[num], &graph->vertices[i]);
		componentof[i] = num;
	}

	*comps = order_comps(graph, components, componentof, ncomponents);
	
	free(componentof);
	free(components); /* but not the vertexlists within: 27 Feb */
	lp_free_graph(transpose);

	return ncomponents;
}


/* Topologically order the components in comps.  This function uses a stack
 * to keep track of the visited components.
 *
 */
struct lp_vlist **order_comps(struct lp_graph *g, struct lp_vlist **comps, int *componentof, int ncomps) 
{
	int compos, currpos, ordpos=0, i;
	struct lp_vlist **ord, *j, *k;
	int *stack;
	int sp=0;
	int *indegree;

	stack = lp_malloc(sizeof(int) * ncomps);
	ord = lp_malloc(sizeof(struct lp_vlist *) * ncomps);
	indegree = lp_calloc(ncomps, sizeof(int));
	
	for(i=0; i<g->nvertices; i++)
		for(j=g->vertices[i].edges; j!=NULL; j=j->next)
			if(componentof[i] != componentof[v_pos(g, j->vertex)])
				++indegree[componentof[v_pos(g, j->vertex)]];

	for(compos=0; compos<ncomps; compos++)
		if(indegree[compos] == 0)
			/* push */
			stack[sp++] = compos;

	while(sp != 0) {
		/* pop */
		compos = stack[--sp];
		indegree[compos] = -1;

		ord[ordpos++] = comps[compos];

		currpos = compos;
		for(j=comps[compos]; j!=NULL; j=j->next) {
			for(k=j->vertex->edges; k!=NULL; k=k->next) {
				compos = componentof[v_pos(g, k->vertex)];
				if(currpos != compos)
					if(--indegree[compos] == 0)
						stack[sp++] = compos;
			}
		}
	}
	
	free(indegree);
	free(stack);
	return ord;
}


/* Free all memory associated with a graph.  This includes edge lists, the
 * array of vertices, and the graph itself.
 *
 */
void lp_free_graph(struct lp_graph *g) 
{
	int i;

	lp_assert(g != NULL);
	
	for(i=0; i<g->nvertices; i++)
		lp_vl_free(g->vertices[i].edges);

	if(g->vertices != NULL)
		free(g->vertices);
	free(g);
}


/* Find the root of the DFS tree containing v.  dfsearch() should already
 * have been called on the graph to which v belongs.
 *
 */
struct lp_vtx *rootof(struct lp_vtx *v) 
{
	lp_assert(v != NULL);
	
	while(v->pred) {
		v = v->pred;
	}
	return v;
}

/* Topologically sort a graph.  This function returns an array of pointers
 * to vertices in graph, sorted in descending order of DFS finishing time.
 * Not only does this generate a topological sorting of a DAG (we do not
 * use it for this purpose), but it is also the first step in our algorithm
 * for finding strongly-connected components of a digraph.
 *
 */
struct lp_vtx **topo_sort(struct lp_graph *graph)
{
	int i;
	struct lp_vtx **result;
	struct lp_vlist *j;
	struct lp_vlist *sortedlist = NULL;

	lp_assert(graph != NULL);
	
	result = lp_malloc(sizeof(struct lp_vtx *) * graph->nvertices);

	for(i=0; i<graph->nvertices; i++)
		result[i] = &graph->vertices[i];

	dfsearch(result, graph->nvertices, &sortedlist);
	
	for(j=sortedlist, i=0; j != NULL; j=j->next, i++)
		result[i] = j->vertex;
	
	lp_vl_free(sortedlist);
	
	return result;
}

