/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - easyeda file format support
 *  Copyright (C) 2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2024)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

/* high level for the 'std' format - included from read.c */

static int easystd_parse_shape(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd);
static int easystd_parse_shapes(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd);

/* Parse a whole ` separated string of attributes; preserves lst but writes it */
static void easystd_parse_attr_lst(read_ctx_t *ctx, csch_attribs_t *attribs, gdom_node_t *src_nd, char *lst)
{
	char *curr, *next;

	if ((lst == NULL) || (*lst == '\0'))
		return;

	for(curr = lst; *curr != '\0'; curr = next) {
		csch_source_arg_t *src;
		char *key = curr, *val;

		val = strchr(key, '`');
		if (val == NULL) {
			error_at(ctx, src_nd, ("attribute list with odd number of elements - ignoring last key\n"));
			break;
		}
		*val = '\0';
		val++;
		next = strchr(val, '`');
		if (next != NULL) {
			*next = '\0';
			next++;
		}

		src = easyeda_attrib_src_c(ctx, src_nd, NULL);
		csch_attrib_set(attribs, CSCH_ATP_USER_DEFAULT, key, val, src, NULL);

		/* restore */
		val[-1] = '`';
		if (next == NULL)
			break;
		next[-1] = '`';
	}
}


static int easystd_parse_wire(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	gdom_node_t *coords;
	long n;
	double lx, ly, x, y;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: wire must be a hash\n"));
		return -1;
	}

	HASH_GET_SUBTREE(coords,   nd, easy_coords, GDOM_ARRAY,   return -1);

	if ((coords->value.array.used % 2) != 0) {
		error_at(ctx, coords, ("wire coords: must have even number of coords\n"));
		return -1;
	}

	for(n = 0; n < coords->value.array.used; n += 2) {
		x = easyeda_get_double(ctx, coords->value.array.child[n]);
		y = easyeda_get_double(ctx, coords->value.array.child[n+1]);

		if (n > 0)
			csch_alien_mknet(&ctx->alien, &ctx->sheet->direct, lx, ly, x, y);
		lx = x;
		ly = y;
	}

	return 0;
}

static int easystd_parse_sym(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	csch_cgrp_t *sym;
	gdom_node_t *shapes;
	csch_source_arg_t *src;
/*	int rot;*/
	char *attribs;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: symbol must be a hash\n"));
		return -1;
	}

	HASH_GET_SUBTREE(shapes,   nd, easy_shapes, GDOM_ARRAY,   return -1);

/*	HASH_GET_DOUBLE(x,   nd, easy_x,     return -1);
	HASH_GET_DOUBLE(y,   nd, easy_y,     return -1);*/
/*	HASH_GET_LONG(rot,   nd, easy_rot,   return -1);*/
	HASH_GET_STRING(attribs, nd, easy_attributes, return -1);

	sym = csch_cgrp_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	src = easyeda_attrib_src_c(ctx, nd, NULL);
	csch_cobj_attrib_set(ctx->sheet, sym, CSCH_ATP_HARDWIRED, "role", "symbol", src);
/* objects are stored in sheet-coords
	sym->x = x;
	sym->y = y;
	*/
	sym->hdr.lock = 0;

	easystd_parse_attr_lst(ctx, &sym->attr, nd, attribs);

	easyeda_apply_lock(ctx, nd, &sym->hdr);

	return easystd_parse_shapes(ctx, sym, shapes);
}

typedef enum {
	EASYEDA_TTY_NORMAL,
	EASYEDA_TTY_SYMLAB,
	EASYEDA_TTY_PINLAB_NAME,
	EASYEDA_TTY_PINLAB_NUM
} easyeda_text_type_t;

static int easystd_parse_text_(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd, easyeda_text_type_t tty, int omit_text_obj)
{
	csch_text_t *txt;
	csch_source_arg_t *src;
	const char *penname, *txtstr, *anchor;
	double x, y;
	long visible, rot;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: text must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,      nd, easy_x,            return -1);
	HASH_GET_DOUBLE(y,      nd, easy_y,            return -1);
	HASH_GET_LONG(rot,    nd, easy_rot,          return -1);
	HASH_GET_STRING(txtstr, nd, easy_text,         return -1);
	HASH_GET_STRING(anchor, nd, easy_text_anchor,  return -1);
	HASH_GET_LONG(visible,  nd, easy_visible,      return -1);

	penname = DECOR_PEN_NAME(parent);
	if (omit_text_obj)
		visible = 0;

	TODO("do something with text_type; 'comment' is normal text, 'spice' is spice command");

	if (visible)
		txt = (csch_text_t *)csch_alien_mktext(&ctx->alien, parent, x, y, penname);
	else
		txt = NULL;

	if ((tty == EASYEDA_TTY_SYMLAB) && (parent->role == CSCH_ROLE_SYMBOL)) { /* set name and device attribute */
		const char *mark;

		HASH_GET_STRING(mark,   nd, easy_mark,         return -1);

		switch(*mark) {
			case 'P': /* Prefix (refdes) */
				src = easyeda_attrib_src_c(ctx, nd, NULL);
				csch_attrib_set(&parent->attr, CSCH_ATP_USER_DEFAULT, "name", txtstr, src, NULL);
				txtstr = "%../a.name%";
				if (txt != NULL)
					txt->dyntext = txt->hdr.floater = 1;
				break;

			case 'N': /* Name (device) */
				src = easyeda_attrib_src_c(ctx, nd, NULL);
				csch_attrib_set(&parent->attr, CSCH_ATP_USER_DEFAULT, "device", txtstr, src, NULL);
				txtstr = "%../a.device%";
				if (txt != NULL)
					txt->dyntext = txt->hdr.floater = 1;
				break;

			case 'L': break;

			default:
				error_at(ctx, nd, ("unknown text mark in symbol: '%s'\n", mark));

		}
	}
	else if ((tty == EASYEDA_TTY_PINLAB_NAME) && (parent->role == CSCH_ROLE_TERMINAL)) { /* set name attribute */
		src = easyeda_attrib_src_c(ctx, nd, NULL);
		csch_attrib_set(&parent->attr, CSCH_ATP_USER_DEFAULT, "name", txtstr, src, NULL);
		txtstr = "%../a.name%";
		if (txt != NULL)
			txt->dyntext = 1;
	}
	else if ((tty == EASYEDA_TTY_PINLAB_NUM) && (parent->role == CSCH_ROLE_TERMINAL)) { /* set num attribute */
		src = easyeda_attrib_src_c(ctx, nd, NULL);
		csch_attrib_set(&parent->attr, CSCH_ATP_USER_DEFAULT, "pinnum", txtstr, src, NULL);
		txtstr = "%../a.pinnum%";
		if (txt != NULL)
			txt->dyntext = 1;
	}

	if (txt != NULL) {
		txt->text = rnd_strdup(txtstr);
		easyeda_text_anchor(ctx, txt, anchor, nd);
		easyeda_apply_lock(ctx, nd, &txt->hdr);

		switch(rot) {
			case 0: case 180: case 270: case 90: txt->spec_rot = rot; break;
			default: error_at(ctx, nd, ("ignoring invalid text rotation value: %ld\n", rot));
		}
	}

	return 0;
}

