/*
    This software may only be used by you under license from AT&T Corp.
    ("AT&T").  A copy of AT&T's Source Code Agreement is available at
    AT&T's Internet website having the URL:
    <http://www.research.att.com/sw/tools/graphviz/license/source.html>
    If you received this software without first entering into a license
    with AT&T, you have an infringing copy of this software and cannot use
    it without violating AT&T's intellectual property rights.
*/
#pragma prototyped

/* 
 * Network Simplex Algorithm for Ranking Nodes of a DAG
 */

#include "dot.h"


static int init_graph(graph_t *);

#define LENGTH(e)		(ND_rank(e->head) - ND_rank(e->tail))
#define SLACK(e)		(LENGTH(e) - ED_minlen(e))
#define SEQ(a,b,c)		(((a) <= (b)) && ((b) <= (c)))
#define TREE_EDGE(e)	(ED_tree_index(e) >= 0)

static graph_t	*G;
static int		N_nodes,N_edges;
static int		Minrank,Maxrank;
static int		S_i;		/* search index for enter_edge */
static int		Search_size;
#define SEARCHSIZE 30
static nlist_t	Tree_node;
static elist	Tree_edge;

void add_tree_edge(edge_t* e)
{
	node_t	*n;
	if (TREE_EDGE(e)) abort();
	ED_tree_index(e) = Tree_edge.size;
	Tree_edge.list[Tree_edge.size++] = e;
	if (ND_mark(e->tail) == FALSE) Tree_node.list[Tree_node.size++] = e->tail;
	if (ND_mark(e->head) == FALSE) Tree_node.list[Tree_node.size++] = e->head;
	n = e->tail;
	ND_mark(n) = TRUE;
	ND_tree_out(n).list[ND_tree_out(n).size++] = e;
	ND_tree_out(n).list[ND_tree_out(n).size] = NULL;
	if (ND_out(n).list[ND_tree_out(n).size-1] == 0) abort();
	n = e->head;
	ND_mark(n) = TRUE;
	ND_tree_in(n).list[ND_tree_in(n).size++] = e;
	ND_tree_in(n).list[ND_tree_in(n).size] = NULL;
	if (ND_in(n).list[ND_tree_in(n).size-1] == 0) abort();
}

void exchange_tree_edges(edge_t *e,edge_t *f)
{
	int		i,j;
	node_t	*n;

	ED_tree_index(f) = ED_tree_index(e);
	Tree_edge.list[ED_tree_index(e)] = f;
	ED_tree_index(e) = -1;

	n = e->tail;
	i = --(ND_tree_out(n).size);
	for (j = 0; j <= i; j++) if (ND_tree_out(n).list[j] == e) break;
	ND_tree_out(n).list[j] = ND_tree_out(n).list[i];
	ND_tree_out(n).list[i] = NULL;
	n = e->head;
	i = --(ND_tree_in(n).size);
	for (j = 0; j <= i; j++) if (ND_tree_in(n).list[j] == e) break;
	ND_tree_in(n).list[j] = ND_tree_in(n).list[i];
	ND_tree_in(n).list[i] = NULL;

	n = f->tail;
	ND_tree_out(n).list[ND_tree_out(n).size++] = f;
	ND_tree_out(n).list[ND_tree_out(n).size] = NULL;
	n = f->head;
	ND_tree_in(n).list[ND_tree_in(n).size++] = f;
	ND_tree_in(n).list[ND_tree_in(n).size] = NULL;
}

void init_rank(void)
{
	int			i,ctr;
	queue		*Q;
	node_t		*v;
	edge_t		*e;

	Q = new_queue(N_nodes);
	ctr = 0;

	for (v = GD_nlist(G); v; v = ND_next(v)) {
		if (ND_priority(v) == 0) enqueue(Q,v);
	}

	while ((v = dequeue(Q))) {
		ND_rank(v) = 0;
		ctr++;
		for (i = 0; (e = ND_in(v).list[i]); i++)
			ND_rank(v) = MAX(ND_rank(v),ND_rank(e->tail) + ED_minlen(e));
		for (i = 0; (e = ND_out(v).list[i]); i++) {
			if (--(ND_priority(e->head)) <= 0) enqueue(Q,e->head);
		}
	}
	if (ctr != N_nodes) {
		agerr(AGERR, "trouble in init_rank\n");
		for (v = GD_nlist(G); v; v = ND_next(v))
			if (ND_priority(v))
				agerr(AGPREV, "\t%s %d\n",v->name,ND_priority(v));
	}
	free_queue(Q);
}

