/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018,2019,2022..2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022, Entrust in 2023)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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
 */

#include "config.h"

#include <stdio.h>

#include <librnd/core/compat_misc.h>

#include "abstract.h"
#include "actions_csch.h"
#include "concrete.h"
#include "event.h"
#include "cnc_line.h"
#include "cnc_arc.h"
#include "cnc_poly.h"
#include "cnc_text.h"
#include "cnc_bitmap.h"
#include "cnc_conn.h"
#include "cnc_grp.h"
#include "cnc_pen.h"
#include "cnc_obj.h"
#include "engine.h"
#include "project.h"
#include "util_compile.h"
#include "util_project.h"
#include "libcschem.h"
#include "non_graphical.h"
#include "hierarchy.h"

#include "compile.h"

int csch_compile_attribute(csch_ahdr_t *dst, const char *dstkey, const csch_attrib_t *srca, csch_source_arg_t *src, const char *err1, const char *err2, const char *err3, int referee, int append, csch_attrib_t **dsta_out)
{
	csch_attrib_t *dsta = NULL;
	const char *key = (dstkey == NULL ? srca->key : dstkey);
	const char *sval, *dval;

	if (err1 == NULL) err1 = "";
	if (err2 == NULL) err2 = "";
	if (err3 == NULL) err3 = "";

	if (key[0] == '-') {
		if (dsta_out != NULL) *dsta_out = NULL;
		csch_attr_src_free(src);
		return 0;
	}

	if (key[0] == '+') {
		key++;
		append = 1;
	}

	dsta = csch_attrib_get(&dst->attr, key);
	if (dsta_out != NULL) *dsta_out = dsta;

	sval = srca == NULL ? NULL : srca->val;
	dval = dsta == NULL ? NULL : dsta->val;

	/* Hierarchic special casing: remove hierarchic prefix from the name
	   attribute so it doesn't cause conflicts, e.g. when a v/foo is merged with
	   a matching ^/foo */
	if ((key[0] == 'n') && (key[1] == 'a') && (key[2] == 'm') && (key[3] == 'e') && (key[4] == '\0')) {
		if (sval != NULL)
			csch_split_name_scope(&sval);
		if (dval != NULL)
			csch_split_name_scope(&dval);
	}

	/* attribute merging logic */
	if (sval != NULL) {
		if (append)
			return csch_attrib_append_val(&dst->attr, srca->prio, key, srca, src);
		if (dsta != NULL) {
			if (dval != NULL) {
				if (strcmp(sval, dval) == 0) {
					/* both set, same value, remember lowest prio */
					if (srca->prio >= dsta->prio) {
						csch_attrib_append_src(dsta, srca->prio, src, 1);
						csch_attr_src_free(src);
						return 0; /* ignore new */
					}
				}
				else {
					/* different values, look at prios */
					if (srca->prio > dsta->prio) {
						csch_attrib_append_src(dsta, srca->prio, src, 1);
						csch_attr_src_free(src);
						return 0; /* lower prio, ignore */
					}
					if (srca->prio == dsta->prio) {
						if (!referee) /* it's okay if a group ref's referee had it - we are overwriting it from the group ref */
							rnd_message(RND_MSG_ERROR, "compile_attributes(): %s %s %s: attribute collision for '%s'\n", err1, err2, err3, srca->key);
						csch_attr_src_free(src);
						return -1;
					}
					/* else set */
				}
			}
		}
		csch_attrib_set(&dst->attr, srca->prio, key, sval, src, dsta_out);
	}
	else {
		if (append) {
			if ((dsta != NULL) && (dval != NULL)) {
				csch_attr_src_free(src);
				rnd_message(RND_MSG_ERROR, "compile_attributes(): %s %s %s: can not append to destination attribute '%s', it is a string not an array\n", err1, err2, err3, key);
				return -1;
			}
			return csch_attrib_append_val(&dst->attr, srca->prio, key, srca, src);
		}
		else
			csch_attrib_set_arr(&dst->attr, srca->prio, key, &srca->arr, src, dsta_out);
	}

	return 0;
}

/* Copy all sources from srca in front of the sources in dst */
static void compile_attribute_copy_srcs_front(csch_attrib_t *dst, const csch_attrib_t *srca)
{
	long n;

	if (srca->source.used <= 0)
		return;

	vts0_alloc_insert(&dst->source, 0, srca->source.used);
	for(n = 0; n < srca->source.used; n++)
		dst->source.array[n] = rnd_strdup(srca->source.array[n]);
}

static int compile_attributes(csch_ahdr_t *dst, const csch_attribs_t *src, csch_cgrp_t *cgrp, const char *err1, const char *err2, const char *err3, int referee)
{
	htsp_entry_t *e;
	int res = 0, r1;

	for(e = htsp_first(src); e; e = htsp_next(src, e)) {
		const csch_attrib_t *srca = e->value;
		csch_source_arg_t *src = csch_attrib_src_ac(&cgrp->hdr, srca->key, "compile_attributes()");
		csch_attrib_t *dsta;

		r1 = csch_compile_attribute(dst, NULL, srca, src, err1, err2, err3, referee, 0, &dsta);

		if ((r1 == 0) && (dsta != NULL))
			compile_attribute_copy_srcs_front(dsta, srca);

		res |= r1;
	}

	TODO("this should return res");
	return 0;
}