static int easystd_parse_text(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	return easystd_parse_text_(ctx, parent, nd, EASYEDA_TTY_SYMLAB, 0);
}

/* Creates a sym with a zero-length-line terminal at x;y and graphics from
   shapes (if not NULL) and/or path (if not NULL). parent_nd is used for
   error reporting. Returns term (sym is term's parent) or NULL on error */
static csch_cgrp_t *easystd_mkpath_sym(read_ctx_t *ctx, csch_cgrp_t *parent, double x, double y, gdom_node_t *parent_nd, gdom_node_t *shapes, const char *path)
{
	csch_source_arg_t *src;
	csch_cgrp_t *sym, *term;

	/* create symbol */
	sym = csch_cgrp_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	src = easyeda_attrib_src_c(ctx, parent_nd, NULL);
	csch_cobj_attrib_set(ctx->sheet, sym, CSCH_ATP_HARDWIRED, "role", "symbol", src);

	/* create terminal */
	src = easyeda_attrib_src_c(ctx, parent_nd, NULL);
	term = csch_cgrp_alloc(ctx->sheet, sym, csch_oid_new(ctx->sheet, sym));
	csch_cobj_attrib_set(ctx->sheet, term, CSCH_ATP_HARDWIRED, "role", "terminal", src);

	csch_alien_mkline(&ctx->alien, term, x, y, x, y, "term-decor");

	if (shapes != NULL) {
		if (easystd_parse_shapes(ctx, sym, shapes) != 0)
			return NULL;
	}

	if (path != NULL) {
		if (easyeda_mkpath(ctx, sym, path, parent_nd, "sym-decor", 0) != 0)
			return NULL;
	}

	return term;
}

/* a netflag is a netname with some graphics, e.g. the VCC rail; all graphics
   specified by the file */
static int easystd_parse_netflag(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	csch_source_arg_t *src;
	csch_cgrp_t *sym, *term;
	gdom_node_t *shapes, *label;
	double x, y;
/*	long rot; */

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: netflags must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,   nd, easy_x,     return -1);
	HASH_GET_DOUBLE(y,   nd, easy_y,     return -1);
/*	HASH_GET_LONG(rot,   nd, easy_rot,   return -1); */
	HASH_GET_SUBTREE(shapes,   nd, easy_shapes, GDOM_ARRAY,   return -1);
	HASH_GET_SUBTREE(label,    nd, easy_label, GDOM_HASH,   return -1);

	term = easystd_mkpath_sym(ctx, parent, x, y, nd, shapes, NULL);
	if (term == NULL)
		return -1;

	sym = term->hdr.parent;
	src = easyeda_attrib_src_c(ctx, nd, "io_easyeda: netflag implicit terminal");
	csch_attrib_set(&term->attr, CSCH_ATP_USER_DEFAULT, "name", "1", src, NULL);

	/* create the label */
	if (label != NULL) {
		const char *netname;

		HASH_GET_STRING(netname,   label, easy_text,   return -1);

		easystd_parse_text_(ctx, sym, label, EASYEDA_TTY_NORMAL, 0);
		easyeda_mkrail(ctx, sym, netname, label);
	}

	easyeda_apply_lock(ctx, nd, &sym->hdr);

	return 0;
}

static int easystd_parse_path(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: path must be a hash\n"));
		return -1;
	}

	return easyeda_parse_path_(ctx, parent, nd, 1, EASYEDA_FILL_AUTO);
}


static int easystd_parse_rect(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	double x, y, w, h, r, rx, ry;
	const char *penname;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: rect must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,   nd, easy_x,     return -1);
	HASH_GET_DOUBLE(y,   nd, easy_y,     return -1);
	HASH_GET_DOUBLE(rx,  nd, easy_rx,    return -1);
	HASH_GET_DOUBLE(ry,  nd, easy_ry,    return -1);
	HASH_GET_DOUBLE(w,   nd, easy_width, return -1);
	HASH_GET_DOUBLE(h,   nd, easy_height,return -1);