node_t *
incident(edge_t* e)
{
	if (ND_mark(e->tail)) {
		if (ND_mark(e->head) == FALSE)
			return e->tail;
	}
	else {
		if (ND_mark(e->head))
			return e->head;
	}
	return NULL;
}

edge_t *
leave_edge (void)
{
	edge_t			*f,*rv = NULL;
	int				j,cnt = 0;

	j = S_i;
	while (S_i < Tree_edge.size) {
		if ((f = Tree_edge.list[S_i])->u.cutvalue < 0) {
			if (rv) {
				if (ED_cutvalue(rv) > ED_cutvalue(f)) rv = f;
			}
			else rv = Tree_edge.list[S_i];
			if (++cnt >= Search_size) return rv;
		}
		S_i++;
	}
	if (j > 0) {
		S_i = 0;
		while (S_i < j) {
			if ((f = Tree_edge.list[S_i])->u.cutvalue < 0) {
				if (rv) {
					if (ED_cutvalue(rv) > ED_cutvalue(f)) rv = f;
				}
				else rv = Tree_edge.list[S_i];
				if (++cnt >= Search_size) return rv;
			}
			S_i++;
		}
	}
	return rv;
}

static edge_t	*Enter;
static int		Low,Lim,Slack;

void dfs_enter_outedge(node_t* v)
{
	int		i,slack;
	edge_t	*e;

	for (i = 0; (e = ND_out(v).list[i]); i++) {
		if (TREE_EDGE(e) == FALSE) {
			if (!SEQ(Low,ND_lim(e->head),Lim)) {
				slack = SLACK(e);
				if ((slack < Slack) || (Enter == NULL)) {
					Enter = e;
					Slack = slack;
				}
			}
		}
		else if (ND_lim(e->head) < ND_lim(v)) dfs_enter_outedge(e->head);
	}
	for (i = 0; (e = ND_tree_in(v).list[i]) && (Slack > 0); i++)
		if (ND_lim(e->tail) < ND_lim(v)) dfs_enter_outedge(e->tail);
}

void dfs_enter_inedge(node_t* v)
{
	int		i,slack;
	edge_t	*e;

	for (i = 0; (e = ND_in(v).list[i]); i++) {
		if (TREE_EDGE(e) == FALSE) {
			if (!SEQ(Low,ND_lim(e->tail),Lim)) {
				slack = SLACK(e);
				if ((slack < Slack) || (Enter == NULL)) {
					Enter = e;
					Slack = slack;
				}
			}
		}
		else if (ND_lim(e->tail) < ND_lim(v)) dfs_enter_inedge(e->tail);
	}
	for (i = 0; (e = ND_tree_out(v).list[i]) && (Slack > 0); i++)
		if (ND_lim(e->head) < ND_lim(v)) dfs_enter_inedge(e->head);
}

edge_t *
enter_edge(edge_t* e)
{
	node_t	*v;
	int		outsearch;

	/* v is the down node */
	if (ND_lim(e->tail) < ND_lim(e->head)) {v = e->tail; outsearch = FALSE;}
	else {v = e->head;outsearch = TRUE;}
	Enter = NULL;
	Slack = MAXINT;
	Low = ND_low(v);
	Lim = ND_lim(v);
	if (outsearch) dfs_enter_outedge(v);
	else dfs_enter_inedge(v);
	return Enter;
}

