/*
    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

#include "libgraph.h"

#ifdef DMALLOC
#include "dmalloc.h"
#endif

Dtdisc_t agNamedisc = {
	offsetof(Agnode_t,name),
	-1,
	-1,							/* link offset */
	NIL(Dtmake_f),
	NIL(Dtfree_f),
	NIL(Dtcompar_f),			/* use strcmp */
	NIL(Dthash_f),
	NIL(Dtmemory_f),
	NIL(Dtevent_f)
};

Dtdisc_t agNodedisc = {
	offsetof(Agnode_t,id),
	sizeof(int),				
	-1,							/* link offset */
	NIL(Dtmake_f),
	NIL(Dtfree_f),
	(Dtcompar_f)agcmpid,
	NIL(Dthash_f),
	NIL(Dtmemory_f),
	NIL(Dtevent_f)
};

Dtdisc_t agIndisc = {
	0,							/* pass whole object as key */
	0,
	-1,							/* link offset */
	NIL(Dtmake_f),
	NIL(Dtfree_f),
	(Dtcompar_f)agcmpin,
	NIL(Dthash_f),
	NIL(Dtmemory_f),
	NIL(Dtevent_f)
};

Dtdisc_t agOutdisc = {
	0,							/* pass whole object as key */
	0,
	-1,							/* link offset */
	(Dtmake_f)0,
	(Dtfree_f)0,
	(Dtcompar_f)agcmpout,
	(Dthash_f)0,
	(Dtmemory_f)0,
	(Dtevent_f)0
};

int	agcmpid(Dt_t *dict,	int	*id0,	int	*id1,	Dtdisc_t *disc)
{
	return (*id0) - *(id1);
}

#ifdef DEBUG
static int
myinedgecmp(e0, e1)
Agedge_t		*e0,*e1;
{
	int rv = myinedgecmp(e0,e1);
	printf("compare (%s,%s:%s),(%s,%s:%s) = %d\n",
		e0->head?e0->head->name:"nil",
		e0->tail?e0->tail->name:"nil",
		e0->key? e0->key : "nil",
		e1->head?e1->head->name:"nil",
		e1->tail?e1->tail->name:"nil",
		e1->key? e1->key : "nil",
		rv);
	return rv;
}
#endif

static int keycmp(Agedge_t* e0, Agedge_t* e1)
{
	char		*key0,*key1;
	key0 = e0->attr? e0->attr[KEYX] : NULL;
	key1 = e1->attr? e1->attr[KEYX] : NULL;
	if (key0 == NULL) return (key1? -1 : 0);
	if (key1 == NULL) return 1;
	return strcmp(key0,key1);
}

int agcmpin(Dict_t* d, Agedge_t* e0, Agedge_t* e1, Dtdisc_t* disc)
{
	int e0tailid, e0headid, e1tailid, e1headid;

	e0tailid = e0->tail? e0->tail->id : -1;
	e0headid = e0->head? e0->head->id : -1;
	e1tailid = e1->tail? e1->tail->id : -1;
	e1headid = e1->head? e1->head->id : -1;

	if (e0headid != e1headid) return e0headid - e1headid;
	if (e0tailid != e1tailid) return e0tailid - e1tailid;
	return keycmp(e0,e1);
}

int agcmpout(Dict_t* d, Agedge_t* e0, Agedge_t* e1, Dtdisc_t* disc)
{
	int e0tailid, e0headid, e1tailid, e1headid;

	e0tailid = e0->tail? e0->tail->id : -1;
	e0headid = e0->head? e0->head->id : -1;
	e1tailid = e1->tail? e1->tail->id : -1;
	e1headid = e1->head? e1->head->id : -1;

	if (e0tailid != e1tailid) return e0tailid - e1tailid;
	if (e0headid != e1headid) return e0headid - e1headid;
	return keycmp(e0,e1);
}