/*	HASH_GET_STRING(fillstr,  nd, easy_fill_color, return -1);*/

	penname = DECOR_PEN_NAME(parent);
/*	filled = ((fillstr == NULL) || (*fillstr == '\0') || (rnd_strcasecmp(fillstr, "none") == 0)) ? 0 : 1;*/
	r = (rx+ry)/2.0;

	parent = (csch_cgrp_t *)csch_alien_mkpoly(&ctx->alien, parent, penname, NULL);

	if ((r > 0) && (rx != ry))
		error_at(ctx, nd, ("round rect: elliptical rounding not supported, using circular with average radius\n"));

	if (r > 0) csch_alien_append_poly_arc(&ctx->alien, &parent->hdr, x+r, y+r, r, 90, 90);
	csch_alien_append_poly_line(&ctx->alien, &parent->hdr, x+r, y,     x+w-r, y);
	if (r > 0) csch_alien_append_poly_arc(&ctx->alien, &parent->hdr, x+w-r, y+r, r, 0, 90);
	csch_alien_append_poly_line(&ctx->alien, &parent->hdr, x+w, y+r,   x+w, y+h-r);
	if (r > 0) csch_alien_append_poly_arc(&ctx->alien, &parent->hdr, x+r, y+h-r, r, 270, -90);
	csch_alien_append_poly_line(&ctx->alien, &parent->hdr, x+w-r, y+h, x+r, y+h);
	if (r > 0) csch_alien_append_poly_arc(&ctx->alien, &parent->hdr, x+w-r, y+h-r, r, 0, -90);
	csch_alien_append_poly_line(&ctx->alien, &parent->hdr, x, y+h-r,   x, y+r);

	easyeda_apply_lock(ctx, nd, &parent->hdr);

	return 0;
}

static int easystd_parse_line(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	double x1, y1, x2, y2;
	const char *penname;
	csch_chdr_t *lin;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: line must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x1,  nd, easy_x1,    return -1);
	HASH_GET_DOUBLE(y1,  nd, easy_y1,    return -1);
	HASH_GET_DOUBLE(x2,  nd, easy_x2,    return -1);
	HASH_GET_DOUBLE(y2,  nd, easy_y2,    return -1);

	penname = DECOR_PEN_NAME(parent);

	lin = csch_alien_mkline(&ctx->alien, parent, x1, y1, x2, y2, penname);

	easyeda_apply_lock(ctx, nd, lin);

	return 0;
}


static int easystd_parse_circle(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	double x, y, r;
	const char *penname, *fillstr;
	csch_chdr_t *circ;
	int filled;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: circle must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,   nd, easy_x,     return -1);
	HASH_GET_DOUBLE(y,   nd, easy_y,     return -1);
	HASH_GET_DOUBLE(r,   nd, easy_r,     return -1);
	HASH_GET_STRING(fillstr,  nd, easy_fill_color, return -1);

	penname = DECOR_PEN_NAME(parent);
	filled = ((fillstr == NULL) || (*fillstr == '\0') || (rnd_strcasecmp(fillstr, "none") == 0)) ? 0 : 1;

	if (filled) {
		circ = csch_alien_mkpoly(&ctx->alien, parent, penname, penname);

		/* create 3 arcs to get a nicely behaving "triangular" polygon, just in case */
		csch_alien_append_poly_arc(&ctx->alien, circ, x, y, r, 0, 120);
		csch_alien_append_poly_arc(&ctx->alien, circ, x, y, r, 120, 120);
		csch_alien_append_poly_arc(&ctx->alien, circ, x, y, r, 240, 120);
	}
	else
		circ = csch_alien_mkarc(&ctx->alien, parent, x, y, r, 0, 360, penname);

	easyeda_apply_lock(ctx, nd, circ);

	return 0;
}


/* filled and unfilled polygons from a coord list */
static int easystd_parse_poly_(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd, int allow_fill, const char *penname)
{
	const char *fill;
	gdom_node_t *coords;
	double lx, ly, x, y;
	long n;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: polyline must be a hash\n"));
		return -1;
	}

	HASH_GET_SUBTREE(coords,   nd, easy_coords, GDOM_ARRAY,   return -1);

	if (allow_fill) {
		HASH_GET_STRING(fill, nd, easy_fill_color, return -1);
		if (rnd_strcasecmp(fill, "none") == 0)
			allow_fill = 0;
	}

	if (penname == NULL)
		penname = DECOR_PEN_NAME(parent);

	if (allow_fill) {
		csch_chdr_t *poly = csch_alien_mkpoly(&ctx->alien, parent, penname, penname);

		for(n = 0; n < coords->value.array.used; n+=2) {
			x = easyeda_get_double(ctx, coords->value.array.child[n]);
			y = easyeda_get_double(ctx, coords->value.array.child[n+1]);
			if (n > 0)
				csch_alien_append_poly_line(&ctx->alien, poly, lx, ly, x, y);
			lx = x;
			ly = y;
		}

		easyeda_apply_lock(ctx, nd, poly);
	}
	else {
		csch_cgrp_t *plp;
		csch_chdr_t *last = NULL;

		if (coords->value.array.used > 2)
			plp = csch_cgrp_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
		else
			plp = parent;

		for(n = 0; n < coords->value.array.used; n+=2) {
			x = easyeda_get_double(ctx, coords->value.array.child[n]);
			y = easyeda_get_double(ctx, coords->value.array.child[n+1]);
			if (n > 0)
				last = csch_alien_mkline(&ctx->alien, plp, lx, ly, x, y, penname);
			lx = x;
			ly = y;
		}

		if (coords->value.array.used > 2)
			easyeda_apply_lock(ctx, nd, &plp->hdr);
		else if (last != NULL)
			easyeda_apply_lock(ctx, nd, last);
	}

	return 0;
}