int treesearch(node_t* v)
{
	int		i;
	edge_t	*e;

	for (i = 0; (e = ND_out(v).list[i]); i++) {
		if ((ND_mark(e->head) == FALSE) && (SLACK(e) == 0)) {
			add_tree_edge(e);
			if ((Tree_edge.size == N_nodes-1) || treesearch(e->head)) return TRUE;
		}
	}
	for (i = 0; (e = ND_in(v).list[i]); i++) {
		if ((ND_mark(e->tail) == FALSE) && (SLACK(e) == 0)) {
			add_tree_edge(e);
			if ((Tree_edge.size == N_nodes-1) || treesearch(e->tail)) return TRUE;
		}
	}
	return FALSE;
}

int
tight_tree(void)
{
	int		i;
	node_t	*n;

	for (n = GD_nlist(G); n; n = ND_next(n)) {
		ND_mark(n) = FALSE;
		ND_tree_in(n).list[0] = ND_tree_out(n).list[0] = NULL;
		ND_tree_in(n).size = ND_tree_out(n).size = 0;
	}
	for (i = 0; i < Tree_edge.size; i++) Tree_edge.list[i]->u.tree_index = -1;

	Tree_node.size = Tree_edge.size = 0;
	for (n = GD_nlist(G); n && (Tree_edge.size == 0); n = ND_next(n)) treesearch(n);
	return Tree_node.size;
}

void init_cutvalues(void)
{
	dfs_range(GD_nlist(G),NULL,1);
	dfs_cutval(GD_nlist(G),NULL);
}

void feasible_tree(void)
{
	int			i,delta;
	node_t		*n;
	edge_t		*e,*f;

	if (N_nodes <= 1) return;
	while (tight_tree() < N_nodes) {
		e = NULL;
		for (n = GD_nlist(G); n; n = ND_next(n)) {
			for (i = 0; (f = ND_out(n).list[i]); i++) {
				if ((TREE_EDGE(f) == FALSE) && incident(f) && ((e == NULL)
					|| (SLACK(f) < SLACK(e)))) e = f;
			}
		}
		if (e) {
			delta = SLACK(e);
			if (delta) {
				if (incident(e) == e->head) delta = -delta;
				for (i = 0; i < Tree_node.size; i++)
					Tree_node.list[i]->u.rank += delta;
			}
		}
		else {
#ifdef DEBUG
			fprintf(stderr,"not in tight tree:\n");
			for (n = GD_nlist(G); n; n = ND_next(n)) {
				for (i = 0; i < Tree_node.size; i++)
					if (Tree_node.list[i] == n) break;
				if (i >= Tree_node.size) fprintf(stderr,"\t%s\n",n->name);
			}
#endif
			abort();
		}
	}
	init_cutvalues();
}

/* walk up from v to LCA(v,w), setting new cutvalues. */
node_t *
treeupdate(node_t *v, node_t *w, int cutvalue, int dir)
{
	edge_t	*e;
	int		d;

	while (!SEQ(ND_low(v),ND_lim(w),ND_lim(v))) {
		e = ND_par(v);
		if (v == e->tail) d = dir; else d = NOT(dir);
		if (d) ED_cutvalue(e) += cutvalue; else ED_cutvalue(e) -= cutvalue;
		if (ND_lim(e->tail) > ND_lim(e->head)) v = e->tail; else v = e->head;
	}
	return v;
}

void rerank(node_t* v, int delta)
{
	int		i;
	edge_t	*e;

	ND_rank(v) -= delta;
	for (i = 0; (e = ND_tree_out(v).list[i]); i++) 
		if (e != ND_par(v)) rerank(e->head,delta);
	for (i = 0; (e = ND_tree_in(v).list[i]); i++) 
		if (e != ND_par(v)) rerank(e->tail,delta);
}

/* e is the tree edge that is leaving and f is the nontree edge that
 * is entering.  compute new cut values, ranks, and exchange e and f.
 */