static int compile_wire_net(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, const csch_sheet_t *sheet, csch_cgrp_t *src)
{
	char tmpname[128];
	const char *orig_name;
	char *name_glob = NULL, *name_loc = NULL;
	const csch_attrib_t *aname;
	csch_anet_t *net;
	int no_uname = 0;
	csch_ascope_t scope;

	aname = csch_attrib_get(&src->attr, "name");
	dst->ucnt.wirenet++;
	if ((aname == NULL) || (aname->key == NULL) || (*aname->key == '\0')) {
		sprintf(tmpname, "anon_net_%ld", dst->ucnt.wirenet);
		orig_name = tmpname;
		no_uname = 1;
	}
	else
		orig_name = aname->val;

 scope = csch_split_name_scope(&orig_name);

	/* Translate name, including hierarchical address prefixes */
	csch_eng_call_namemod((csch_project_t *)sheet->hidlib.project, viewid, hpath, CSCH_ENGHK_WIRENET_NAME_TO_NET_NAME,
		&name_glob, &name_loc, orig_name, FGW_COBJ, src, FGW_HPATH, hpath, FGW_INVALID);

	if (name_glob == NULL) {
		rnd_message(RND_MSG_ERROR, "Internal error: failed to perform wire_net name translation\n");
		return -1;
	}

	net = csch_anet_get_at(dst, hpath->hlev, scope, name_loc);
	if (net == NULL) {
		net = csch_anet_new(dst, hpath->hlev, scope, name_glob, name_loc, no_uname);
		if (net == NULL) {
			csch_eng_free_namemod(&name_glob, &name_loc);
			return -1;
		}
	}
	csch_eng_free_namemod(&name_glob, &name_loc);

	csch_compile_add_source(src, &net->hdr);
	return compile_attributes(&net->hdr, &src->attr, src, "net", orig_name, NULL, 0);
}

static csch_aport_t *compile_port(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, const csch_sheet_t *sheet, csch_acomp_t *comp, csch_cgrp_t *t, int hier_down)
{
	char tmpname[128];
	const csch_attrib_t *trole, *tname;
	const char *orig_name;
	char *name_glob = NULL, *name_loc = NULL;
	csch_aport_t *port = NULL;

	if ((t->hdr.type != CSCH_CTYPE_GRP) && (t->hdr.type != CSCH_CTYPE_GRP_REF))
		return NULL;

	if (t->hdr.type == CSCH_CTYPE_GRP_REF)
		trole = csch_cgrp_ref_get_attr(t, "role");
	else
		trole = csch_attrib_get(&t->attr, "role");
	if ((trole == NULL) || (trole->val == NULL) || (strcmp(trole->val, "terminal") != 0))
		return NULL;

	if (t->hdr.type == CSCH_CTYPE_GRP_REF)
		tname = csch_cgrp_ref_get_attr(t, "name");
	else
		tname = csch_attrib_get(&t->attr, "name");

	dst->ucnt.port++;

	if ((tname == NULL) || (tname->key == NULL) || (*tname->key == '\0')) {
		sprintf(tmpname, "anon_%ld", dst->ucnt.port);
		orig_name = tmpname;
	}
	else
		orig_name = tname->val;

	csch_eng_call_namemod((csch_project_t *)sheet->hidlib.project, viewid, hpath, CSCH_ENGHK_TERMINAL_NAME_TO_PORT_NAME,
		&name_glob, &name_loc, orig_name, FGW_AOBJ, comp, FGW_COBJ, t, FGW_HPATH, hpath, FGW_INVALID);

	if (name_glob == NULL) {
		rnd_message(RND_MSG_ERROR, "Internal error: failed to perform port name translation\n");
		return NULL;
	}

	port = csch_aport_get(dst, comp, name_glob, 1);
	if (port == NULL)
		return NULL;

	port->parent = comp;
	csch_compile_add_source(t, &port->hdr);

	compile_attributes(&port->hdr, &t->attr, t, "port", (comp == NULL ? "" : comp->name), orig_name, 0);

	csch_eng_free_namemod(&name_glob, &name_loc);

	return port;
}