static int easystd_parse_polyline(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	return easystd_parse_poly_(ctx, parent, nd, 0, NULL);
}

static int easystd_parse_polygon(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	return easystd_parse_poly_(ctx, parent, nd, 1, NULL);
}

static int easystd_parse_bus(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	return easystd_parse_poly_(ctx, parent, nd, 1, "bus");
}

/* This is really a bus terminal */
static int easystd_parse_busentry(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	double x1, y1, x2, y2;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: busentry must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x1,       nd, easy_x1,       return -1);
	HASH_GET_DOUBLE(y1,       nd, easy_y1,       return -1);
	HASH_GET_DOUBLE(x2,       nd, easy_x2,       return -1);
	HASH_GET_DOUBLE(y2,       nd, easy_y2,       return -1);

	/* should be a straight line between the two coords */
	csch_alien_mkline(&ctx->alien, parent, x1, y1, x2, y2, "busterm-decor");

	TODO("figure how to bind net to bus (maybe in postproc");
	error_at(ctx, nd, ("internal: busentry not yet implemented (TODO)\n"));

/*	easyeda_apply_lock(ctx, nd, &sym->hdr); */

	return 0;
}

static int easystd_parse_image_common(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	csch_cgrp_t *sym;
	double x, y, w, h;

	HASH_GET_DOUBLE(x,        nd, easy_x,        return -1);
	HASH_GET_DOUBLE(y,        nd, easy_y,        return -1);
	HASH_GET_DOUBLE(w,        nd, easy_width,    return -1);
	HASH_GET_DOUBLE(h,        nd, easy_height,   return -1);

	sym = easyeda_mkimage_sym(ctx, parent, nd, x, y, w, h);

	easyeda_apply_lock(ctx, nd, &sym->hdr);

	return 0;
}

static int easystd_parse_pimage(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: pimage must be a hash\n"));
		return -1;
	}
	return easystd_parse_image_common(ctx, parent, nd);
}

static int easystd_parse_image(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: image must be a hash\n"));
		return -1;
	}

	return easystd_parse_image_common(ctx, parent, nd);
}

static int easystd_parse_pin(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	csch_source_arg_t *src;
	csch_cgrp_t *pin;
	const char *pathstr, *clkpath, *show;
	gdom_node_t *nname, *nnum;
	long clk_visible, not_visible;
	double notx, noty;
	int hide;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: rect must be a hash\n"));
		return -1;
	}

/* not used
	HASH_GET_DOUBLE(x,        nd, easy_x,        return -1);
	HASH_GET_DOUBLE(y,        nd, easy_y,        return -1);
*/
	HASH_GET_STRING(pathstr,  nd, easy_pin_path,               return -1);
	HASH_GET_SUBTREE(nname,   nd, easy_name,      GDOM_HASH,   return -1);
	HASH_GET_SUBTREE(nnum,    nd, easy_num,       GDOM_HASH,   return -1);
	HASH_GET_STRING(show,     nd, easy_show,                   return -1);
	HASH_GET_LONG(clk_visible,nd, easy_clk_visible,            return -1);
	HASH_GET_STRING(clkpath,  nd, easy_clk_path,               return -1);
	HASH_GET_LONG(not_visible,nd, easy_not_visible,            return -1);
	HASH_GET_DOUBLE(notx,     nd, easy_not_x,                  return -1);
	HASH_GET_DOUBLE(noty,     nd, easy_not_y,                  return -1);

	src = easyeda_attrib_src_c(ctx, nd, NULL);
	pin = csch_cgrp_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	csch_cobj_attrib_set(ctx->sheet, pin, CSCH_ATP_HARDWIRED, "role", "terminal", src);


	hide = (*show == 'n') || (*show == 'N');

	easystd_parse_text_(ctx, pin, nname, EASYEDA_TTY_PINLAB_NAME, hide);
	easystd_parse_text_(ctx, pin, nnum, EASYEDA_TTY_PINLAB_NUM, hide);

	if (!hide) {
		easyeda_mkpath(ctx, pin, pathstr, nd, "term-decor", 0);

		/* create extra pin graphics */
		if (clk_visible)
			easyeda_mkpath(ctx, pin, clkpath, nd, "term-decor", 0);

		if (not_visible)
			csch_alien_mkarc(&ctx->alien, pin, notx, noty, 3, 0, 360, "term-decor");

		easyeda_apply_lock(ctx, nd, &pin->hdr);
	}

	return 0;
}

static int easystd_parse_arc(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: arc must be a hash\n"));
		return -1;
	}

	/* arc is specified by an svg path */
	return easyeda_parse_path_(ctx, parent, nd, 1, EASYEDA_FILL_AUTO);
}

static int easystd_parse_pie(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: pie must be a hash\n"));
		return -1;
	}

	/* pie is specified by an svg path */
	return easyeda_parse_path_(ctx, parent, nd, 1, EASYEDA_FILL_AUTO);
}

static int easystd_parse_arrowhead(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: arrowhead must be a hash\n"));
		return -1;
	}

	/* arrowhead is specified by an svg path */
	return easyeda_parse_path_(ctx, parent, nd, 1, EASYEDA_FILL_AUTO);
}

