/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/


/*
 * Written by Emden R. Gansner
 */



#include "config.h"

#include <getopt.h>

#include <assert.h>
#include <gvc/gvc.h>
#include <common/render.h>
#include <neatogen/neatoprocs.h>
#include <ingraphs/ingraphs.h>
#include <pack/pack.h>
#include <stddef.h>

#if defined(_WIN32)
  __declspec(dllimport)
#endif
extern gvplugin_library_t gvplugin_neato_layout_LTX_library;

lt_symlist_t lt_preloaded_symbols[] = {
#if defined(_WIN32)
	{ "gvplugin_neato_layout_LTX_library", 0 },
#else
	{ "gvplugin_neato_layout_LTX_library", &gvplugin_neato_layout_LTX_library },
#endif
	{ 0, 0 }
};

/* gvpack:
 * Input consists of graphs in dot format.
 * The graphs should have pos, width and height set for nodes, 
 * bb set for clusters, and, optionally, spline info for edges.
 * The graphs are packed geometrically and combined
 * into a single output graph, ready to be sent to neato -s -n2.
 *  -m <i> specifies the margin, in points, about each graph.
 */

typedef struct {
    Dtlink_t link;
    char *name;
    char *value;
    int cnt;
} attr_t;

typedef struct {
    Dtlink_t link;
    char *name;
    int cnt;
} pair_t;

static int verbose = 0;
static char **myFiles = 0;
static int nGraphs = 0;		/* Guess as to no. of graphs */
static FILE *outfp;		/* output; stdout by default */
static Agdesc_t kind;		/* type of graph */
static int G_cnt;		/* No. of -G arguments */
static int G_sz;		/* Storage size for -G arguments */
static attr_t *G_args;		/* Storage for -G arguments */
static int doPack;              /* Do packing if true */
static char* gname = "root";

#define NEWNODE(n) ((node_t*)ND_alg(n))

static char *useString =
    "Usage: gvpack [-gnuv?] [-m<margin>] {-array[_rc][n]] [-o<outf>] <files>\n\
  -n          - use node granularity\n\
  -g          - use graph granularity\n\
  -array*     - pack as array of graphs\n\
  -G<n>=<v>   - attach name/value attribute to output graph\n\
  -m<n>       - set margin to <n> points\n\
  -s<gname>   - use <gname> for name of root graph\n\
  -o<outfile> - write output to <outfile>\n\
  -u          - no packing; just combine graphs\n\
  -v          - verbose\n\
  -?          - print usage\n\
If no files are specified, stdin is used\n";

static void usage(int v)
{
    printf("%s",useString);
    exit(v);
}

static FILE *openFile(const char *name)
{
    FILE *fp;

    fp = fopen(name, "w");
    if (!fp) {
	fprintf(stderr, "gvpack: could not open file %s for writing\n", name);
	exit(1);
    }
    return (fp);
}

#define G_CHUNK 10

/* setNameValue:
 * If arg is a name-value pair, add it to the list
 * and return 0; otherwise, return 1.
 */
static int setNameValue(char *arg)
{
    char *p;
    char *rhs = "true";

    if ((p = strchr(arg, '='))) {
	*p++ = '\0';
	rhs = p;
    }
    if (G_cnt >= G_sz) {
	G_sz += G_CHUNK;
	G_args = RALLOC(G_sz, G_args, attr_t);
    }
    G_args[G_cnt].name = arg;
    G_args[G_cnt].value = rhs;
    G_cnt++;

    return 0;
}

/* setUInt:
 * If arg is an integer, value is stored in v
 * and function returns 0; otherwise, returns 1.
 */
static int setUInt(unsigned int *v, char *arg)
{
    char *p;
    unsigned int i;

    i = (unsigned int) strtol(arg, &p, 10);
    if (p == arg) {
	fprintf(stderr, "Error: bad value in flag -%s - ignored\n",
		arg - 1);
	return 1;
    }
    *v = i;
    return 0;
}