/* Compile a normal (non-sheet-ref) symbol into a component; */
static int compile_symbol_to_component(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, csch_ascope_t scope, const csch_sheet_t *sheet, csch_cgrp_t *src, char *name_glob, char *name_loc, const char *orig_name)
{
	htip_entry_t *e;
	csch_acomp_t *comp;
	int res = 0;

	/* don't compile if it had no name - note: at the moment this can't happen (because of anon_comp_%ld) */
	if (orig_name == NULL)
		return 0;

	comp = csch_acomp_get_at(dst, hpath->hlev, scope, name_loc);
	if (comp == NULL)
		comp = csch_acomp_new(dst, hpath->hlev, scope, name_glob, name_loc);
	
	if (comp == NULL)
		return -1;

	csch_compile_add_source(src, &comp->hdr);

	res |= compile_attributes(&comp->hdr, &src->attr, src, "component", orig_name, NULL, 0);
	if ((src->hdr.type == CSCH_CTYPE_GRP_REF) && (src->data.ref.grp != NULL))
		res |= compile_attributes(&comp->hdr, &src->data.ref.grp->attr, src->data.ref.grp, "component", orig_name, NULL, 1);

	csch_eng_call((csch_project_t *)sheet->hidlib.project, viewid, hpath, CSCH_ENGHK_SYMBOL_JOINED_COMPONENT,
		FGW_COBJ, src, FGW_AOBJ, comp, FGW_HPATH, hpath, FGW_INVALID);

	/* compile ports */
	for(e = htip_first(&src->id2obj); e != NULL; e = htip_next(&src->id2obj, e))
		compile_port(dst, viewid, hpath, sheet, comp, e->value, 0);

	return res;
}

/* Return is grp is directly on the sheet and not within a symbol on any level */
static int is_grp_a_sheet_port(const csch_sheet_t *sheet, csch_cgrp_t *grp)
{
	csch_cgrp_t *g;

	for(g = grp->hdr.parent; g != NULL; g = g->hdr.parent) {
		if (g == &sheet->direct)
			return 1;
		if (g->role == CSCH_ROLE_SYMBOL)
			return 0;
	}

	return 1;
}

/* Hierarchic: search child sheet terminals and return the abstract port that
   has a matching port name (or NULL if not found or error). Also sets
   *error_indicated_out to 1 if an error message was printed. */
static csch_aport_t *find_subsheet_port(csch_abstract_t *dst, csch_sheet_t *child, const char *port_name, int *error_indicated_out)
{
	csch_chdr_t *h;
	csch_aport_t *res = NULL;

	for(h = gdl_first(&child->active); h != NULL; h = gdl_next(&child->active, h)) {
		csch_cgrp_t *grp = (csch_cgrp_t *)h;

		if (h->indirect)
			continue;

		if ((h->type != CSCH_CTYPE_GRP) && (h->type != CSCH_CTYPE_GRP_REF))
			continue;

		/* can use enum role here because role attribute overwrite is handled in csch_cgrp_ref_render() */
		if (grp->role == CSCH_ROLE_TERMINAL) {
			if (is_grp_a_sheet_port(child, grp)) {
				long n;
				for(n = 0; n < grp->aid.used; n++) {
					csch_aport_t *aport = htip_get(&dst->aid2obj, grp->aid.array[n]);
					if ((aport != NULL) && (aport->hdr.type == CSCH_ATYPE_PORT)) {
						if (strcmp(aport->name, port_name) == 0) {
							if (res != NULL) {
								rnd_msg_error("Compilation error: sheet '%s': multiple sheet terminals are called '%s'\n", child->hidlib.loadname, port_name);
								*error_indicated_out = 1;
								return NULL;
							}
							res = aport;
							/*go on searching for more so the above error can trigger */
						}
					}
				}
			}
		}
	}

	return res;
}

/* Returns whether a sheet ref sym has parameter pass-down attributes */
static int srs_has_params(csch_cgrp_t *srs)
{
	htsp_entry_t *e;

	for(e = htsp_first(&srs->attr); e != NULL; e = htsp_next(&srs->attr, e))
		if (strncmp(e->key, "cschem/param/", 13) == 0)
			return 1;

	return 0;
}

int csch_compile_descend(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, csch_cgrp_t *srs, char *name_loc, csch_sheet_t *child)
{
	int res, restore_attrs = 0;
	csch_hier_temp_t htmp;
	csch_attribs_t aorig;

	/* pass down parameters by temporary switching child to a new attribute hash */
	if (srs_has_params(srs)) {
		htsp_entry_t *e, *old;

		/* clone orig attrs */
		memcpy(&aorig, &child->direct.attr, sizeof(csch_attribs_t));
		csch_attrib_init(&child->direct.attr);
		csch_attrib_copy_all(&child->direct.attr, &aorig);
		restore_attrs = 1;

		/* copy the new ones */
		for(e = htsp_first(&srs->attr); e != NULL; e = htsp_next(&srs->attr, e)) {
			if (strncmp(e->key, "cschem/param/", 13) == 0) {
				const char *new_key = e->key+13;
				csch_attrib_t *a = csch_attrib_dup_rename(e->value, new_key);

				old = htsp_popentry(&child->direct.attr, new_key);
				if (old != NULL)
					csch_attr_free(e->value);

				htsp_set(&child->direct.attr, a->key, a);
			}
		}
	}

	res = csch_hier_enter(hpath, &htmp, srs, name_loc);
	if (res != 0) return -1;
	res = csch_compile_sheet_(dst, viewid, hpath, child);
	csch_hier_leave(hpath, &htmp);

	/* restore child's old attribute hash (undo parameter passing) */
	if (restore_attrs) {
		csch_attrib_uninit(&child->direct.attr);
		memcpy(&child->direct.attr, &aorig, sizeof(csch_attribs_t));
	}

	return res;
}