static int easystd_parse_ellipse(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	path_ctx_t pctx;
	double x, y, rx, ry, ex, ey;
	const char *penname, *fillstr;
	int filled;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: arc must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,        nd, easy_x,        return -1);
	HASH_GET_DOUBLE(y,        nd, easy_y,        return -1);
	HASH_GET_DOUBLE(rx,       nd, easy_rx,       return -1);
	HASH_GET_DOUBLE(ry,       nd, easy_ry,       return -1);
	HASH_GET_STRING(fillstr,  nd, easy_fill_color, return -1);

	penname = DECOR_PEN_NAME(parent);
	filled = ((fillstr == NULL) || (*fillstr == '\0') || (rnd_strcasecmp(fillstr, "none") == 0)) ? 0 : 1;

	/* draw circles accurately using arcs */
	if (fabs(rx - ry) < (rx / 1000.0)) {
		if (filled) {
			csch_chdr_t *poly = csch_alien_mkpoly(&ctx->alien, parent, penname, penname);

			/* create 3 arcs to get a nicely behaving "triangular" polygon, just in case */
			csch_alien_append_poly_arc(&ctx->alien, poly, x, y, rx, 0, 120);
			csch_alien_append_poly_arc(&ctx->alien, poly, x, y, rx, 120, 120);
			csch_alien_append_poly_arc(&ctx->alien, poly, x, y, rx, 240, 120);
		}
		else
			csch_alien_mkarc(&ctx->alien, parent, x, y, rx, 0, 360, penname);
		return 0;
	}

	/* real ellipse - approximate */
	ex = x + rx;
	ey = y;

	/* svgpath happens to have an elliptical approximator and can handle
	   filled/unfilled already */
	easyeda_svgpath_setup();

	pctx.ctx = ctx;
	pctx.nd = nd;
	pctx.penname = penname;
	pctx.in_poly = csch_alien_mkpoly(&ctx->alien, parent, penname, filled ? penname : NULL);
	pctx.parent = parent;

	svgpath_approx_earc(&pathcfg, &pctx, ex, ey, x, y, rx, ry, 0, 2*M_PI, 0, ex, ey, pathcfg.curve_approx_seglen*pathcfg.curve_approx_seglen);

	easyeda_apply_lock(ctx, nd, pctx.in_poly);

	return 0;
}

static int easystd_parse_noconn(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	csch_cgrp_t *sym, *term;
	const char *path;
	double x, y;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: noconn must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,   nd, easy_x,     return -1);
	HASH_GET_DOUBLE(y,   nd, easy_y,     return -1);
	HASH_GET_STRING(path,nd, easy_path,  return -1);

	/* noconn is specified by an svg path */
	term = easystd_mkpath_sym(ctx, parent, x, y, nd, NULL, path);
	if (term == NULL)
		return -1;

	TODO("do whatever needed for making this sym a real noconn for the DRC");
	sym = term->hdr.parent;

	easyeda_apply_lock(ctx, nd, &sym->hdr);

	return 0;
}

static int easystd_parse_netlabel(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	double x, y, text_x, text_y;
	const char *textstr, *penname, *templ, *anchor;
	csch_line_t *wire;
	csch_cgrp_t *eff_parent;
	csch_text_t *txt;

	if (nd->type != GDOM_HASH) {
		error_at(ctx, nd, ("internal: noconn must be a hash\n"));
		return -1;
	}

	HASH_GET_DOUBLE(x,       nd, easy_x,            return -1);
	HASH_GET_DOUBLE(y,       nd, easy_y,            return -1);
	HASH_GET_DOUBLE(text_x,  nd, easy_text_x,       return -1);
	HASH_GET_DOUBLE(text_y,  nd, easy_text_y,       return -1);
	HASH_GET_STRING(textstr, nd, easy_text,         return -1);
	HASH_GET_STRING(anchor,  nd, easy_text_anchor,  return -1);

	wire = csch_find_wire_at(ctx->sheet, csch_alien_coord_x(&ctx->alien, x), csch_alien_coord_y(&ctx->alien, y), NULL);
	if (wire == NULL) {
		error_at(ctx, nd, ("Can not find wire for netlabel %s - placing as decoration text\n", textstr));
		eff_parent = parent;
		penname = "sheet-decor";
		templ = textstr;
	}
	else {
		csch_source_arg_t *src;

		eff_parent = wire->hdr.parent;
		penname = "wire";
		templ = "%../A.name%";

		src = easyeda_attrib_src_c(ctx, nd, NULL);
		csch_attrib_set(&eff_parent->attr, CSCH_ATP_USER_DEFAULT, "name", textstr, src, NULL);
	}

	txt = (csch_text_t *)csch_alien_mktext(&ctx->alien, eff_parent, text_x, text_y, penname);
	txt->text = rnd_strdup(templ);

	if (wire != NULL)  {
		txt->dyntext = 1;
		txt->hdr.floater = 1;
	}

	easyeda_text_anchor(ctx, txt, anchor, nd);

	easyeda_apply_lock(ctx, nd, &txt->hdr);

	return 0;
}

/*** shape (generic) ***/