void update(edge_t *e, edge_t *f)
{
	int		cutvalue,delta;
	node_t	*lca;

	delta = SLACK(f);
	/* "for (v = in nodes in tail side of e) do ND_rank(v) -= delta;" */
	if (delta > 0) {
		int s;
		s = ND_tree_in(e->tail).size + ND_tree_out(e->tail).size;
		if (s == 1) rerank(e->tail,delta);
		else {
			s = ND_tree_in(e->head).size + ND_tree_out(e->head).size;
			if (s == 1) rerank(e->head,-delta);
			else {
				if (ND_lim(e->tail) < ND_lim(e->head)) rerank(e->tail,delta);
				else rerank(e->head,-delta);
			}
		}
	}

	cutvalue = ED_cutvalue(e);
	lca = treeupdate(f->tail,f->head,cutvalue,1);
	if (treeupdate(f->head,f->tail,cutvalue,0) != lca) abort();
	ED_cutvalue(f) = -cutvalue;
	ED_cutvalue(e) = 0;
	exchange_tree_edges(e,f);
	dfs_range(lca,ND_par(lca),ND_low(lca));
}

void scan_and_normalize(void)
{
	node_t	*n;

	Minrank = MAXINT;
	Maxrank = -MAXINT;
	for (n = GD_nlist(G); n; n = ND_next(n)) {
		if (ND_node_type(n) == NORMAL) {
			Minrank = MIN(Minrank,ND_rank(n));
			Maxrank = MAX(Maxrank,ND_rank(n));
		}
	}
	if (Minrank != 0) {
		for (n = GD_nlist(G); n; n = ND_next(n)) ND_rank(n) -= Minrank;
		Maxrank -= Minrank;
		Minrank = 0;
	}
}

void LR_balance(void)
{
	int		i,delta;
	node_t	*n;
	edge_t	*e,*f;

	for (i = 0; i < Tree_edge.size; i++) {
		e = Tree_edge.list[i];
		if (ED_cutvalue(e) == 0) {
			f = enter_edge(e);
			if (f == NULL) continue;
			delta = SLACK(f);
			if (delta <= 1) continue;
			if (ND_lim(e->tail) < ND_lim(e->head)) rerank(e->tail,delta/2);
			else rerank(e->head,-delta/2);
		}
	}
	for (n = GD_nlist(G); n; n = ND_next(n)) {
		free_list(ND_tree_in(n));
		free_list(ND_tree_out(n));
		ND_mark(n) = FALSE;
	}
}

void TB_balance(void)
{
	node_t	*n;
	edge_t	*e;
	int		i,low,high,choice,*nrank;
	int		inweight,outweight;

	scan_and_normalize();

	/* find nodes that are not tight and move to less populated ranks */
	nrank = N_NEW(Maxrank+1,int);
	for (i = 0; i <= Maxrank; i++) nrank[i] = 0;
	for (n = GD_nlist(G); n; n = ND_next(n))
		if (ND_node_type(n) == NORMAL) nrank[ND_rank(n)]++;
	for (n = GD_nlist(G); n; n = ND_next(n)) {
		if (ND_node_type(n) != NORMAL) continue;
		inweight = outweight = 0;
		low = 0;
		high = Maxrank;
		for (i = 0; (e = ND_in(n).list[i]); i++) {
			inweight += ED_weight(e);
			low = MAX(low,ND_rank(e->tail) + ED_minlen(e));
		}
		for (i = 0; (e = ND_out(n).list[i]); i++) {
			outweight += ED_weight(e);
			high = MIN(high,ND_rank(e->head) - ED_minlen(e));
		}
		if (low < 0) low = 0;	/* vnodes can have ranks < 0 */
		if (inweight == outweight) {
			choice = low;
			for (i = low + 1; i <= high; i++)
				if (nrank[i] < nrank[choice]) choice = i;
			nrank[ND_rank(n)]--; nrank[choice]++;
			ND_rank(n) = choice;
		}
		free_list(ND_tree_in(n));
		free_list(ND_tree_out(n));
		ND_mark(n) = FALSE;
	}
	free(nrank);
}