/* Compile a normal (non-sheet-ref) symbol into a component; use name_tmp
   if available, else use name_ */
static int compile_symbol_child_sheet(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, csch_ascope_t scope, const csch_sheet_t *sheet, csch_cgrp_t *src, char *name_glob, char *name_loc, const char *orig_name, csch_sheet_t *child)
{
	int res;
	csch_acomp_t *comp;
	htip_entry_t *e;

	if (orig_name == NULL) {
		rnd_message(RND_MSG_ERROR, "Sheet reference symbol in hierarchic design must have a name\n");
		return -1; /* comp has no name -> error - can't happen because of anon_comp_%ld  */
	}

	comp = csch_acomp_get(dst, name_glob);
	if (comp == NULL) {
		comp = csch_acomp_new(dst, hpath->hlev, CSCH_ASCOPE_SHEET_LOCAL, name_glob, name_loc);

		res = csch_compile_descend(dst, viewid, hpath, src, name_loc, child);

		comp->child_sheet = child;
		comp->hdr.omit = 1;
	}
	else
		res = 0;

	/* Bind sheet ref sym terminals to child sheet terminals */
	for(e = htip_first(&src->id2obj); e != NULL; e = htip_next(&src->id2obj, e)) {
		csch_aport_t *sym_port;
		const char *sym_port_api_name;

		sym_port = compile_port(dst, viewid, hpath, sheet, comp, e->value, 1);

		if (sym_port != NULL) {
			int error_indicated = 0;

			sym_port_api_name = sym_port->name;
			sym_port->subsheet_port = find_subsheet_port(dst, child, sym_port_api_name, &error_indicated);

			if (sym_port->subsheet_port == NULL) {
				if (!error_indicated)
					rnd_message(RND_MSG_ERROR,
						"Hierarchy error: sheet %s doesn't have terminal %s for symbol port %s:%s\n",
						child->hidlib.loadname, sym_port_api_name, comp->name, sym_port_api_name);
				res = -1;
			}
			else
				sym_port->subsheet_port->referer = comp;
			sym_port->hdr.omit = 1;
		}
	}

	return res;
}

static int compile_symbol(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, const csch_sheet_t *sheet, csch_cgrp_t *src, rnd_bool do_sheet_ref_syms)
{
	char tmpname[128];
	const char *orig_name;
	char *name_glob = NULL, *name_loc = NULL;
	const csch_attrib_t *aname = NULL;
	csch_sheet_t *child = NULL;
	int hres, cres;
	csch_ascope_t scope = CSCH_ASCOPE_unknown;

	hres = csch_hier_find_sym_child(sheet, src, &child, 0);
	if (hres < 0)
		return hres;

	/* we are called in two passes, first doing non-sheet-refs then sheet refs */
	if ((hres == 0) && do_sheet_ref_syms)
		return 0;
	if ((hres == 1) && !do_sheet_ref_syms)
		return 0;

	if (src->hdr.type == CSCH_CTYPE_GRP_REF)
		aname = csch_cgrp_ref_get_attr(src, "name");
	else
		aname = csch_attrib_get(&src->attr, "name");


	dst->ucnt.comp++;
	if ((aname == NULL) || (aname->val == NULL) || (*aname->val == '\0') || (aname->key == NULL) || (*aname->key == '\0')) {
		sprintf(tmpname, "anon_comp_%ld", dst->ucnt.comp);
		orig_name = tmpname;
	}
	else {
		orig_name = aname->val;
		scope = csch_split_name_scope(&orig_name);
	}

	csch_eng_call_namemod((csch_project_t *)sheet->hidlib.project, viewid, hpath, CSCH_ENGHK_SYMBOL_NAME_TO_COMPONENT_NAME,
		&name_glob, &name_loc, orig_name, FGW_COBJ, src, FGW_HPATH, hpath, FGW_INVALID);

	if (name_glob == NULL) {
		csch_eng_free_namemod(&name_glob, &name_loc);
		rnd_message(RND_MSG_ERROR, "Internal error: failed to perform symbol name translation\n");
		return -1;
	}


	if (hres == 1)
		cres = compile_symbol_child_sheet(dst, viewid, hpath, scope, sheet, src, name_glob, name_loc, orig_name, child);
	else
		cres = compile_symbol_to_component(dst, viewid, hpath, scope, sheet, src, name_glob, name_loc, orig_name);

	csch_eng_free_namemod(&name_glob, &name_loc);

	return cres;
}