static int easystd_parse_shape_pass1(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	/* pass 1 recognizes unknown/unhandled objects and creates the basic drawing */
	switch(nd->name) {
		case easy_wire: return easystd_parse_wire(ctx, parent, nd);
		case easy_symbol: return easystd_parse_sym(ctx, parent, nd);
		case easy_netflag: return easystd_parse_netflag(ctx, parent, nd);
		case easy_text: return easystd_parse_text(ctx, parent, nd);
		case easy_path: return easystd_parse_path(ctx, parent, nd);
		case easy_rect: return easystd_parse_rect(ctx, parent, nd);
		case easy_line: return easystd_parse_line(ctx, parent, nd);
		case easy_circle: return easystd_parse_circle(ctx, parent, nd);
		case easy_polyline: return easystd_parse_polyline(ctx, parent, nd);
		case easy_polygon: return easystd_parse_polygon(ctx, parent, nd);
		case easy_bus: return easystd_parse_bus(ctx, parent, nd);
		case easy_busentry: return easystd_parse_busentry(ctx, parent, nd);
		case easy_pimage: return easystd_parse_pimage(ctx, parent, nd);
		case easy_image: return easystd_parse_image(ctx, parent, nd);
		case easy_pin: return easystd_parse_pin(ctx, parent, nd);
		case easy_arc: return easystd_parse_arc(ctx, parent, nd);
		case easy_pie: return easystd_parse_pie(ctx, parent, nd);
		case easy_ellipse: return easystd_parse_ellipse(ctx, parent, nd);
		case easy_arrowhead: return easystd_parse_arrowhead(ctx, parent, nd);
		case easy_junction: return 0; /* junctions are auto-created */
		case easy_noconn: return easystd_parse_noconn(ctx, parent, nd);
		case easy_netlabel: return 0; /* in pass 2 */
		default:
			error_at(ctx, nd, ("Unknown shape '%s'\n", easy_keyname(nd->name)));
			if (nd->type == GDOM_STRING)
				error_at(ctx, nd, (" shape string is '%s'\n", nd->value.str));
	}

	return -1;
}

static int easystd_parse_shape_pass2(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	/* pass 2 deals with delayed object creation only */
	switch(nd->name) {
		case easy_netlabel: return easystd_parse_netlabel(ctx, parent, nd);
	}

	return 0;
}

static int easystd_parse_shape(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	switch(ctx->pass) {
		case 1: return easystd_parse_shape_pass1(ctx, parent, nd);
		case 2: return easystd_parse_shape_pass2(ctx, parent, nd);
		default: abort();
	}
	return -1;
}

/* Draw all shapes of a sheet, single pass (call it twice with ctx->pass 1 and 2 */
static int easystd_parse_shapes(read_ctx_t *ctx, csch_cgrp_t *parent, gdom_node_t *nd)
{
	if (nd != NULL) {
		long n;

		if (nd->type != GDOM_ARRAY) {
			error_at(ctx, nd, ("schematics/shape or symbol/shape is not an array\n"));
			return -1;
		}

		for(n = 0; n < nd->value.array.used; n++)
			if (easystd_parse_shape(ctx, parent, nd->value.array.child[n]) != 0)
				return -1;

	}
	else
		warn_at(ctx, ctx->root, ("schematics or symbol without any shape\n"));

	return 0;
}

/* Parse sheet_root into ctx->sheet */
static int easystd_parse_sheet(read_ctx_t *ctx, gdom_node_t *sheet_root)
{
	gdom_node_t *nd, *data_str;
	int res;

/*	rnd_trace("LOAD SHEET\n");*/

	/* verify docType */
	HASH_GET_SUBTREE(nd,   sheet_root, easy_docType, GDOM_STRING,   return -1);
	if (strcmp(nd->value.str, "1") != 0) {
		error_at(ctx, nd, ("Invalid docType; expected '1', got '%s'", nd->value.str));
		return -1;
	}

	nd = gdom_hash_get(sheet_root, easy_description);
	if (easyeda_parse_attr_str(ctx, &ctx->sheet->direct.attr, nd, "description") != 0)
		return -1;

	/* pick up sheet level metadata */
	nd = gdom_hash_get(sheet_root, easy_title);
	if (easyeda_parse_attr_str(ctx, &ctx->sheet->direct.attr, nd, "title") != 0)
		return -1;

	/* set sheet title for the sheet selector */
	ctx->sheet->hidlib.fullpath = rnd_strdup_printf("%s_%s", ctx->fn, nd->value.str);
	ctx->sheet->hidlib.loadname = rnd_strdup(nd->value.str);

	/* load data */
	HASH_GET_SUBTREE(data_str,   sheet_root, easy_dataStr, GDOM_HASH,   return -1);

	/* load shapes */
	nd = gdom_hash_get(data_str, easy_shape);

	ctx->pass = 1;
	res = easystd_parse_shapes(ctx, &ctx->sheet->direct, nd);
	if (res != 0)
		return res;

	ctx->pass = 2;
	res = easystd_parse_shapes(ctx, &ctx->sheet->direct, nd);
	return res;
}