static int
init_graph(graph_t* g)
{
	int		i,feasible;
	node_t	*n;
	edge_t	*e;

	G = g;
	N_nodes = N_edges = S_i = 0;
	for (n = GD_nlist(g); n; n = ND_next(n)) {
		ND_mark(n) = FALSE;
		N_nodes++;
		for (i = 0; (e = ND_out(n).list[i]); i++) N_edges++;
	}

	Tree_node.list = ALLOC(N_nodes,Tree_node.list,node_t*);
	Tree_node.size = 0;
	Tree_edge.list = ALLOC(N_nodes,Tree_edge.list,edge_t*);
	Tree_edge.size = 0;

	feasible = TRUE;
	for (n = GD_nlist(g); n; n = ND_next(n)) {
		ND_priority(n) = 0;
		for (i = 0; (e = ND_in(n).list[i]); i++) {
			ND_priority(n)++;
			ED_cutvalue(e) = 0;
			ED_tree_index(e) = -1;
			if (feasible && (ND_rank(e->head) - ND_rank(e->tail) < ED_minlen(e)))
				feasible = FALSE;
		}
		ND_tree_in(n).list = N_NEW(i+1,edge_t*);
		ND_tree_in(n).size = 0;
		for (i = 0; (e = ND_out(n).list[i]); i++);
		ND_tree_out(n).list = N_NEW(i+1,edge_t*);
		ND_tree_out(n).size = 0;
	}
	return feasible;
}

void rank(graph_t *g, int balance, int maxiter)
{
	int	iter = 0,feasible;
	char	*s,*ns = "network simplex: ";
	edge_t	*e,*f;

	if (Verbose) start_timer();
	feasible = init_graph(g);
	if (!feasible) init_rank();
	if (maxiter <= 0) return;

	if ((s = agget(g,"searchsize"))) Search_size = atoi(s);
	else Search_size = SEARCHSIZE;

	feasible_tree();
	while ((e = leave_edge())) {
		f = enter_edge(e);
		update(e,f);
		iter++;
		if (Verbose && (iter % 100 == 0)) {
			if (iter % 1000 == 100) fputs(ns,stderr);
			fprintf(stderr,"%d ",iter);
			if (iter % 1000 == 0) fputc('\n',stderr);
		}
		if (iter >= maxiter) break;
	}
	switch(balance){
	case 1: TB_balance(); break;
	case 2: LR_balance(); break;
	default: scan_and_normalize(); break;
	}
	if (Verbose) {
		if (iter >= 100) fputc('\n',stderr);
		fprintf(stderr,"%s%d nodes %d edges %d iter %.2f sec\n",
			ns,N_nodes,N_edges,iter,elapsed_sec());
	}
}

/* set cut value of f, assuming values of edges on one side were already set */
void x_cutval(edge_t* f)
{
	node_t	*v;
	edge_t	*e;
	int		i,sum,dir;

	/* set v to the node on the side of the edge already searched */
	if (ND_par(f->tail) == f) { v = f->tail; dir = 1; }
	else { v = f->head; dir = -1; }

	sum = 0;
	for (i = 0; (e = ND_out(v).list[i]); i++) sum += x_val(e,v,dir);
	for (i = 0; (e = ND_in(v).list[i]); i++) sum += x_val(e,v,dir);
	ED_cutvalue(f) = sum;
}

int x_val(edge_t* e, node_t* v, int dir)
{
	node_t	*other;
	int		d,rv,f;

	if (e->tail == v) other = e->head; else other = e->tail;
	if (!(SEQ(ND_low(v),ND_lim(other),ND_lim(v)))) {f = 1; rv = ED_weight(e);}
	else {
		f = 0;
		if (TREE_EDGE(e)) rv = ED_cutvalue(e);
		else rv = 0;
		rv -= ED_weight(e);
	}
	if (dir > 0) {if (e->head == v) d = 1; else d = -1;}
	else {if (e->tail == v) d = 1; else d = -1; }
	if (f) d = -d;
	if (d < 0) rv = -rv;
	return rv;
}