static Agdata_t *agnewdata(void)
{
	Agdata_t		*rv;

	rv = NEW(Agdata_t);
	rv->node_dict	= dtopen(&agNamedisc,Dttree);
	rv->globattr	= agNEWdict("graph");
	rv->nodeattr	= agNEWdict("node");
	rv->edgeattr	= agNEWdict("edge");
	if (AG.proto_g) {
		agcopydict(rv->globattr,AG.proto_g->univ->globattr);
		agcopydict(rv->nodeattr,AG.proto_g->univ->nodeattr);
		agcopydict(rv->edgeattr,AG.proto_g->univ->edgeattr);
	}
	return rv;
}

static void agfreedata(Agraph_t* g)
{
	agFREEdict(g,g->univ->globattr);
	agFREEdict(g,g->univ->nodeattr);
	agFREEdict(g,g->univ->edgeattr);
	dtclose(g->univ->node_dict);
	free(g->univ);
}

static void dup_proto(Agraph_t* g, Agproto_t* proto)
{
	Agnode_t	*n = NULL;
	Agedge_t	*e = NULL;
	Agproto_t	*s = NEW(Agproto_t);

	s->prev = g->proto;
	if (proto) {n = proto->n; e = proto->e;}
	s->n = agNEWnode(g,"\001proto",n);
	s->e = agNEWedge(g,s->n,s->n,e);
	g->proto = s;
}

void agpushproto(Agraph_t* g)
{
	dup_proto(g,g->proto);
}

void agpopproto(Agraph_t* g)
{
	Agproto_t		*s = g->proto;
	if (s != NULL) {
		g->proto = s->prev;
		s->e->tail = s->e->head = s->n;
		agFREEedge(s->e);
		agFREEnode(s->n);
		free(s);
	}
}

static Agraph_t *agNEWgraph(char* name, Agraph_t* parent, int kind)
{
	int				i, nobj;
	Agraph_t		*g;

	if (AG.init_called == FALSE) {
		agerr (AGERR, "libag error -- aginit() was not called\n");
		return 0;
	}
	g			= (Agraph_t*) calloc(1,AG.graph_nbytes);
	g->tag		= TAG_GRAPH;
	g->kind		= kind;
	g->nodes	= dtopen(&agNodedisc,Dttree);
	g->inedges	= dtopen(&agIndisc,Dttree);
	g->outedges	= dtopen(&agOutdisc,Dttree);

	if (parent == NULL) {
		g->univ		= agnewdata();
		g->root		= g;
		nobj = dtsize(g->univ->globattr->dict);
		if (nobj)
		    g->attr	= N_NEW(nobj,char*);
		else
		    g->attr	= NULL;
		for (i = 0; i < nobj; i++)
			g->attr[i] = agstrdup(AG.proto_g->attr[i]);
	}
	else {
		g->univ		= parent->univ;
		g->root 	= parent->root;
		nobj = dtsize(parent->univ->globattr->dict);
		if (nobj)
			g->attr = N_NEW(nobj,char*);
		else
			g->attr	= NULL;
		for (i = 0; i < nobj; i++)
			g->attr[i] = agstrdup(parent->attr[i]);
	}

	g->meta_node 	= NULL;
	g->name			= agstrdup(name);
	g->proto		= NULL;

	if (parent) dup_proto(g,parent->proto);
	else agpushproto(g);
	return g;
}

static int reach0(Dict_t* m, Agnode_t* from, Agnode_t* to)
{
	Agedge_t		*e;

	if (from == to) return TRUE;
	if (agfindedge(from->graph->root,from,to)) return TRUE;
	dtinsert(m,from);
	for (e = agfstout(from->graph,from); e; e = agnxtout(from->graph,e))
		if ((dtsearch(m,e->head) == NULL) && reach0(m,e->head,to))
			return TRUE;
	return FALSE;
}

static int reach(Agnode_t* from, Agnode_t* to)
{
	Dict_t		*m;
	int			rv;

	m = dtopen(&agNodedisc,Dttree);
	rv = reach0(m,from,to);
	dtclose(m);
	return rv;
}

Agraph_t *agusergraph(Agnode_t* n)
{
	return (n->graph->meta_node ? NULL : (Agraph_t*)(n->attr[0]));
}

Agraph_t *agopen(char* name, int kind)
{
	Agraph_t *g,*meta;

	g = agNEWgraph(name,NULL,kind);
	meta = agNEWgraph(name,NULL,AGMETAGRAPH);
	if (!g || !meta) return 0;
	agnodeattr(meta, "agusergraph", NULL);
	g->meta_node = agnode(meta,name);
	g->meta_node->attr[0] = (char*) g;
	return g;
}