static Agsym_t *agraphattr(Agraph_t *g, char *name, char *value)
{
    return agattr(g, AGRAPH, name, value);
}

static Agsym_t *agnodeattr(Agraph_t *g, char *name, char *value)
{
    return agattr(g, AGNODE, name, value);
}

static Agsym_t *agedgeattr(Agraph_t *g, char *name, char *value)
{
    return agattr(g, AGEDGE, name, value);
}

/* init:
 */
static void init(int argc, char *argv[], pack_info* pinfo)
{
    int c;
    char buf[BUFSIZ];
    char* bp;

    agnodeattr(NULL, "label", NODENAME_ESC);
    pinfo->mode = l_clust;
    pinfo->margin = CL_OFFSET;
    pinfo->doSplines = TRUE; /* Use edges in packing */
    pinfo->fixed = 0;
    pinfo->sz = 0;

    opterr = 0;
    while ((c = getopt(argc, argv, ":na:gvum:s:o:G:?")) != -1) {
	switch (c) {
	case 'a': {
	    size_t len = strlen(optarg) + 2;
	    if (len > BUFSIZ)
		bp = N_GNEW(len, char);
	    else
		bp = buf;
	    sprintf (bp, "a%s\n", optarg);
	    parsePackModeInfo (bp, pinfo->mode, pinfo);
	    if (bp != buf)
		free (bp);
	    break;
	}
	case 'n':
	    parsePackModeInfo ("node", pinfo->mode, pinfo);
	    break;
	case 's':
	    gname = optarg;
	    break;
	case 'g':
	    parsePackModeInfo ("graph", pinfo->mode, pinfo);
	    break;
	case 'm':
	    setUInt(&pinfo->margin, optarg);
	    break;
	case 'o':
	    if (outfp != NULL)
		fclose(outfp);
	    outfp = openFile(optarg);
	    break;
	case 'u':
	    pinfo->mode = l_undef;
	    break;
	case 'G':
	    if (*optarg)
		setNameValue(optarg);
	    else
		fprintf(stderr,
			"gvpack: option -G missing argument - ignored\n");
	    break;
	case 'v':
	    verbose = 1;
	    Verbose = 1;
	    break;
	case ':':
	    fprintf(stderr, "gvpack: option -%c missing argument - ignored\n", optopt);
	    break;
	case '?':
	    if (optopt == '\0' || optopt == '?')
		usage(0);
	    else {
		fprintf(stderr,
			"gvpack: option -%c unrecognized\n", optopt);
		usage(1);
	    }
	    break;
	}
    }
    argv += optind;
    argc -= optind;

    if (argc > 0) {
	myFiles = argv;
	nGraphs = argc;		/* guess one graph per file */
    } else
	nGraphs = 10;		/* initial guess as to no. of graphs */
    if (!outfp)
	outfp = stdout;		/* stdout the default */
    if (verbose)
	fprintf (stderr, "  margin %d\n", pinfo->margin);
}

/* init_node_edge:
 * initialize node and edge attributes
 */
static void init_node_edge(Agraph_t * g)
{
    node_t *n;
    edge_t *e;
    int nG = agnnodes(g);
    attrsym_t *N_pos = agfindnodeattr(g, "pos");
    attrsym_t *N_pin = agfindnodeattr(g, "pin");

    for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	neato_init_node(n);
	user_pos(N_pos, N_pin, n, nG);	/* set user position if given */
    }
    for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	for (e = agfstout(g, n); e; e = agnxtout(g, e))
	    common_init_edge(e);
    }
}

/* init_graph:
 * Initialize attributes. We always do the minimum required by
 * libcommon. If fill is true, we use init_nop (neato -n) to
 * read in attributes relevant to the layout.
 */