int csch_compile_connect_net_to(csch_anet_t **net, csch_ahdr_t *a, int allow_reconn)
{
	if (a->type == CSCH_ATYPE_PORT) {
		csch_aport_t *ap = (csch_aport_t *)a;
		csch_anet_t *new_net;

		if (ap->sheet_port) {
/*			rnd_trace(" conn sheet port '%s' to net '%s'!\n", ap->name, (*net)->name);*/
			ap->hdr.omit = 1;
			/* go on so that sheet local connections are made */
		}

		if ((ap->parent != NULL) && (ap->parent->child_sheet != NULL)) {
/*			rnd_trace(" conn ref sym port '%s' to net '%s'!\n", ap->name, (*net)->name);*/
			if ((ap->subsheet_port != NULL) && (ap->subsheet_port->conn.net)) {
/*				rnd_trace("  connect to subsheet port's net %s\n", ap->subsheet_port->conn.net->name);*/
				new_net = csch_cmp_merge_nets(*net, ap->subsheet_port->conn.net);
				if (new_net == NULL) {
					rnd_message(RND_MSG_ERROR, "csch_compile_connect_net_to(): failed to merge nets %s and %s at port %s:%s for hierarchic connection down\n", (*net)->name, ap->subsheet_port->conn.net->name, APORT_PARENT_NAME(ap), ap->name);
					return -1;
				}
				*net = new_net;
			}
			/* go on so that sheet local connections are made */
		}

		if (ap->conn.net != NULL) {
			int allowed = 0;

			/* special case: resistor term overlap with vcc term and a wire is
			   also connected; this means we have a term-term net and a wirenet
			   and this shouldn't be considered as a reconn because the
			   term-term connection is really invisible */
			if (ap->conn.net->term_term || (*net)->term_term)
				allowed = 1;

			if (!allow_reconn && !allowed) {
				rnd_message(RND_MSG_ERROR, "csch_compile_connect_net_to(): port %s:%s already connected\n(This error is generated because multiport_net_merge is disabled in the config)\n", APORT_PARENT_NAME(ap), ap->name);
				return -1;
			}
			new_net = csch_cmp_merge_nets(*net, ap->conn.net);
			if (new_net == NULL) {
				rnd_message(RND_MSG_ERROR, "csch_compile_connect_net_to(): failed to merge nets %s and %s at port %s:%s\n", (*net)->name, ap->conn.net->name, APORT_PARENT_NAME(ap), ap->name);
				return -1;
			}
			*net = new_net;
		}
		ap->conn.net = *net;

		TODO("bus: do the same for buses and bus ports? Or do we put nets in buses?");
		vtp0_append(&(*net)->conns, a);
		return 0;
	}

	rnd_message(RND_MSG_ERROR, "csch_compile_connect_net_to(): unsupported object type\n");
	return -1;
}

int csch_compile_disconnect(csch_ahdr_t *a)
{
	if (a->type == CSCH_ATYPE_PORT) {
		csch_aport_t *ap = (csch_aport_t *)a;
		csch_anet_t *net = ap->conn.net;
		long n;

		if (net == NULL)
			return 0; /* already disconnected */

		ap->conn.net = NULL;
		for(n = 0; n < net->conns.used; n++) {
			if (net->conns.array[n] == ap) {
				vtp0_remove(&net->conns, n, 1);
				break;
			}
		}

		return 0;
	}
	rnd_message(RND_MSG_ERROR, "csch_compile_disconnect(): unsupported object type\n");
	return -1;
}


static int compile_conn(csch_abstract_t *dst, const csch_sheet_t *sheet, csch_hier_path_t *hpath, const csch_conn_t *src)
{
	long n;
	csch_anet_t *net = NULL;

	if (src->conn.used < 2) {
		rnd_msg_error("Ignoring conn object with %d connection(s) - needs at least 2\n", src->conn.used);
		return -1;
	}


	/* first pass: find the net */
	for(n = 0; n < src->conn.used; n++) {
		const csch_chdr_t *obj = src->conn.array[n];
		const csch_cgrp_t *grp = obj->parent;
		csch_ahdr_t *a;

		TODO("remove this:");
		if (grp == NULL) continue;

		if ((grp->hdr.type != CSCH_CTYPE_GRP) && (grp->hdr.type != CSCH_CTYPE_GRP_REF))
			continue;

		if (grp->aid.used < 1) {
			rnd_msg_error("Invalid object on the conn list (empty); conn id: #%ld\n", src->hdr.oid);
			return -1;
		}

		a = htip_get(&dst->aid2obj, grp->aid.array[0]);
		if (a == NULL) {
			rnd_msg_error("Invalid object on the conn list (NULL); conn id: #%ld\n", src->hdr.oid);
			return -1;
		}
		switch(a->type) {
			case CSCH_ATYPE_NET:
				if ((net != NULL) && (&net->hdr != a)) {
					TODO("figure how to handle network collisions");
					rnd_msg_error("Network collision\n");
					return -1;
				}
				net = (csch_anet_t *)a;
				break;
			case CSCH_ATYPE_PORT:
				break;
			default:
				rnd_msg_error("Invalid object on the conn list (%s)\n", csch_atype_name(a->type));
				return -1;
		}
	}

 /* create an anon net if there was no wirenet in the conn list; this happens
    in a term-term connection */
	if (net == NULL) {
		char tmpname[128];

		sprintf(tmpname, "anon_net_%ld", ++dst->ucnt.wirenet);
		net = csch_anet_new(dst, NULL, CSCH_ASCOPE_GLOBAL, tmpname, tmpname, 1);
		if (net == NULL) {
			rnd_msg_error("failed to create anon net for terminal-terminal connection\n");
			return -1;
		}
		net->term_term = 1;
		net->hdepth = hpath->hdepth;
		net->hlev = hpath->hlev; /* even tho it won't be referenced because it's a globally unique name... */
	}

	/* second pass: make the connections */
	for(n = 0; n < src->conn.used; n++) {
		const csch_chdr_t *obj = src->conn.array[n];
		const csch_cgrp_t *grp = obj->parent;
		csch_ahdr_t *a;

		TODO("remove this:");
		if (grp == NULL) continue;

		if ((grp->hdr.type != CSCH_CTYPE_GRP) && (grp->hdr.type != CSCH_CTYPE_GRP_REF))
			continue;

		if (grp->aid.used < 1)
			continue;

		a = htip_get(&dst->aid2obj, grp->aid.array[0]);
		if (a == &net->hdr)
			continue;

		if (csch_compile_connect_net_to(&net, a, CSCH_CFG(multiport_net_merge, 0)) != 0)
			return -1;
	}
	return 0;
}