void dfs_cutval(node_t* v, edge_t* par)
{
	int		i;
	edge_t	*e;

	for (i = 0; (e = ND_tree_out(v).list[i]); i++)
		if (e != par) dfs_cutval(e->head,e);
	for (i = 0; (e = ND_tree_in(v).list[i]); i++)
		if (e != par) dfs_cutval(e->tail,e);
	if (par) x_cutval(par);
}

int dfs_range(node_t* v, edge_t* par, int low)
{
	edge_t	*e;
	int		i,lim;

	lim = low;
	ND_par(v) = par;
	ND_low(v) = low;
	for (i = 0; (e = ND_tree_out(v).list[i]); i++)
		if (e != par) lim = dfs_range(e->head,e,lim);
	for (i = 0; (e = ND_tree_in(v).list[i]); i++)
		if (e != par) lim = dfs_range(e->tail,e,lim);
	ND_lim(v) = lim;
	return lim + 1;
}

#ifdef DEBUG
void tchk(void)
{
	int		i,n_cnt,e_cnt;
	node_t	*n;
	edge_t	*e;

	n_cnt = 0;
	e_cnt = 0;
	for (n = GD_nlist(G); n; n = ND_next(n)) {
		n_cnt++;
		for (i = 0; e = ND_tree_out(n).list[i]; i++) {
			e_cnt++;
			if (SLACK(e) > 0)
				printf("not a tight tree %x",e);
		}
	}
	if ((n_cnt != Tree_node.size)  || (e_cnt != Tree_edge.size))
		printf("something missing\n");
}

void check_cutvalues(void)
{
	node_t	*v;
	edge_t	*e;
	int		i,save;

	for (v = GD_nlist(G); v; v = ND_next(v)) {
		for (i = 0; (e = ND_tree_out(v).list[i]); i++) {
			save = ED_cutvalue(e);
			x_cutval(e);
			if (save != ED_cutvalue(e)) abort();
		}
	}
}

int
check_ranks(void)
{
	int		i, cost = 0;
	node_t	*n;
	edge_t	*e;

	for (n = GD_nlist(G); n; n = ND_next(n)) {
		for (i = 0; (e = ND_out(n).list[i]); i++) {
			cost += (ED_weight(e))*abs(LENGTH(e));
	if (ND_rank(e->head) - ND_rank(e->tail) - ED_minlen(e) < 0) abort();
		}
	}
	fprintf(stderr,"rank cost %d\n",cost);
	return cost;
}

void checktree(void)
{
	int		i,n = 0,m = 0;
	node_t	*v;
	edge_t	*e;

	for (v = GD_nlist(G); v; v = ND_next(v)) {
		for (i = 0; (e = ND_tree_out(v).list[i]); i++) n++;
		if (i != ND_tree_out(v).size) abort();
		for (i = 0; (e = ND_tree_in(v).list[i]); i++) m++;
		if (i != ND_tree_in(v).size) abort();
	}
	printf("%d %d %d\n",Tree_edge.size,n,m);
}

void check_fast_node(node_t* n)
{
	node_t	*nptr;
	nptr = GD_nlist(n->graph);
	while (nptr && nptr != n) nptr = ND_next(nptr);
	assert (nptr != NULL);
}

void checkdfs(node_t* n)
{
	int		i;
	edge_t	*e;
	node_t	*w;

	if (ND_mark(n)) return;
	ND_mark(n) = TRUE;
	ND_onstack(n) = TRUE;
	for (i = 0; (e = ND_out(n).list[i]); i++) {
		w = e->head;
		if (ND_onstack(w))
			fprintf(stderr,"cycle involving %s %s\n",n->name,w->name);
		else {
			if (ND_mark(w) == FALSE) checkdfs(w);
		}
	}
	ND_onstack(n) = FALSE;
}

void check_cycles(graph_t* g)
{
	node_t	*n;
	for (n = GD_nlist(g); n; n = ND_next(n)) ND_mark(n) = ND_onstack(n) = FALSE;
	for (n = GD_nlist(g); n; n = ND_next(n)) checkdfs(n);
}
#endif /* DEBUG */
