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

#define EXTERN
#include "libgraph.h"

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

Agdict_t *agdictof(void* obj)
{
	Agdict_t		*d = NULL;

	switch (TAG_OF(obj)) {
		case TAG_GRAPH:	d = ((Agraph_t*)obj)->univ->globattr; break;
		case TAG_NODE:	d = ((Agnode_t*)obj)->graph->univ->nodeattr; break;
		case TAG_EDGE:	d = ((Agedge_t*)obj)->tail->graph->univ->edgeattr; break;
	}
	return d;
}

Agsym_t *agNEWsym(Agdict_t* dict, char* name, char* value)
{
	Agsym_t		*a;
	int			i;

	a			=	NEW(Agsym_t);
	a->name		=	agstrdup(name);
	a->value	=	agstrdup(value);
	a->printed	=	TRUE;
	i = a->index	=	dtsize(dict->dict);
	dict->list	=	ALLOC(i + 2, dict->list, Agsym_t*);
	dict->list[i++] = a;
	dict->list[i++] = NULL;
	dtinsert(dict->dict,a);
	return a;
}

static void obj_init_attr(void* obj, Agsym_t* attr)
{
	int			i;
	Agraph_t	*gobj;	/* generic object */

	gobj = (Agraph_t*) obj;
	i = attr->index;
	gobj->attr = ALLOC(i + 1, gobj->attr, char*);
	gobj->attr[i] = agstrdup(attr->value);
}

static void add_graph_attr(Agraph_t* g, Agsym_t* attr)
{
	Agnode_t		*n;

	if (g->meta_node) {
		for (n = agfstnode(g->meta_node->graph); n;
			n = agnxtnode(g->meta_node->graph,n))
				obj_init_attr(agusergraph(n),attr);
	}
	else obj_init_attr(g,attr);
}

static void add_node_attr(Agraph_t* g, Agsym_t* attr)
{
	Agnode_t		*n;
	Agproto_t		*proto;

	for (n = agfstnode(g); n; n = agnxtnode(g,n)) obj_init_attr(n,attr);
	if (g->meta_node) {
		for (n = agfstnode(g->meta_node->graph); n;
			 n = agnxtnode(g->meta_node->graph,n))
				for (proto = agusergraph(n)->proto; proto; proto = proto->prev)
					obj_init_attr(proto->n,attr);
	}
	else for (proto = g->proto; proto; proto = proto->prev)
		obj_init_attr(proto->n,attr);
}

static void add_edge_attr(Agraph_t* g, Agsym_t* attr)
{
	Agnode_t		*n;
	Agedge_t		*e;
	Agproto_t		*proto;

	for (n = agfstnode(g); n; n = agnxtnode(g,n))
		for (e = agfstout(g,n); e; e = agnxtout(g,e))
			obj_init_attr(e,attr);
	if (g->meta_node) {
		for (n = agfstnode(g->meta_node->graph); n;
			n = agnxtnode(g->meta_node->graph,n))
				for (proto = agusergraph(n)->proto; proto; proto = proto->prev)
					obj_init_attr(proto->e,attr);
	}
	else for (proto = g->proto; proto; proto = proto->prev)
		obj_init_attr(proto->e,attr);
}

static Agsym_t *dcl_attr(void* obj, char* name, char* value)
{
    Agsym_t   *rv;
	
	rv = agfindattr(obj, name);
    if (rv) {
        if (strcmp(rv->value,value)) {
			agerr (AGWARN, "Attribute %s=\"%s\" cannot be redeclared as \"%s\"\n",
				name, rv->value, value);
        }
		return rv;
    }
    rv = agNEWsym(agdictof(obj),name,value);
    if (rv) {
        switch (TAG_OF(obj)) {
            case TAG_GRAPH: add_graph_attr((Agraph_t*)obj,rv);			break;
            case TAG_NODE : add_node_attr(((Agnode_t*)obj)->graph,rv);	break;
            case TAG_EDGE : add_edge_attr(((Agedge_t*)obj)->head->graph,rv);break;
        }
    }
    return rv;
}

Agraph_t *agprotograph() 
{
	return AG.proto_g;
}