static void compile_reset_abstract(csch_abstract_t *dst)
{
	/* reset uniq name counters */
	dst->ucnt.wirenet = 0;
	dst->ucnt.comp = 0;
	dst->ucnt.port = 0;
	dst->ucnt.target_tmp = 0;
}

static void csch_reset_sheet_aids(const csch_sheet_t *src)
{
	const csch_chdr_t *h;

	for(h = gdl_first(&src->active); h != NULL; h = gdl_next(&src->active, h)) {
		if (csch_obj_is_grp(h)) {
			csch_cgrp_t *grp = (csch_cgrp_t *)h;
			grp->aid.used = 0;
		}
	}
}


int csch_compile_sheet_(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, const csch_sheet_t *src)
{
	int res = 0;
	const csch_chdr_t *h;

	if (src->non_graphical) {
		if ((src->non_graphical_impl == NULL) || (src->non_graphical_impl->compile_sheet == NULL)) {
			rnd_message(RND_MSG_ERROR, "Don't know how to compile non-graphical sheet %s\n", src->hidlib.fullpath);
			return -1;
		}
		return src->non_graphical_impl->compile_sheet(dst, viewid, hpath, src);
	}

	csch_reset_sheet_aids(src);

	/* compile nets and non-sheet-ref components */
	for(h = gdl_first(&src->active); h != NULL; h = gdl_next(&src->active, h)) {
		csch_cgrp_t *grp = (csch_cgrp_t *)h;

		if (h->indirect)
			continue;

		if ((h->type != CSCH_CTYPE_GRP) && (h->type != CSCH_CTYPE_GRP_REF))
			continue;

		/* can use enum role here because role attribute overwrite is handled in csch_cgrp_ref_render() */
		switch(grp->role) {
			case CSCH_ROLE_WIRE_NET:   res |= compile_wire_net(dst, viewid, hpath, src, grp); break;
			case CSCH_ROLE_SYMBOL:     res |= compile_symbol(dst, viewid, hpath, src, grp, 0); break;

			case CSCH_ROLE_TERMINAL:
				if (is_grp_a_sheet_port(src, grp)) {
					csch_aport_t *aport = compile_port(dst, viewid, hpath, src, NULL, grp, 0);
					csch_eng_call((csch_project_t *)src->hidlib.project, viewid, hpath, CSCH_ENGHK_COMPILE_PORT, FGW_AOBJ, aport, FGW_HPATH, hpath, FGW_INVALID);
					aport->sheet_port = 1;
/*					rnd_trace("Sheet port: prefix='%s' name='%s'\n", hpath->prefix.array, aport->name);*/
				}
				break;

			case CSCH_ROLE_invalid:
			case CSCH_ROLE_empty:
			case CSCH_ROLE_BUS_NET:
			case CSCH_ROLE_BUS_TERMINAL:
			case CSCH_ROLE_HUB_POINT:
			case CSCH_ROLE_JUNCTION:
			case CSCH_ROLE_EXTOBJ_GFX:
				break;
		}
	}

	/* compile sheet-ref components */
	for(h = gdl_first(&src->active); h != NULL; h = gdl_next(&src->active, h)) {
		csch_cgrp_t *grp = (csch_cgrp_t *)h;

		if (h->indirect)
			continue;

		if ((h->type != CSCH_CTYPE_GRP) && (h->type != CSCH_CTYPE_GRP_REF))
			continue;

		if (grp->role == CSCH_ROLE_SYMBOL)
			res |= compile_symbol(dst, viewid, hpath, src, grp, 1);
	}

	csch_eng_call((csch_project_t *)src->hidlib.project, viewid, hpath, CSCH_ENGHK_CONNS_BEFORE,
		FGW_PTR, dst, FGW_HPATH, hpath, FGW_INVALID);

	/* compile connections between nets and components (the "netlist" part) */
	for(h = gdl_first(&src->active); h != NULL; h = gdl_next(&src->active, h)) {
		const csch_conn_t *conn = (const csch_conn_t *)h;

		if (h->indirect)
			continue;

		if (h->type != CSCH_CTYPE_CONN)
			continue;
		res |= compile_conn(dst, src, hpath, conn);
	}

	csch_eng_call((csch_project_t *)src->hidlib.project, viewid, hpath, CSCH_ENGHK_CONNS_AFTER,
		FGW_PTR, dst, FGW_HPATH, hpath, FGW_INVALID);


	return res;
}