/* parse symbol from sym_root into resgrp */
static csch_cgrp_t *easystd_parse_grp_(read_ctx_t *ctx, gdom_node_t *sym_root, csch_cgrp_t *resgrp)
{
	int rv = 0;
	gdom_node_t *shapes, *head, *cpara;
	htip_entry_t *e;

	/* load graphics */
	HASH_GET_SUBTREE(shapes, sym_root, easy_shape, GDOM_ARRAY, rv = 1;);
	if (rv == 0) {
		ctx->pass = 1;
		rv = easystd_parse_shapes(ctx, resgrp, shapes);
	}
	if (rv == 0) {
		ctx->pass = 2;
		rv = easystd_parse_shapes(ctx, resgrp, shapes);
	}

	/* load metadata: header/c_para (attrs) */
	if (rv == 0)
		HASH_GET_SUBTREE(head, sym_root, easy_head, GDOM_HASH, rv = 1;);
	if (rv == 0)
		HASH_GET_SUBTREE(cpara, head, easy_c_para, GDOM_HASH, rv = 1;);
	for(e = htip_first(&cpara->value.hash); e != NULL; e = htip_next(&cpara->value.hash, e)) {
		if (e->key >= 0) {
			const char *key = easy_keyname(e->key);
			gdom_node_t *val = e->value;
			if ((val->type == GDOM_STRING) && (val->value.str != NULL) && (*val->value.str != '\0')) {
				csch_source_arg_t *src;
				char *ekey = rnd_concat("EasyEDA::", key, NULL);

				src = easyeda_attrib_src_c(ctx, val, NULL);
				csch_attrib_set(&resgrp->attr, CSCH_ATP_USER_DEFAULT, ekey, rnd_strdup(val->value.str), src, NULL);
			}
		}
	}

	if (rv == 0) {
		csch_cgrp_update(ctx->sheet, resgrp, 1);
		csch_sheet_bbox_update(ctx->sheet);
	}
	else {
		csch_cgrp_free(resgrp);
		resgrp = NULL;
	}

	return resgrp;
}


static csch_cgrp_t *easystd_parse_grp(read_ctx_t *ctx, gdom_node_t *sym_root)
{
	csch_source_arg_t *src;
	csch_cgrp_t *resgrp = NULL;

	/* create the symbol group */
	resgrp = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct, csch_oid_new(ctx->sheet, &ctx->sheet->direct));
	src = csch_attrib_src_c(ctx->fn, 0, 0, NULL); /* whole-file context, no need to set location */
	csch_cobj_attrib_set(ctx->sheet, resgrp, CSCH_ATP_HARDWIRED, "role", "symbol", src);

	return easystd_parse_grp_(ctx, sym_root, resgrp);
}

/* Copy only standard, symbol-related pen names from the default sheet */
static int sym_as_sheet_chk_copy_pen(void *udata, csch_sheet_t *dst, csch_cpen_t *p)
{
	if (strncmp(p->name.str, "busterm-", 8) == 0) return 1;
	if (strncmp(p->name.str, "term-", 5) == 0) return 1;
	if (strncmp(p->name.str, "sym-", 4) == 0) return 1;
	return 0;
}

static int easystd_load_sym_as_sheet(FILE *f, const char *fn, csch_sheet_t *sheet)
{
	read_ctx_t ctx = {0};
	csch_cgrp_t *resgrp;
	int save;

	/* ctx->sheet->indirect and ctx->sheet->direct are initialized and empty */
	ctx.f = f;
	ctx.fn = fn;
	ctx.sheet = sheet;
	ctx.root = easystd_low_parse(f, 1);
	if (ctx.root == NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': low level 'std' parser failed\n", fn);
		return -1;
	}

	alien_setup(&ctx);

	/* need to emulate a symbol parent for pen lookup to use symbol-decor */
	save = sheet->direct.role;
	sheet->direct.role = CSCH_ROLE_SYMBOL;

	resgrp = easystd_parse_grp_(&ctx, ctx.root, &sheet->direct);

	sheet->direct.role = save;

	if (io_easyeda_postproc(&ctx, 0) != 0)
		rnd_message(RND_MSG_ERROR, "io_easyeda: failed to postprocess newly loaded symbol\n");

	if (ctx.root != NULL) {
		gdom_free(ctx.root);
		ctx.root = NULL;
	}

	if (resgrp != NULL) {
		int res;

		sch_rnd_sheet_setup(sheet, SCH_RND_SSC_PENS | SCH_RND_SSC_PEN_MARK_DEFAULT, sym_as_sheet_chk_copy_pen, NULL);
		sheet->is_symbol = 1;

		res = io_easyeda_postproc(&ctx, 1);
		if ((res == 0) && io_easyeda_conf.plugins.io_easyeda.auto_normalize)
			csch_alien_postproc_normalize(&ctx.alien);

		return 0;
	}

	return -1;
}

/* doctype is an integer that identifies what the json is about */
static int easystd_get_doctype(char *stray, FILE *f, char *buf, long buf_len)
{
	char *line = stray, *end;
	int n, res;

	/* don't re-read this line */
	*buf = '\0';

	for(n = 0; n < 32; n++) {
		while(isspace(*line)) line++;
		if (*line == '\0') goto next;
		if (*line == ':') line++;
		while(isspace(*line)) line++;
		if (*line == '\0') goto next;
		if (*line == '"') line++;
		res = strtol(line, &end, 10);
		while(isspace(*end)) end++;
		if (*end != '"')
			return -1;
		return res;

		next:;
		line = fgets(buf, sizeof(buf), f);
		if (line == NULL)
			return -1;
	}

	return -1;
}

/* similar to fgets: get a logical line of json (approximation): break
   at newline or comma */
static char * test_parse_getline(char *dst, long maxlen, FILE *f)
{
	char *s;
	for(s = dst; maxlen > 1; maxlen--,s++) {
		int c = fgetc(f);
		if (c == EOF) {
			if (s == dst)
				return NULL;
			break;
		}
		if ((c == ',') || (c == '\n') || (c == '\r'))
			break;
		*s = c;
	}
	*s = '\0';
	return dst;
}