static void initproto(void)
{
	Agsym_t		*a;
	Agraph_t	*g;
	g = AG.proto_g = agopen("ProtoGraph",AGRAPH);
	a = dcl_attr(g->proto->e,KEY_ID,"");
	if (a->index != KEYX) abort();
	a = dcl_attr(g->proto->e,TAIL_ID,"");
	if (a->index != TAILX) abort();
	a->printed = FALSE;
	a = dcl_attr(g->proto->e,HEAD_ID,"");
	if (a->index != HEADX) abort();
	a->printed = FALSE;
}

Agsym_t *agraphattr(Agraph_t* g, char* name, char* value)
{
	if (g == NULL) g = AG.proto_g;
	if (g != g->root) return NULL;
	return dcl_attr(g,name,value);
}

Agsym_t *agnodeattr (Agraph_t* g, char* name, char* value)
{
	if (g == NULL) g = AG.proto_g;
	if (g != g->root) return NULL;
	return dcl_attr(g->proto->n,name,value);
}

Agsym_t * agedgeattr(Agraph_t* g, char* name, char* value)
{
	if (g == NULL) g = AG.proto_g;
	if (g != g->root) return NULL;
	return dcl_attr(g->proto->e,name,value);
}

/* attribute dictionaries */

static void agfreesym(void* ptr)
{
	Agsym_t		 *a;
	a = (Agsym_t*)ptr;
	agstrfree(a->name);
	agstrfree(a->value);
	free(a);
}

void agFREEdict(Agraph_t* g, Agdict_t* dict)
{
	int		i;
	Agsym_t		*a;

	g = g;
	dtclose(dict->dict);
	if (dict->list) {
		i = 0;
		while ((a=dict->list[i++]))
			agfreesym(a);
		free(dict->list);
	}
	free(dict);
}

Agdict_t * agNEWdict(char* name)
{
	Agdict_t	*dict;
	static Dtdisc_t symdisc = {
		offsetof(Agsym_t,name),		/* key */
		-1,							/* size */
		-1,							/* link */
		(Dtmake_f)0,
		(Dtfree_f)0,
		(Dtcompar_f)0,			/* use strcmp */
		(Dthash_f)0,
		(Dtmemory_f)0,
		(Dtevent_f)0
	};

	dict	= NEW(Agdict_t);
	dict->name = name;
	dict->dict = dtopen(&symdisc,Dttree);
	dict->list = NULL;
	return dict;
}

void agcopydict(Agdict_t* to_dict, Agdict_t* from_dict)
{
	int			i,n;
	Agsym_t		*a,*b;

	n = dtsize(from_dict->dict);
	for (i = 0; i < n; i++) {
		a = from_dict->list[i];
		b = agNEWsym(to_dict,a->name,a->value);
		b->printed = a->printed;
#ifdef MSWIN32
		/* Microsoft C is a thing of wonder. */
fprintf(stderr,"", a->name, a->value);
#endif
	}
}

Agsym_t *agfindattr(void* obj, char* name)
{
	Agsym_t		*rv;
	Agdict_t	*dict	= agdictof(obj);

	rv = (Agsym_t*) dtmatch(dict->dict,name);
	return rv;
}

	/* this is normally called by the aginit() macro */
void aginitlib(int gs, int ns, int es)
{
	if (AG.proto_g == NULL) {
		AG.graph_nbytes = gs;
		AG.node_nbytes = ns;
		AG.edge_nbytes = es;
		AG.init_called = TRUE;
		initproto();
	}
	else
		if ((AG.graph_nbytes!=gs)||(AG.node_nbytes!=ns)||(AG.edge_nbytes!=es))
			agerr(AGWARN,"aginit() called multiply with inconsistent args\n");
}

char *agget(void* obj, char* attr)
{
	return agxget(obj,agindex(obj,attr));
}

int agset(void* obj, char* attr, char* value)
{
	return agxset(obj,agindex(obj,attr),value);
}

int agindex(void* obj, char* name)
{
	Agsym_t		*a;
	int			rv = -1;
	
	a = agfindattr(obj,name);
	if (a) rv = a->index;
	return rv;
}

char *agxget(void* obj, int index)
{
	if (index >= 0) return ((Agraph_t*)obj)->attr[index];
	return NULL;
}

int agxset(void* obj, int index, char* buf)
{
	char	**p;
	if (index >= 0) {
		p = ((Agraph_t*)obj)->attr;
		agstrfree(p[index]);
		p[index] = agstrdup(buf);
		return 0;
	}
	else return -1;
}