static void init_graph(Agraph_t * g, boolean fill, GVC_t* gvc)
{
    int d;
    node_t *n;
    edge_t *e;

    aginit (g, AGRAPH, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE);
    aginit (g, AGNODE, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE);
    aginit (g, AGEDGE, "Agedgeinfo_t", sizeof(Agedgeinfo_t), TRUE);
    GD_gvc(g) = gvc;
    graph_init(g, FALSE);
    d = late_int(g, agfindgraphattr(g, "dim"), 2, 2);
    if (d != 2) {
	fprintf(stderr, "Error: graph %s has dim = %d (!= 2)\n", agnameof(g),
		d);
	exit(1);
    }
    Ndim = GD_ndim(g) = 2;
    init_node_edge(g);
    if (fill) {
	int ret = init_nop(g,0);
	if (ret) {
	    if (ret < 0)
		fprintf(stderr, "Error loading layout info from graph %s\n",
		    agnameof(g));
	    else if (ret > 0)
		fprintf(stderr, "gvpack does not support backgrounds as found in graph %s\n",
		    agnameof(g));
	    exit(1);
	}
	if (Concentrate) { /* check for edges without pos info */
	    for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
		for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
		    if (ED_spl(e) == NULL) ED_edge_type(e) = IGNORED;
		}
	    }
	}
    }
}

/* cloneAttrs:
 * Copy all attributes from old object to new. Assume
 * attributes have been initialized.
 */
static void cloneDfltAttrs(Agraph_t *old, Agraph_t *new, int kind)
{
    Agsym_t *a;

    for (a = agnxtattr(old, kind, 0); a; a =  agnxtattr(old, kind, a)) {
	if (aghtmlstr(a->defval)) {
	    char *s = agstrdup_html(new, a->defval);
	    agattr(new, kind, a->name, s);
	    agstrfree(new, s); // drop the extra reference count we bumped for s
	} else {
	    agattr (new, kind, a->name, a->defval);
	}
    }
}
static void cloneAttrs(void *old, void *new)
{
    int kind = AGTYPE(old);
    Agsym_t *a;
    char* s;
    Agraph_t *g = agroot(old);
    Agraph_t *ng = agroot(new);

    for (a = agnxtattr(g, kind, 0); a; a =  agnxtattr(g, kind, a)) {
	s = agxget (old, a);
	if (aghtmlstr(s)) {
	    char *scopy = agstrdup_html(ng, s);
	    agset(new, a->name, scopy);
	    agstrfree(ng, scopy); // drop the extra reference count we bumped for scopy
	} else {
	    agset(new, a->name, s);
	}
    }
}

/* cloneEdge:
 * Note that here, and in cloneNode and cloneCluster,
 * we do a shallow copy. We thus assume that attributes
 * are not disturbed. In particular, we assume they are
 * not deallocated.
 */
static void cloneEdge(Agedge_t * old, Agedge_t * new)
{
    cloneAttrs(old, new);
    ED_spl(new) = ED_spl(old);
    ED_edge_type(new) = ED_edge_type(old);
    ED_label(new) = ED_label(old);
    ED_head_label(new) = ED_head_label(old);
    ED_tail_label(new) = ED_tail_label(old);
    ED_xlabel(new) = ED_xlabel(old);
}

/* cloneNode:
 */
static void cloneNode(Agnode_t * old, Agnode_t * new)
{
    cloneAttrs(old, new);
    ND_coord(new).x = POINTS(ND_pos(old)[0]);
    ND_coord(new).y = POINTS(ND_pos(old)[1]);
    ND_height(new) = ND_height(old);
    ND_ht(new) = ND_ht(old);
    ND_width(new) = ND_width(old);
    ND_lw(new) = ND_lw(old);
    ND_rw(new) = ND_rw(old);
    ND_shape(new) = ND_shape(old);
    ND_shape_info(new) = ND_shape_info(old);
    ND_xlabel(new) = ND_xlabel(old);
}

/* cloneCluster:
 */
static void cloneCluster(Agraph_t * old, Agraph_t * new)
{
    /* string attributes were cloned as subgraphs */
    GD_label(new) = GD_label(old);
    GD_bb(new) = GD_bb(old);
}