int csch_compile_sheet(csch_abstract_t *dst, int viewid, const csch_sheet_t *src)
{
	int res;
	csch_hier_path_t hpath;

	csch_hier_path_init(&hpath, dst);

	compile_reset_abstract(dst);

	res = csch_compile_sheet_(dst, viewid, &hpath, src);

	csch_hier_path_free(&hpath);
	return res;
}

/* Returns whether the loop should be restarted because of new components added */
RND_INLINE int csch_compile_post_port(csch_project_t *proj, int viewid, csch_hier_path_t *hpath, csch_abstract_t *dst, csch_aport_t *port)
{
	if (port->hdr.compiled) return 0;
	port->hdr.compiled = 1;

	csch_eng_call(proj, viewid, hpath, CSCH_ENGHK_COMPILE_PORT, FGW_AOBJ, port, FGW_INVALID);

	return dst->new_ports;
}


/* Returns whether the loop should be restarted because of new components added */
RND_INLINE int csch_compile_post_comp(csch_project_t *proj, int viewid, csch_hier_path_t *hpath, csch_abstract_t *dst, csch_acomp_t *comp)
{
	htsp_entry_t *p;

	if (comp->hdr.compiled) return 0;
	comp->hdr.compiled = 1;

	csch_eng_call(proj, viewid, hpath, CSCH_ENGHK_COMPILE_COMPONENT0, FGW_AOBJ, comp, FGW_INVALID);
	csch_compile_update_obj_cache(&comp->hdr);
	csch_eng_call(proj, viewid, hpath, CSCH_ENGHK_COMPILE_COMPONENT1, FGW_AOBJ, comp, FGW_INVALID);
	csch_compile_update_obj_cache(&comp->hdr);

	restart_port:;
	dst->new_ports = 0;
	for(p = htsp_first(&comp->ports); p != NULL; p = htsp_next(&comp->ports, p))
		if (csch_compile_post_port(proj, viewid, hpath, dst, p->value))
			goto restart_port; /* if new components are created restart the loop because genht gets confused */

	csch_eng_call(proj, viewid, hpath, CSCH_ENGHK_COMPILE_COMPONENT2, FGW_AOBJ, comp, FGW_INVALID);
	return dst->new_comps;
}

/* Returns whether the loop should be restarted because of new components added */
RND_INLINE int csch_compile_post_net(csch_project_t *proj, int viewid, csch_hier_path_t *hpath, csch_abstract_t *dst, csch_anet_t *net)
{
	if (net->hdr.compiled) return 0;
	net->hdr.compiled = 1;

	csch_eng_call(proj, viewid, hpath, CSCH_ENGHK_COMPILE_NET, FGW_AOBJ, net, FGW_INVALID);

	return dst->new_nets;
}

RND_INLINE void csch_compile_post_update_cache_(csch_ahdr_t *obj)
{
	const char *val;

	val = csch_attrib_get_str(&obj->attr, "display/dnp");
	if ((val != NULL) && (*val != '\0'))
		obj->dnp = 1;

	val = csch_attrib_get_str(&obj->attr, "display/omit");
	if ((val != NULL) && (*val != '\0'))
		obj->omit = 1;
}

void csch_compile_update_obj_cache(csch_ahdr_t *obj)
{
	csch_compile_post_update_cache_(obj);
}


/* Calculate all caches in the abstract model after the compilation has finished */
RND_INLINE void csch_compile_post_update_cache(csch_project_t *proj, int viewid, csch_abstract_t *abst)
{
	htsp_entry_t *n, *c, *p;

	for(n = htsp_first(&abst->nets); n != NULL; n = htsp_next(&abst->nets, n)) {
		csch_anet_t *net = n->value;
		csch_compile_post_update_cache_(&net->hdr);
	}


	for(c = htsp_first(&abst->comps); c != NULL; c = htsp_next(&abst->comps, c)) {
		csch_acomp_t *comp = c->value;
		csch_compile_post_update_cache_(&comp->hdr);
		for(p = htsp_first(&comp->ports); p != NULL; p = htsp_next(&comp->ports, p)) {
			csch_aport_t *port = p->value;
			csch_compile_post_update_cache_(&port->hdr);
		}
	}
}

static int cmp_comp_hdepth(const void *C1, const void *C2)
{
	const csch_acomp_t **c1 = (const csch_acomp_t **)C1;
	const csch_acomp_t **c2 = (const csch_acomp_t **)C2;

	/* secondary key: if depths are the same, sort by aid for reproducible output
	   (different libc implementations will behave differently if primary key
	   is equal) */
	if ((*c1)->hdepth == (*c2)->hdepth)
		return ((*c1)->hdr.aid > (*c2)->hdr.aid) ? +1 : -1;

	/* primary key: hdepth */
	if ((*c1)->hdepth > (*c2)->hdepth) return 1;
	return -1;
}