/* verify easyeda json header */
static int io_easystd_test_parse_(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type, int *is_sym)
{
	char buf[1024], *line;
	int n, doctype;
	unsigned found = 0;

	*is_sym = 0;

	/* ensure all vital header fields are present in the first 32 lines */
	for(n = 0; n < 32; n++) {
		line = test_parse_getline(buf, sizeof(buf), f);
		if (line == NULL)
			return -1;

		got_line:;
		while(isspace(*line)) line++;
		if (*line != '"') continue;
		line++;

		if (strncmp(line, "editorVersion\"", 14) == 0)
			found |= 1; /* generic easyeda */

		if (strncmp(line, "docType\"", 8) == 0) {
			found |= 2;        /* generic easyeda */
			doctype = easystd_get_doctype(line + 8, f, line, sizeof(line));
			if ((doctype == 2) && ((type == CSCH_IOTYP_SHEET) || (type == CSCH_IOTYP_GROUP))) {
				*is_sym = 1;
				found |= 4;    /* open symbols in sym edit mode or load from lib */
			}

			if (*line != '\0')
				goto got_line;
		}

		if (strncmp(line, "schematics\"", 11) == 0) {
			if (type == CSCH_IOTYP_SHEET)
				found |= 4;    /* for shcematics pages */
			else
				return -1;
		}

		if (found == (1|2|4))
			return 0;
	}

	return -1;
}

/* wrap HASH_GET_SUBTREE so that ctx is a ptr */
static gdom_node_t *get_root_sch(read_ctx_t *ctx)
{
	gdom_node_t *root_sch;

	HASH_GET_SUBTREE(root_sch, ctx->root, easy_schematics, GDOM_ARRAY, NULL);

	return root_sch;
}


/*** entry ***/

typedef struct {
	gdom_node_t *root;
	int sheet_idx;
	unsigned sym_as_sheet:1;
} easystd_bundle_t;

int io_easystd_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	int is_sym;
	return io_easystd_test_parse_(f, fn, fmt, type, &is_sym);
}

/* IO API function (load symbol from lib) */
csch_cgrp_t *io_easystd_load_grp(FILE *f, const char *fn, const char *fmt, const char *subsymname, csch_sheet_t *sheet)
{
	read_ctx_t ctx = {0};
	csch_cgrp_t *grp;

	if (htip_get(&sheet->direct.id2obj, 1) != NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': there's already a group1 in destination sheet\n", fn);
		return NULL;
	}

	ctx.f = f;
	ctx.fn = fn;
	ctx.sheet = sheet;
	ctx.root = easystd_low_parse(f, 1);
	if (ctx.root == NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': low level 'std' parser failed\n", fn);
		return NULL;
	}

	alien_setup(&ctx);

	grp = easystd_parse_grp(&ctx, ctx.root);
	if (io_easyeda_postproc(&ctx, 0) != 0)
		rnd_message(RND_MSG_ERROR, "io_easyeda: failed to postprocess newly loaded symbol\n");

	if (ctx.root != NULL) {
		gdom_free(ctx.root);
		ctx.root = NULL;
	}

	return grp;
}


/* load a single sheet from f/fn into preallocated empty sheet dst;
   fmt is an optional hint on the format (safe to ignore). Return 0
   on success */
int io_easystd_load_sheet_bundled(void *cookie, FILE *f, const char *fn, csch_sheet_t *dst)
{
	int res = -1;
	gdom_node_t *root_sch;
	easystd_bundle_t *bnd = cookie;
	read_ctx_t ctx = {0};

	if (bnd->sym_as_sheet) {
		res = easystd_load_sym_as_sheet(f, fn, dst);
		return res == 0 ? 1 : -1;
	}

	ctx.f = f;
	ctx.fn = fn;
	ctx.sheet = ctx.alien.sheet = dst;
	ctx.root = bnd->root;

	alien_setup(&ctx);
	csch_alien_sheet_setup(&ctx.alien, 1);

	root_sch = get_root_sch(&ctx);
	if (root_sch->value.array.used == 0)
		return 0;

	/* load the next sheet - only the std sheet format is supported */
	res = easystd_parse_sheet(&ctx, root_sch->value.array.child[bnd->sheet_idx]);
	if (res == 0)
		res = io_easyeda_postproc(&ctx, 1);

	if ((res == 0) && io_easyeda_conf.plugins.io_easyeda.auto_normalize)
		csch_alien_postproc_normalize(&ctx.alien);

	if (res == 0)
		csch_alien_update_conns(&ctx.alien);

	if (res != 0)
		return -1;

	dst->changed = 0;

	bnd->sheet_idx++;
	if (bnd->sheet_idx >= root_sch->value.array.used)
		return 1; /* no more */
	return 0; /* go on loading the next sheet */
}

void *io_easystd_test_parse_bundled(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	int is_sym, res = io_easystd_test_parse_(f, fn, fmt, type, &is_sym);

	if ((res == 0) && is_sym) {
		easystd_bundle_t *bnd = calloc(sizeof(easystd_bundle_t), 1);
		rewind(f);
		bnd->sym_as_sheet = 1;
		return bnd;
	}

	if ((res == 0) && !is_sym) {
		easystd_bundle_t *bnd = calloc(sizeof(easystd_bundle_t), 1);

		rewind(f);
		bnd->root = easystd_low_parse(f, 0);
		if (bnd->root == NULL)
			goto quit;

		if (bnd->root->type != GDOM_HASH) {
			rnd_message(RND_MSG_ERROR, "io_easyeda: root node must be a hash\n");
			quit:;
			if (bnd->root != NULL)
				gdom_free(bnd->root);
			free(bnd);
			return NULL;
		}

		return bnd;
	}

	return NULL;
}


void io_easystd_end_bundled(void *cookie, const char *fn)
{
	easystd_bundle_t *bnd = cookie;

	if (bnd->root != NULL)
		gdom_free(bnd->root);
	free(bnd);
}