/* freef:
 * Generic free function for dictionaries.
 */
static void freef(Dt_t * dt, void * obj, Dtdisc_t * disc)
{
    (void)dt;
    (void)disc;

    free(obj);
}

static Dtdisc_t attrdisc = {
    offsetof(attr_t, name),	/* key */
    -1,				/* size */
    offsetof(attr_t, link),	/* link */
    (Dtmake_f) 0,
    (Dtfree_f) freef,
    (Dtcompar_f) 0,		/* use strcmp */
    (Dthash_f) 0,
    (Dtmemory_f) 0,
    (Dtevent_f) 0
};

/* fillDict:
 * Fill newdict with all the name-value attributes of
 * objp. If the attribute has already been defined and
 * has a different default, set default to "".
 */
static void fillDict(Dt_t * newdict, Agraph_t* g, int kind)
{
    Agsym_t *a;
    char *name;
    char *value;
    attr_t *rv;

    for (a = agnxtattr(g,kind,0); a; a = agnxtattr(g,kind,a)) {
	name = a->name;
	value = a->defval;
	rv = dtmatch(newdict, name);
	if (!rv) {
	    rv = NEW(attr_t);
	    rv->name = name;
	    rv->value = value;
	    rv->cnt = 1;
	    dtinsert(newdict, rv);
	} else if (!strcmp(value, rv->value))
	    rv->cnt++;
    }
}

/* fillGraph:
 * Use all the name-value entries in the dictionary d to define
 * to define universal node/edge/graph attributes for g.
 * For a non-empty default value, the attribute must be defined and the
 * same in all graphs.
 */
static void
fillGraph(Agraph_t * g, Dt_t * d,
	  Agsym_t * (*setf) (Agraph_t *, char *, char *), int cnt)
{
    attr_t *av;
    for (av = (attr_t *) dtflatten(d); av;
	 av = (attr_t *) dtlink(d, (Dtlink_t *) av)) {
	if (cnt == av->cnt)
	    setf(g, av->name, av->value);
	else
	    setf(g, av->name, "");
    }
}

/* initAttrs:
 * Initialize the attributes of root as the union of the
 * attributes in the graphs gs.
 */
static void initAttrs(Agraph_t * root, Agraph_t ** gs, int cnt)
{
    Agraph_t *g;
    Dt_t *n_attrs;
    Dt_t *e_attrs;
    Dt_t *g_attrs;
    int i;

    n_attrs = dtopen(&attrdisc, Dtoset);
    e_attrs = dtopen(&attrdisc, Dtoset);
    g_attrs = dtopen(&attrdisc, Dtoset);

    for (i = 0; i < cnt; i++) {
	g = gs[i];
	fillDict(g_attrs, g, AGRAPH);
	fillDict(n_attrs, g, AGNODE);
	fillDict(e_attrs, g, AGEDGE);
    }

    fillGraph(root, g_attrs, agraphattr, cnt);
    fillGraph(root, n_attrs, agnodeattr, cnt);
    fillGraph(root, e_attrs, agedgeattr, cnt);

    dtclose(n_attrs);
    dtclose(e_attrs);
    dtclose(g_attrs);
}

/* cloneGraphAttr:
 */
static void cloneGraphAttr(Agraph_t * g, Agraph_t * ng)
{
    cloneAttrs(g, ng);
    cloneDfltAttrs(g, ng, AGNODE);
    cloneDfltAttrs(g, ng, AGEDGE);
}

#ifdef UNIMPL
/* redoBB:
 * If "bb" attribute is valid, translate to reflect graph's
 * new packed position.
 */
static void redoBB(Agraph_t * g, char *s, Agsym_t * G_bb, point delta)
{
    box bb;
    char buf[100];

    if (sscanf(s, "%d,%d,%d,%d", &bb.LL.x, &bb.LL.y, &bb.UR.x, &bb.UR.y) ==
	4) {
	bb.LL.x += delta.x;
	bb.LL.y += delta.y;
	bb.UR.x += delta.x;
	bb.UR.y += delta.y;
	snprintf(buf, sizeof(buf), "%d,%d,%d,%d", bb.LL.x, bb.LL.y, bb.UR.x, bb.UR.y);
	agxset(g, G_bb->index, buf);
    }
}
#endif