int csch_compile_post(csch_project_t *proj, int viewid, csch_abstract_t *dst)
{
	htsp_entry_t *e;
	csch_sheet_t *sheet;
	csch_hier_path_t *hpath = NULL; /* we have no path after compilation */
	vtp0_t lst = {0};
	long n;

	/* NOTE: have to sort objects and go from top to bottom in hierarchy to
	   make sure the top end of a v/foo reference is created before the bottom
	   end ^/foo (or just foo) is found */

	restart_comp:;
	lst.used = 0;
	dst->new_comps = 0;
	for(e = htsp_first(&dst->comps); e != NULL; e = htsp_next(&dst->comps, e)) {
		csch_acomp_t *acomp = e->value;
		if (!acomp->postprocessed)
			vtp0_append(&lst, acomp);
	}

	if (lst.used > 0) {
		qsort(lst.array, lst.used, sizeof(void *), cmp_comp_hdepth);

		for(n = 0; n < lst.used; n++) {
			csch_acomp_t *acomp = lst.array[n];
			acomp->postprocessed = 1;
			csch_compile_post_comp(proj, viewid, hpath, dst, acomp);
		}

		if (dst->new_comps)
			goto restart_comp;
	}

	lst.used = 0;


	restart_net:;
	dst->new_nets = 0;
	for(e = htsp_first(&dst->nets); e != NULL; e = htsp_next(&dst->nets, e))
		if (csch_compile_post_net(proj, viewid, hpath, dst, e->value))
			goto restart_net; /* if new components are created restart the loop because genht gets confused */

	vtp0_uninit(&lst);

	csch_text_invalidate_all_project(proj, 1);

	csch_eng_call(proj, viewid, NULL, CSCH_ENGHK_PROJECT_AFTER,
		FGW_PTR, dst, FGW_PTR, proj, FGW_INVALID);

	csch_compile_post_update_cache(proj, viewid, dst);

	sheet = *vtp0_get(&proj->hdr.designs, 0, 0);
	rnd_event(&sheet->hidlib, CSCH_EVENT_PRJ_COMPILED, NULL);

	return 0;
}

int csch_compile_project(csch_project_t *prj, int viewid, csch_abstract_t *dst, int quiet)
{
	int res = 0, r;
	long n;
	htpp_entry_t *e;

	compile_reset_abstract(dst);
	htpp_init(&dst->eng_transient, ptrhash, ptrkeyeq);
	dst->view_id = viewid;
	dst->prj = prj;
	dst->comp_cnt++;

	if (csch_project_is_partial(prj))
		rnd_message(RND_MSG_ERROR, "*** PARTIAL PROJECT ***\nNot all root sheets are loaded, the resulting abstract model\nwill be partial too.\n");

	csch_eng_call(prj, viewid, NULL, CSCH_ENGHK_PROJECT_BEFORE,
		FGW_PTR, dst, FGW_PTR, prj, FGW_INVALID);

	for(n = 0; n < vtp0_len(&prj->hdr.designs); n++) {
		csch_sheet_t *sheet = *vtp0_get(&prj->hdr.designs, n, 0);

		/* hierarchy: start from root sheets only, skip aux sheets; unlisted/unknown are rather compiled so no-project-file works */
		if ((sheet->stype == CSCH_SHTY_AUX) || (sheet->stype == CSCH_SHTY_EXTERNAL)) continue;
		if ((sheet->stype == CSCH_SHTY_UNLISTED) || (sheet->stype == CSCH_SHTY_unknown)) {
			if (prj->num_root_sheets != 0) {
				rnd_message(RND_MSG_WARNING, "Compile: not compiling sheet %s: unlisted file in a project with explicit sheet list in project file\n", sheet->hidlib.loadname);
				if (!sheet->non_graphical)
					csch_reset_sheet_aids(sheet);
				continue;
			}
			else {
				/* implicit project file, accept and compile unlisted sheets */
			}
		}

		{
			csch_hier_path_t hpath;
			csch_hier_path_init(&hpath, dst);
			r = csch_compile_sheet_(dst, viewid, &hpath, sheet);
			csch_hier_path_free(&hpath);
		}
		if (r != 0)
			rnd_message(RND_MSG_ERROR, "sheet #%d (%s) compilation failed: %d\n", n, sheet->hidlib.fullpath, r);
		res |= r;
	}
	csch_compile_post(prj, viewid, dst);
	
	for(e = htpp_first(&dst->eng_transient); e != NULL; e = htpp_next(&dst->eng_transient, e))
		rnd_message(RND_MSG_ERROR, "csch_compile_project(): engine transient leftover: %s (memory leak)\n", e->key);
	htpp_uninit(&dst->eng_transient);
	dst->prj = NULL;
	return res;
}