Agraph_t *agsubg(Agraph_t* g, char* name)
{
	Agraph_t	*subg,*meta;
	Agnode_t	*n;

	meta = g->meta_node->graph;
	n = agfindnode(meta,name);
	if (n) subg = agusergraph(n);
	else {
		subg = agNEWgraph(name,g,g->kind);
		if (!subg) return 0;
		n = agnode(meta,name);
		subg->meta_node = n;
		n->attr[0] = (char*) subg;
	}
	agINSgraph(g,subg);
	return subg;
}

Agraph_t *agfindsubg(Agraph_t* g, char* name)
{
	Agnode_t	*n;

	if (g->meta_node) {
		n = agfindnode(g->meta_node->graph,name);
		if (n) return agusergraph(n);
	}
	return NULL;
}

void agINSgraph(Agraph_t* g, Agraph_t* subg)
{
	Agnode_t	*h,*t;
	t = g->meta_node;
	h = subg->meta_node;
	if (t && h && (reach(h,t) == FALSE))
		agedge(t->graph,t,h);
}

void agclose(Agraph_t* g)
{
	Agedge_t	*e,*f;
	Agnode_t	*n,*nn;
	Agraph_t	*meta = NULL;
	int			i,nobj,flag,is_meta;

	if ((g == NULL) || (TAG_OF(g) != TAG_GRAPH)) return;
	is_meta = AG_IS_METAGRAPH(g);
	if (is_meta == FALSE) {
		meta = g->meta_node->graph;
		/* recursively remove its subgraphs */
		do {	/* better semantics would be to find strong component */
			flag = FALSE;
			for (e = agfstout(meta,g->meta_node); e; e = f) {
				f = agnxtout(meta,e);
				if (agnxtin(meta,agfstin(meta,e->head)) == NULL) {
					agclose(agusergraph(e->head));
					flag = TRUE;
				}
			}
		} while (flag);
	}
	while (g->proto) agpopproto(g);
	if (is_meta == FALSE) {
		nobj = dtsize(g->univ->globattr->dict);
		for (i = 0; i < nobj; i++)
			agstrfree(g->attr[i]);
	}
	if (g->attr)
		free(g->attr);
	if (g == g->root) {
		for (n = agfstnode(g); n; n = nn) {
			nn = agnxtnode(g,n);
			agDELnode(g,n);
		}
		if (is_meta == FALSE) agclose(g->meta_node->graph);
		agfreedata(g);
	}
	else {
		if (is_meta == FALSE) agdelete(meta,g->meta_node);
	}
	dtclose(g->nodes);
	dtclose(g->inedges);
	dtclose(g->outedges);
	agstrfree(g->name);
	TAG_OF(g) = -1;
	free(g);
}

int agcontains(Agraph_t* g, void* obj)
{
	switch(TAG_OF(obj)) {
		case TAG_NODE: return(agidnode(g,((Agnode_t*)obj)->id) != NULL);
		case TAG_EDGE: return(dtsearch(g->inedges,(Agedge_t*)obj) != NULL);
		case TAG_GRAPH: return(reach(g->meta_node,((Agraph_t*)obj)->meta_node));
	}
	return FALSE;
}

void aginsert(Agraph_t* g, void* obj)
{
	switch (TAG_OF(obj)) {
		case TAG_NODE: 	agINSnode(g,obj); break;
		case TAG_EDGE:	agINSedge(g,obj); break;
		case TAG_GRAPH:	agINSgraph(g,obj); break;
	}
}

void agdelete(Agraph_t* g, void* obj)
{
	switch (TAG_OF(obj)) {
		case TAG_NODE: 	agDELnode(g,obj); break;
		case TAG_EDGE:	agDELedge(g,obj); break;
		case TAG_GRAPH:	agclose(obj); break;
	}
}

int agnnodes(Agraph_t* g)
{ return dtsize(g->nodes); }
int agnedges(Agraph_t* g)
{ return dtsize(g->outedges); }