/* xName:
 * Create a name for an object in the new graph using the
 * dictionary names and the old name. If the old name has not
 * been used, use it and add it to names. If it has been used,
 * create a new name using the old name and a number.
 * Note that returned string will immediately made into an agstring.
 */
static char *xName(Dt_t * names, char *oldname)
{
    static char* name = NULL;
    static size_t namelen = 0;
    char *namep;
    pair_t *p;
    size_t len;

    p = dtmatch(names, oldname);
    if (p) {
	p->cnt++;
	len = strlen(oldname) + 100; /* 100 for "_gv" and decimal no. */
	if (namelen < len) {
	    free (name);
	    name = N_NEW(len, char);
	    namelen = len;
	}
	sprintf(name, "%s_gv%d", oldname, p->cnt);
	namep = name;
    } else {
	p = NEW(pair_t);
	p->name = oldname;
	dtinsert(names, p);
	namep = oldname;
    }
    return namep;
}

#define MARK(e) (ED_alg(e) = e)
#define MARKED(e) (ED_alg(e))
#define ISCLUSTER(g) (!strncmp(agnameof(g),"cluster",7))
#define SETCLUST(g,h) (GD_alg(g) = h)
#define GETCLUST(g) ((Agraph_t*)GD_alg(g))

/* cloneSubg:
 * Create a copy of g in ng, copying attributes, inserting nodes
 * and adding edges.
 */
static void
cloneSubg(Agraph_t * g, Agraph_t * ng, Agsym_t * G_bb, Dt_t * gnames)
{
    node_t *n;
    node_t *nn;
    edge_t *e;
    edge_t *ne;
    node_t *nt;
    node_t *nh;
    Agraph_t *subg;
    Agraph_t *nsubg;

    cloneGraphAttr(g, ng);
    if (doPack)
	agxset(ng, G_bb, "");	/* Unset all subgraph bb */

    /* clone subgraphs */
    for (subg = agfstsubg (g); subg; subg = agnxtsubg (subg)) {
	nsubg = agsubg(ng, xName(gnames, agnameof(subg)), 1);
	agbindrec (nsubg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE);
	cloneSubg(subg, nsubg, G_bb, gnames);
	/* if subgraphs are clusters, point to the new 
	 * one so we can find it later.
	 */
	if (ISCLUSTER(subg))
	    SETCLUST(subg, nsubg);
    }

    /* add remaining nodes */
    for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	nn = NEWNODE(n);
        agsubnode(ng, nn, 1);
    }

    /* add remaining edges. libgraph doesn't provide a way to find
     * multiedges, so we add edges bottom up, marking edges when added.
     */
    for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
	    if (MARKED(e))
		continue;
	    nt = NEWNODE(agtail(e));
	    nh = NEWNODE(aghead(e));
	    ne = agedge(ng, nt, nh, NULL, 1);
	    agbindrec (ne, "Agedgeinfo_t", sizeof(Agedgeinfo_t), TRUE);
	    cloneEdge(e, ne);
	    MARK(e);
	}
    }
}

/* cloneClusterTree:
 * Given old and new subgraphs which are corresponding
 * clusters, recursively create the subtree of clusters
 * under ng using the subtree of clusters under g.
 */
static void cloneClusterTree(Agraph_t * g, Agraph_t * ng)
{
    int i;

    cloneCluster(g, ng);

    if (GD_n_cluster(g)) {
	GD_n_cluster(ng) = GD_n_cluster(g);
	GD_clust(ng) = N_NEW(1 + GD_n_cluster(g), Agraph_t *);
	for (i = 1; i <= GD_n_cluster(g); i++) {
	    Agraph_t *c = GETCLUST(GD_clust(g)[i]);
	    GD_clust(ng)[i] = c;
	    cloneClusterTree(GD_clust(g)[i], c);
	}
    }
}

static Dtdisc_t pairdisc = {
    offsetof(pair_t, name),	/* key */
    -1,				/* size */
    offsetof(attr_t, link),	/* link */
    (Dtmake_f) 0,
    (Dtfree_f) freef,
    (Dtcompar_f) 0,		/* use strcmp */
    (Dthash_f) 0,
    (Dtmemory_f) 0,
    (Dtevent_f) 0
};

/* cloneGraph:
 * Create and return a new graph which is the logical union
 * of the graphs gs. 
 */
static Agraph_t *cloneGraph(Agraph_t ** gs, int cnt, GVC_t * gvc)
{
    Agraph_t *root;
    Agraph_t *g;
    Agraph_t *subg;
    Agnode_t *n;
    Agnode_t *np;
    int i;
    Dt_t *gnames;		/* dict of used subgraph names */
    Dt_t *nnames;		/* dict of used node names */
    Agsym_t *G_bb;
    Agsym_t *rv;
    boolean doWarn = TRUE;

    if (verbose)
	fprintf(stderr, "Creating clone graph\n");
    root = agopen(gname, kind, &AgDefaultDisc);
    initAttrs(root, gs, cnt);
    G_bb = agfindgraphattr(root, "bb");
    if (doPack) assert(G_bb);

    /* add command-line attributes */
    for (i = 0; i < G_cnt; i++) {
	rv = agfindgraphattr(root, G_args[i].name);
	if (rv)
	    agxset(root, rv, G_args[i].value);
	else
	    agattr(root, AGRAPH, G_args[i].name, G_args[i].value);
    }

    /* do common initialization. This will handle root's label. */
    init_graph(root, FALSE, gvc);
    State = GVSPLINES;

    gnames = dtopen(&pairdisc, Dtoset);
    nnames = dtopen(&pairdisc, Dtoset);
    for (i = 0; i < cnt; i++) {
	g = gs[i];
	if (verbose)
	    fprintf(stderr, "Cloning graph %s\n", agnameof(g));
	GD_n_cluster(root) += GD_n_cluster(g);
	GD_has_labels(root) |= GD_has_labels(g);

	/* Clone nodes, checking for node name conflicts */
	for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	    if (doWarn && agfindnode(root, agnameof(n))) {
		fprintf(stderr,
			"Warning: node %s in graph[%d] %s already defined\n",
			agnameof(n), i, agnameof(g));
		fprintf(stderr, "Some nodes will be renamed.\n");
		doWarn = FALSE;
	    }
	    np = agnode(root, xName(nnames, agnameof(n)), 1);
	    agbindrec (np, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE);
	    ND_alg(n) = np;
	    cloneNode(n, np);
	}

	/* wrap the clone of g in a subgraph of root */
	subg = agsubg(root, xName(gnames, agnameof(g)), 1);
	agbindrec (subg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE);
	cloneSubg(g, subg, G_bb, gnames);
    }
    dtclose(gnames);
    dtclose(nnames);

    /* set up cluster tree */
    if (GD_n_cluster(root)) {
	int j, idx;
	GD_clust(root) = N_NEW(1 + GD_n_cluster(root), graph_t *);

	idx = 1;
	for (i = 0; i < cnt; i++) {
	    g = gs[i];
	    for (j = 1; j <= GD_n_cluster(g); j++) {
		Agraph_t *c = GETCLUST(GD_clust(g)[j]);
		GD_clust(root)[idx++] = c;
		cloneClusterTree(GD_clust(g)[j], c);
	    }
	}
    }

    return root;
}

static Agraph_t *gread(FILE * fp)
{
    return agread(fp, NULL);
}

/* readGraphs:
 * Read input, parse the graphs, use init_nop (neato -n) to
 * read in all attributes need for layout.
 * Return the list of graphs. If cp != NULL, set it to the number
 * of graphs read.
 * We keep track of the types of graphs read. They all must be
 * either directed or undirected. If all graphs are strict, the
 * combined graph will be strict; other, the combined graph will
 * be non-strict.
 */
static Agraph_t **readGraphs(int *cp, GVC_t* gvc)
{
    Agraph_t *g;
    Agraph_t **gs = 0;
    ingraph_state ig;
    int cnt = 0;
    int sz = 0;
    int kindUnset = 1;

    /* set various state values */
    PSinputscale = POINTS_PER_INCH;
    Nop = 2;

    newIngraph(&ig, myFiles, gread);
    while ((g = nextGraph(&ig)) != 0) {
	if (verbose)
	    fprintf(stderr, "Reading graph %s\n", agnameof(g));
	if (agnnodes(g) == 0) {
	    fprintf(stderr, "Graph %s is empty - ignoring\n", agnameof(g));
	    continue;
	}
	if (cnt >= sz) {
	    sz += nGraphs;
	    gs = ALLOC(sz, gs, Agraph_t *);
	}
	if (kindUnset) {
	    kindUnset = 0;
	    kind = g->desc;
	}
	else if (kind.directed != g->desc.directed) {
	    fprintf(stderr,
		    "Error: all graphs must be directed or undirected\n");
	    exit(1);
	} else if (!agisstrict(g))
	    kind = g->desc;
	init_graph(g, doPack, gvc);
	gs[cnt++] = g;
    }

    gs = RALLOC(cnt, gs, Agraph_t *);
    if (cp)
	*cp = cnt;
    return gs;
}

/* compBB:
 * Compute the bounding box containing the graphs.
 * We can just use the bounding boxes of the graphs.
 */
static boxf compBB(Agraph_t ** gs, int cnt)
{
    boxf bb, bb2;
    int i;

    bb = GD_bb(gs[0]);

    for (i = 1; i < cnt; i++) {
	bb2 = GD_bb(gs[i]);
	bb.LL.x = MIN(bb.LL.x, bb2.LL.x);
	bb.LL.y = MIN(bb.LL.y, bb2.LL.y);
	bb.UR.x = MAX(bb.UR.x, bb2.UR.x);
	bb.UR.y = MAX(bb.UR.y, bb2.UR.y);
    }

    return bb;
}

#ifdef DEBUG
void dump(Agraph_t * g)
{
    node_t *v;
    edge_t *e;

    for (v = agfstnode(g); v; v = agnxtnode(g, v)) {
	fprintf(stderr, "%s\n", agnameof(v));
	for (e = agfstout(g, v); e; e = agnxtout(g, e)) {
	    fprintf(stderr, "  %s -- %s\n", agnameof(agtail(e)), agnameof(aghead(e)));
	}
    }
}

void dumps(Agraph_t * g)
{
    graph_t *subg;

    for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
	dump(subg);
	fprintf(stderr, "====\n");
    }
}
#endif

int main(int argc, char *argv[])
{
    Agraph_t **gs;
    Agraph_t *g;
    int cnt;
    pack_info pinfo;
    GVC_t * gvc;

    init(argc, argv, &pinfo);

    doPack = (pinfo.mode != l_undef);

#if defined(_WIN32)
    lt_preloaded_symbols[0].address = &gvplugin_neato_layout_LTX_library;
#endif
    gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING);
    gs = readGraphs(&cnt, gvc);
    if (cnt == 0)
	exit(0);

    /* pack graphs */
    if (doPack) {
	if (packGraphs(cnt, gs, 0, &pinfo)) {
	    fprintf(stderr, "gvpack: packing of graphs failed.\n");
	    exit(1);
	}
    }

    /* create union graph and copy attributes */
    g = cloneGraph(gs, cnt, gvc);

    /* compute new top-level bb and set */
    if (doPack) {
	GD_bb(g) = compBB(gs, cnt);
	dotneato_postprocess(g);
	attach_attrs(g);
    }
    agwrite(g, outfp);
    exit(0);
}
