/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - spice target
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2022)
 *
 *  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
 */

#include <libcschem/config.h>

#include <stdlib.h>
#include <assert.h>
#include <ctype.h>

#include <libfungw/fungw.h>
#include <genht/htsp.h>
#include <genht/hash.h>
#include <load_cache/load_cache.h>
#include <librnd/core/plugins.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/safe_fs.h>

#include <libcschem/libcschem.h>
#include <libcschem/abstract.h>
#include <libcschem/compile.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/engine.h>
#include <libcschem/event.h>
#include <libcschem/actions_csch.h>
#include <libcschem/attrib.h>
#include <libcschem/util_compile.h>
#include <libcschem/util_loclib.h>
#include <libcschem/util_lib_fs.h>
#include <libcschem/hierarchy.h>

#include <plugins/sch_dialogs/quick_attr_util.h>
#include <plugins/lib_target/lib_target.h>

#include "htcp.h"
#include "target_spice_conf.h"
#include "conf_internal.c"

static conf_target_spice_t target_spice_conf;

typedef struct {
	ldch_ctx_t spicelibs;
	csch_view_eng_t *eng;
	ldch_low_parser_t *low_parser;
	ldch_high_parser_t *high_parser;
	vtp0_t ssyms;
	htcp_t shared_ports;  /* key is comp-term, value is a vtp0_t of ports in different slots */
	htsp_t bridged_ports; /* ports that need brindging; key is bridge model name (assuming vector bridges), value is vtp0_t of (csch_aport_t *) */
	unsigned compiling:1;
} spicelib_ctx_t; /* per view data */

typedef struct spicelib_s {
	gds_t *text;
} spicelib_t;

static spicelib_ctx_t *global_spicelib_ctx = NULL;

static const char cookie[] = "target_spice";
static const char spicelib_cookie[] = "target_spice/spicelib";
static csch_lib_backend_t be_spicelib_mod;
static csch_lib_master_t *spicelibmaster;

const char csch_acts_quick_attr_spice__model[] = "quick_attr_spice__model(objptr)";
const char csch_acth_quick_attr_spice__model[] = "Quick Attribute Edit for spice/model using the devmap library";
fgw_error_t csch_act_quick_attr_spice__model(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	csch_cgrp_t *grp;
	fgw_arg_t ares, args[4];
	int ret;

	QUICK_ATTR_GET_GRP(grp, "quick_attr_spice__model");

	args[1].type = FGW_STR;
	args[1].val.cstr = "spicelib";
	args[2].type = FGW_STR;
	args[2].val.cstr = "sheet";
	args[3].type = FGW_STR;
	args[3].val.cstr = "modal";

	ret = rnd_actionv_bin(&sheet->hidlib, "librarydialog", &ares, 4, args);
	if ((ret == 0) && ((ares.type & FGW_STR) == FGW_STR)) {
		csch_source_arg_t *src;
		char *path = ares.val.str, *sep = NULL;

		if ((path != NULL) && (*path != '\0'))
			sep = strrchr(path, '/');
		if (sep != NULL) {
			char *end = strrchr(sep+1, '.');
			if ((end != NULL) && ((rnd_strcasecmp(end, ".prm") == 0) || (rnd_strcasecmp(end, ".mod") == 0)))
				*end = '\0';

			src = csch_attrib_src_p("tagret_spice", "manually picked from the model lib");
			csch_attr_modify_str(sheet, grp, -CSCH_ATP_USER_DEFAULT, "spice/model", sep+1, src, 1);
/*			rnd_trace("new devmap val: '%s'\n", sep+1);*/
		}
	}
	fgw_arg_free(&rnd_fgw, &ares);


	RND_ACT_IRES(1);
	return 0;
}

static void spicelib_sheet_postload_ev(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	csch_lib_add_all(sheet, spicelibmaster, &target_spice_conf.plugins.target_spice.search_paths, 0);
	csch_lib_add_local(sheet, spicelibmaster);
}

#include "mod_parse.c"
#include "loclib.c"
#include "libs.c"
#include "preview.c"


/*** hooks ***/

#define SLOTSEP "__"

/* Return the slot value of a symbol */
static const char *get_slot(csch_cgrp_t *sym)
{
	const char *slot = csch_attrib_get_str(&sym->attr, "-slot");

	if (slot == NULL)
		slot = csch_attrib_get_str(&sym->attr, "slot");
	return slot;
}

/* Return the slot value of a component (looking back at source symbols) */
static const char *get_aslot(csch_acomp_t *comp)
{
	long n;

	for(n = 0; n < comp->hdr.srcs.used; n++) {
		csch_cgrp_t *sym = comp->hdr.srcs.array[n];
		if (csch_obj_is_grp(&sym->hdr)) {
			const char *slot = get_slot(sym);
			if (slot != NULL)
				return slot;
		}
	}

	return NULL;
}


fgw_error_t target_spice_sym2comp(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *sym;
	const char *sname, *slot;
	csch_hier_path_t *hpath;
	gds_t tmp;

	CSCH_HOOK_CONVARG(1, FGW_STR,  target_spice_sym2comp, sname = argv[1].val.cstr);
	CSCH_HOOK_CONVARG(2, FGW_COBJ, target_spice_sym2comp, sym = fgw_cobj(&argv[2]));
	CSCH_HOOK_CONVARG(3, FGW_HPATH, target_spice_sym2comp, hpath = fgw_hpath(&argv[3]));

	slot = get_slot(sym);
	if (slot == NULL)
		return 0;

	/* output port name is slot/input port name */
	gds_init(&tmp);
	if (hpath->prefix.used > 0) {
		gds_append(&tmp, sname[0]);
		gds_append_len(&tmp, hpath->prefix.array, hpath->prefix.used);
	}
	gds_append_str(&tmp, sname);
	gds_append_str(&tmp, SLOTSEP);
	gds_append_str(&tmp, slot);
	gds_append(&tmp, '\n');
	gds_append_str(&tmp, sname);
	gds_append_str(&tmp, SLOTSEP);
	gds_append_str(&tmp, slot);
	res->type = FGW_STR | FGW_DYN;
	res->val.str = tmp.array;
	return 0;

}


fgw_error_t target_spice_compile_port(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;*/
	fgw_obj_t *obj = argv[0].val.argv0.func->obj;
	spicelib_ctx_t *ctx = obj->script_data;
	csch_view_eng_t *eng = ctx->eng;
	csch_aport_t *port;
	const char *pinnum, *bridgemod;
	csch_source_arg_t *src;


	CSCH_HOOK_CONVARG(1, FGW_AOBJ, std_cschem_comp_update, port = fgw_aobj(&argv[1]));
	assert(port->hdr.type == CSCH_ATYPE_PORT);

	/* remember bridged ports, per bridge model type */
	bridgemod = csch_attrib_get_str(&port->hdr.attr, "spice/bridge/model");
	if (bridgemod != NULL) {
		htsp_entry_t *e = htsp_getentry(&ctx->bridged_ports, bridgemod);
		vtp0_t *plist;

		if (e == NULL) {
			plist = calloc(sizeof(vtp0_t), 1);
			htsp_set(&ctx->bridged_ports, (char *)bridgemod, (void *)plist);
		}
		else
			plist = e->value;
		vtp0_append(plist, port);
	}

	/* figure pin number */
	pinnum = csch_attrib_get_str(&port->hdr.attr, "spice/pinnum");
	if (pinnum != NULL)
		src = csch_attrib_src_pa(&port->hdr, "spice/pinnum", "target_spice", NULL);

	if (pinnum == NULL) {
		pinnum = csch_attrib_get_str(&port->hdr.attr, "pinnum");
		if (pinnum != NULL)
			src = csch_attrib_src_pa(&port->hdr, "pinnum", "target_spice", NULL);
	}
	if (pinnum == NULL) {
		pinnum = port->name;
		if (pinnum != NULL)
			src = csch_attrib_src_p("target_spice", "fallback on port name");
	}

	/* Calculate final name */
	if (pinnum != NULL)
		csch_attrib_set(&port->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "display/name", pinnum, src, NULL);

	/* remember shared ports */
	if (pinnum != NULL) {
		const char *shared = csch_attrib_get_str(&port->hdr.attr, "spice/shared");
		if (shared != NULL) {
			htcp_key_t shkey;
			htcp_entry_t *e;

			shkey.orig_comp_name = csch_attrib_get_str(&port->parent->hdr.attr, "name");
			if (shkey.orig_comp_name != NULL) {
				shkey.pinnum = strtol(pinnum, NULL, 10);

				e = htcp_getentry(&ctx->shared_ports, shkey);
				if (e == NULL) {
					htcp_value_t tmp = {0};
					htcp_set(&ctx->shared_ports, shkey, tmp);
					e = htcp_getentry(&ctx->shared_ports, shkey);
				}
				vtp0_append(&e->value.ports, port);
			}
		}
	}

	return 0;
}

/* Returns whether model_card's first model is a subckt */
static int model_is_subckt(const char *model_card, const char *model_name)
{
	const char *curr, *next;
	for(curr = model_card; curr != NULL; curr = next) {
		next = strchr(curr, '\n');
		if (next != NULL)
			next++;
		while(isspace(*curr)) curr++;
		if (rnd_strncasecmp(curr, ".subckt", 7) == 0)
			return 1;
		if (rnd_strncasecmp(curr, ".model", 6) == 0)
			return 0;
	}
	return 0;
}

/* Create a component for a reusable (lib) model card */
static csch_acomp_t *make_acomp_for_model_card(spicelib_ctx_t *ctx, csch_acomp_t *comp, const char *modcompname, const char *model_name, const char *model_card, int *is_subckt_out, int set_uname)
{
	csch_source_arg_t *src;
	char *freeme = NULL;
	const char *existing;
	csch_acomp_t *modcomp;

	if (modcompname == NULL)
		modcompname = freeme = rnd_concat("_spice_model_card_", model_name, NULL);

	modcomp = csch_acomp_get(comp->hdr.abst, modcompname);
	if (modcomp == NULL)
		modcomp = csch_acomp_new(comp->hdr.abst, comp->hdr.abst->hroot, CSCH_ASCOPE_GLOBAL, modcompname, modcompname);
	if (modcomp == NULL)
		return NULL;

	if (set_uname) {
		char *uname = rnd_concat("schrnd_", modcompname, NULL);
		src = csch_attrib_src_p("target_spice", "model card copied from lib");
		csch_attrib_set(&comp->hdr.attr, 0, "spice/model_card_uname", uname, src, NULL);
		free(uname);
	}

	existing = csch_attrib_get_str(&modcomp->hdr.attr, "spice/model_card");
	if (existing != NULL) {
		const char *s_is_subckt = csch_attrib_get_str(&modcomp->hdr.attr, "spice/model_is_subckt");
		if (strcmp(existing, model_card) != 0)
			rnd_message(RND_MSG_ERROR, "target spice: internal error: model card mismatch for %s\n", comp->name);
		*is_subckt_out = ((s_is_subckt != NULL) && (s_is_subckt[0] == '1'));
	}
	else {
		src = csch_attrib_src_p("target_spice", "model card copied from lib");
		csch_attrib_set(&modcomp->hdr.attr, 0, "spice/model_card", model_card, src, NULL);

		*is_subckt_out = model_is_subckt(model_card, model_name);
		src = csch_attrib_src_p("target_spice", "parsed from model card");
		csch_attrib_set(&modcomp->hdr.attr, 0, "spice/model_is_subckt", ((*is_subckt_out) ? "1" : "0"), src, NULL);
	}
	free(freeme);
	return modcomp;
}

fgw_error_t target_spice_compile_component0(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	fgw_obj_t *obj = argv[0].val.argv0.func->obj;
	spicelib_ctx_t *ctx = obj->script_data;
	csch_view_eng_t *eng = ctx->eng;
	csch_acomp_t *comp;

	CSCH_HOOK_CONVARG(1, FGW_AOBJ, std_cschem_comp_update, comp = fgw_aobj(&argv[1]));

	sch_trgt_copy_alt_attrib(&comp->hdr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "target_spice", "display/omit",   "spice/omit", "omit", NULL);
	return 0;
}


fgw_error_t target_spice_compile_component1(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;*/
	fgw_obj_t *obj = argv[0].val.argv0.func->obj;
	spicelib_ctx_t *ctx = obj->script_data;
	csch_view_eng_t *eng = ctx->eng;
	csch_acomp_t *comp;
	const char *prefix, *name, *slot;
	csch_attrib_t *portmap, *amodel;
	char *tmp;
	csch_source_arg_t *src;
	const char *model_card, *shprefix;
	int is_subckt = 0;
	gds_t hprefix = {0};


	CSCH_HOOK_CONVARG(1, FGW_AOBJ, std_cschem_comp_update, comp = fgw_aobj(&argv[1]));
	assert(comp->hdr.type == CSCH_ATYPE_COMP);


	model_card = csch_attrib_get_str(&comp->hdr.attr, "spice/model_card");
	if (model_card != NULL) {
		char *uname = rnd_concat("schrnd_", comp->name, NULL);

		src = csch_attrib_src_p("target_spice", "model card copied from symbol");
		csch_attrib_set(&comp->hdr.attr, 0, "spice/model_card_uname", uname, src, NULL);
		free(uname);
	}
	else {
		amodel = csch_attrib_get(&comp->hdr.attr, "spice/model");
		if (amodel != NULL) {
			const char *model_name = (amodel->val == NULL ? "<null>" : amodel->val);
			const char *model_card;
			model_card = spicelib_get_from_any_lib_for_comp(ctx, comp, amodel, obj);
			if (model_card != NULL)
				make_acomp_for_model_card(ctx, comp, NULL, model_name, model_card, &is_subckt, 1);
			else
				rnd_message(RND_MSG_ERROR, "Can't find spicelib model card %s for component %s\n", model_name, comp->name);
		}
	}


	/* this component1() runs before ports are compiled for the component, so
	   it is safe to set up the portmap here */
	portmap = csch_attrib_get(&comp->hdr.attr, "spice/portmap");
	if (portmap != NULL) {
		/* rnd_trace("spice pm: %s\n", comp->name);*/
		src = csch_attrib_src_p("target_spice", "acquired from spice/portmap");
		csch_compile_attribute(&comp->hdr, "portmap", portmap, src, "component", comp->name, NULL, 0, 1, NULL);
	}


	/* Set 'X' prefix for subcircuit models on normal engine prio; lets the
	   user both override and fallback it with different prios (250 and 31050) */
	if (is_subckt) {
		prefix = "X";
		src = csch_attrib_src_p("target_spice", "model is a subcircuit");
		csch_attrib_set(&comp->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "spice/prefix", "X", src, NULL);
	}

	prefix = csch_attrib_get_str(&comp->hdr.attr, "spice/prefix");
	if (prefix == NULL)
		return 0;

	name = csch_attrib_get_str(&comp->hdr.attr, "display/name");
	if (name == NULL)
		name = csch_attrib_get_str(&comp->hdr.attr, "name");
	if (name == NULL)
		name = comp->name;

	/* do not prefix if no need to */
	if (*name == *prefix)
		return 0;

/*	rnd_trace("spice rename: name='%s' prefix='%s'\n", name, prefix);*/

	/* compute hierarchic prefix */
	if (comp->hlev->parent != NULL) {
		csch_hier_build_path(&hprefix, comp->hlev);
		shprefix = hprefix.array;
	}
	else
		shprefix ="";

	src = csch_attrib_src_p("target_spice", "instance prefix from spice/prefix");
	slot = get_aslot(comp);
	if (slot != NULL)
		tmp = rnd_concat(prefix, "_", shprefix, name, SLOTSEP, slot, NULL);
	else
		tmp = rnd_concat(prefix, "_", shprefix, name, NULL);

	csch_attrib_set(&comp->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "display/name", tmp, src, NULL);
	gds_uninit(&hprefix);
	free(tmp);

	return 0;
}

fgw_error_t target_spice_compile_project_before(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;*/
	fgw_obj_t *obj = argv[0].val.argv0.func->obj;
	spicelib_ctx_t *ctx = obj->script_data;

	assert(!ctx->compiling); /* we are not reentrant */
	ctx->compiling = 1;
	htcp_init(&ctx->shared_ports, htcp_keyhash, htcp_keyeq);
	htsp_init(&ctx->bridged_ports, strhash, strkeyeq);
	return 0;
}

#define MAXSC     (1L<<30)
#define SCORE(x)  (MAXSC-1000L+x)

static void elect_net_zero(spicelib_ctx_t *ctx, csch_abstract_t *abst)
{
	csch_view_eng_t *eng = ctx->eng;
	csch_source_arg_t *src;
	csch_anet_t *best = NULL;
	long score = 0; /* higher value is more preferred */
	htsp_entry_t *e;
	int warn = 1;

	for(e = htsp_first(&abst->nets); e != NULL; e = htsp_next(&abst->nets, e)) {
		csch_anet_t *net = e->value;
		const char *attr = csch_attrib_get_str(&net->hdr.attr, "spice/gnd");
		if (attr != NULL) {
			if (score < MAXSC) {
				score = MAXSC;
				best = net;
				warn = 0;
				/* keep on searching to find redundant spice/gnd attrs */
			}
			else
				rnd_message(RND_MSG_ERROR, "Multiple nets are marked with spice/gnd: %s and %s\n", best->name, net->name);
		}
		else {
			if ((score < SCORE(100)) && (rnd_strcasecmp(net->name, "gnd") == 0)) {
				score = SCORE(100);
				best = net;
				warn = 0;
			}
			else if ((SCORE(score) < 90) && ((strstr(net->name, "gnd") != NULL) || (strstr(net->name, "GND") != NULL))) {
				score = SCORE(90);
				best = net;
			}
			else if (net->conns.used > score) {
				/* if name doesn't elect a gnd net, go for the largest net */
				score = net->conns.used;
				best = net;
			}
		}
	}

	if (score >= MAXSC)
		return; /* do not set attribute if we got one already */

	if (best == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to elect gnd net (node 0). Spice needs at least 2 nets, one being the gnd.\n");
		return;
	}

	if (warn)
		rnd_message(RND_MSG_WARNING, "Spice needs a ground net, also known as node 0.\nIt's best to select it by setting spice/gnd attribute on a net.\nThere was no such net, my best bet for gnd is net %s\n", best->name);

	src = csch_attrib_src_p("target_spice", "elected gnd (node 0) net");
	csch_attrib_set(&best->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "spice/gnd", "elected", src, NULL);
}

#undef MAXSC
#undef SCORE

static void shared_port_connect(spicelib_ctx_t *ctx, csch_abstract_t *abst)
{
	htcp_entry_t *e;

	/* find all shared ports among components and copy their connections */
	for(e = htcp_first(&ctx->shared_ports); e != NULL; e = htcp_next(&ctx->shared_ports, e)) {
		long n;
		csch_anet_t *net = NULL;

		/* find the net that's connected */
		for(n = 0; n < e->value.ports.used; n++) {
			csch_aport_t *port = e->value.ports.array[n];
			if (port->conn.net != NULL) {
				net = port->conn.net;
				break;
			}
		}

		/* if there's a net, connect any unconnected ports there and warn for ports connected elsewhere */
		if (net != NULL) {
			for(n = 0; n < e->value.ports.used; n++) {
				csch_aport_t *port = e->value.ports.array[n];
				if ((port->conn.net == NULL) || (port->conn.net != net)) {
					if (csch_compile_connect_net_to(&net, &port->hdr, 1) != 0)
						rnd_message(RND_MSG_ERROR, "target_spice: shared port %s-%s failed to connect to %s\n", APORT_PARENT_NAME(port), port->name, net->name);
				}
			}
		}

		vtp0_uninit(&e->value.ports);
	}

	htcp_uninit(&ctx->shared_ports);
}

/* Create a model card and a component instance for a bridge; return
   the bridge component or NULL on error. Also set *is_subckt depending
   on model type (1=subckt, 0=model) */
static csch_acomp_t *make_bridge_comp(spicelib_ctx_t *ctx, csch_abstract_t *abst, csch_project_t *prj, const char *model_name, const char *brname, int *is_subckt, int multi)
{
	csch_view_eng_t *eng = ctx->eng;
	csch_source_arg_t *src;
	csch_acomp_t *br;
	void *obj = NULL;
	const char *model_card = spicelib_get_from_any_lib_for_project(ctx, prj, model_name, obj);

	if (model_card != NULL) {
		const char *compname;
		csch_acomp_t dummy = {0};

		dummy.hdr.abst = abst;
		dummy.name = "(auto-bridge)";

		compname = multi ? brname : model_name;
		br = make_acomp_for_model_card(ctx, &dummy, compname, model_card, model_card, is_subckt, 0);
	}
	else {
		rnd_message(RND_MSG_ERROR, "Can't find spicelib model card %s for auto-bridge\n", model_name);
		return NULL;
	}

	src = csch_attrib_src_p("target_spice", "automatic bridge (port attrib)");
	csch_attrib_set(&br->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "display/name", brname, src, NULL);

	src = csch_attrib_src_p("target_spice", "automatic bridge (port attrib)");
	csch_attrib_set(&br->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "spice/model_card_uname", model_name, src, NULL);

	return br;
}

/* Return model basename and figure default model direction */
static const char *bridge_model_suffix(const char *model_name, int *straight_by_model)
{
	const char *end = strrchr(model_name, '/');
	if (end == NULL)
		end = model_name;

	/* reverse order of pins in dac by default, assuming bridging is done
	   on the digital side */
	if ((strstr(model_name, "dac") != NULL) || (strstr(model_name, "DAC") != NULL))
		*straight_by_model = 0;
	else
		*straight_by_model = 1;

	return end;
}

static void bridge_insert(spicelib_ctx_t *ctx, csch_abstract_t *abst, csch_aport_t *port, csch_acomp_t *br, const char *pn1, const char *pn2, const char *modelsuff, long puid, int straight_by_model, gds_t *tmp)
{
	csch_source_arg_t *src;
	csch_view_eng_t *eng = ctx->eng;

	csch_anet_t *new_net, *old_net;
	csch_aport_t *brp_in, *brp_out;
	char *netname, pname[128];
	int straight;

	/* create a new net (to be in between the bridge and the port) */
	tmp->used = 0;
	rnd_append_printf(tmp, "br_%s_%ld", modelsuff, puid);
	netname = tmp->array;
	new_net = csch_anet_new(abst, NULL, CSCH_ASCOPE_GLOBAL, netname, netname, 1);

/*rnd_trace("  net: '%s' %p\n", netname, new_net);*/
	old_net = port->conn.net;
	csch_compile_disconnect(&port->hdr);
	csch_compile_connect_net_to(&new_net, &port->hdr, 1);

	sprintf(pname, "s1p%ld", puid);
	brp_in = csch_aport_get(abst, br, pname, 1);
	src = csch_attrib_src_p("target_spice", "automatic bridge (port attrib)");
	csch_attrib_set(&brp_in->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "display/name", pn1, src, NULL);

	sprintf(pname, "s2p%ld", puid);
	brp_out = csch_aport_get(abst, br, pname, 1);
	src = csch_attrib_src_p("target_spice", "automatic bridge (port attrib)");
	csch_attrib_set(&brp_out->hdr.attr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "display/name", pn2, src, NULL);

	/* detect ordering - use the one suggested by the model's name but let
	   the user override it. */
	{
		const char *dir = csch_attrib_get_str(&port->hdr.attr, "spice/bridge/dir");

		straight = straight_by_model;
		if (dir != NULL) {
			int err = 0;
			switch(dir[0]) {
				case 'n': case 'N':
					switch(dir[1]) {
						case '1': straight = 1; break; /* n1p2 */
						case '2': straight = 0; break; /* n2p1 */
						default: err = 1;
					}
					break;
				case 'p': case 'P':
					switch(dir[1]) {
						case '1': straight = 0; break; /* p1n2 */
						case '2': straight = 1; break; /* p2n1 */
						default: err = 1;
					}
					break;
				default:
					err = 1;
			}
			if (err)
				rnd_message(RND_MSG_ERROR, "Invalid spice/bridge/dir on component %s port %s: '%s'; should be n1p2 or p1n2\n", APORT_PARENT_NAME(port), port->name, dir);
		}
	}

	/* hook up an input and and output port of the bridge */
	if (straight) { /* net -> bridge[1] -> bridge[2] -> port */
		csch_compile_connect_net_to(&new_net, &brp_out->hdr, 1);
		csch_compile_connect_net_to(&old_net, &brp_in->hdr, 1);
	}
	else { /* net -> bridge[2] -> bridge[1] -> port */
		csch_compile_connect_net_to(&new_net, &brp_in->hdr, 1);
		csch_compile_connect_net_to(&old_net, &brp_out->hdr, 1);
	}
}


/* Insert a vector bridge between ports and their nets (disconnecting the ports);
   create one bridge component per bridge type, use vector ports */
static void install_vector_bridges(spicelib_ctx_t *ctx, csch_abstract_t *abst, csch_project_t *prj)
{
	htsp_entry_t *e;
	gds_t tmp = {0};

	for(e = htsp_first(&ctx->bridged_ports); e != NULL; e = htsp_next(&ctx->bridged_ports, e)) {
		csch_acomp_t *br;
		vtp0_t *plist = e->value;
		int is_subckt = 0, straight_by_model = 1;
		long n;
		const char *model_name = e->key, *end, *brname;

/*		rnd_trace("BRIDGED: %s has %d ports\n", model_name, plist->used);*/

		end = bridge_model_suffix(model_name, &straight_by_model);

		tmp.used = 0;
		rnd_append_printf(&tmp, "A_sch_rnd_br_%s", end);
		brname = tmp.array;

		br = make_bridge_comp(ctx, abst, prj, model_name, brname, &is_subckt, 0);
		if (br == NULL)
			continue;

		for(n = 0; n < plist->used; n++)
			bridge_insert(ctx, abst, plist->array[n], br, "[1]", "[2]", end, n, straight_by_model, &tmp);

		vtp0_uninit(plist);
		free(plist);
		e->value = NULL;
	}
	htsp_uninit(&ctx->bridged_ports);
	gds_uninit(&tmp);
}

/* Insert a scalar bridge between ports and their nets (disconnecting the ports);
   create one bridge component per port */
static void install_scalar_bridges(spicelib_ctx_t *ctx, csch_abstract_t *abst, csch_project_t *prj)
{
	htsp_entry_t *e;
	gds_t tmp = {0};
	unsigned long bruid = 0;

	for(e = htsp_first(&ctx->bridged_ports); e != NULL; e = htsp_next(&ctx->bridged_ports, e)) {
		csch_acomp_t *br;
		vtp0_t *plist = e->value;
		int is_subckt = 0, straight_by_model = 1;
		long n;
		const char *model_name = e->key, *end, *brname;

/*		rnd_trace("BRIDGED: %s has %d ports\n", model_name, plist->used);*/

		end = bridge_model_suffix(model_name, &straight_by_model);


		for(n = 0; n < plist->used; n++) {
			tmp.used = 0;
			rnd_append_printf(&tmp, "A_sch_rnd_br_%ld", bruid++);
			brname = tmp.array;

			br = make_bridge_comp(ctx, abst, prj, model_name, brname, &is_subckt, 1);
			if (br == NULL) {
				continue;
			}

			bridge_insert(ctx, abst, plist->array[n], br, "1", "2", end, n, straight_by_model, &tmp);
		}

		vtp0_uninit(plist);
		free(plist);
		e->value = NULL;
	}
	htsp_uninit(&ctx->bridged_ports);
	gds_uninit(&tmp);

}

static void target_spice_gen_net_omits(spicelib_ctx_t *ctx, csch_abstract_t *abst)
{
	htsp_entry_t *e;
	csch_view_eng_t *eng = ctx->eng;

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

		sch_trgt_copy_alt_attrib(&net->hdr, eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "target_spice", "display/omit",   "spice/omit", "omit", NULL);
	}
}


fgw_error_t target_spice_compile_project_after(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;*/
	fgw_obj_t *obj = argv[0].val.argv0.func->obj;
	spicelib_ctx_t *ctx = obj->script_data;
	csch_abstract_t *abst;
	csch_project_t *prj;

	CSCH_HOOK_CONVARG(1, FGW_STRUCT|FGW_PTR, std_cschem_comp_update, abst = argv[1].val.ptr_void);
	CSCH_HOOK_CONVARG(2, FGW_STRUCT|FGW_PTR, std_cschem_comp_update, prj = argv[2].val.ptr_void);

	shared_port_connect(ctx, abst);
	switch(*target_spice_conf.plugins.target_spice.bridges) {
		case 'v': case 'V': install_vector_bridges(ctx, abst, prj); break;
		case 's': case 'S': install_scalar_bridges(ctx, abst, prj); break;
		default: /* no bridges inserted */ ;
	}
	elect_net_zero(ctx, abst);
	target_spice_gen_net_omits(ctx, abst);

	ctx->compiling = 0;

	return 0;
}

static int on_load(fgw_obj_t *obj, const char *filename, const char *opts)
{
	spicelib_ctx_t *ctx;

	fgw_func_reg(obj, "symbol_name_to_component_name", target_spice_sym2comp);
	fgw_func_reg(obj, "compile_port", target_spice_compile_port);
	fgw_func_reg(obj, "compile_component0", target_spice_compile_component0);
	fgw_func_reg(obj, "compile_component1", target_spice_compile_component1);
	fgw_func_reg(obj, "compile_project_before", target_spice_compile_project_before);
	fgw_func_reg(obj, "compile_project_after", target_spice_compile_project_after);


	/* initialize view-local cache */
	obj->script_data = ctx = calloc(sizeof(spicelib_ctx_t), 1);
	ldch_init(&ctx->spicelibs);
	ctx->eng = obj->script_user_call_ctx;
	ctx->spicelibs.load_name_to_real_name = spicelib_lib_lookup;
	ctx->spicelibs.user_data = obj;
	ctx->low_parser = spicelib_reg_low_parser(&ctx->spicelibs);
	ctx->high_parser = ldch_reg_high_parser(&ctx->spicelibs, "spicelib");
	ctx->high_parser->parse = spicelib_parse;
	ctx->high_parser->free_payload = spicelib_free_payload;

	if (global_spicelib_ctx == NULL)
		global_spicelib_ctx = ctx;


	return 0;
}

static int on_unload(fgw_obj_t *obj)
{
	spicelib_ctx_t *ctx = obj->script_data;
	ldch_uninit(&ctx->spicelibs);
	free(ctx);
	return 0;
}

static const fgw_eng_t fgw_target_spice_eng = {
	"target_spice",
	csch_c_call_script,
	NULL,
	on_load,
	on_unload
};

static rnd_action_t spicelib_action_list[] = {
	{"quick_attr_spice__model", csch_act_quick_attr_spice__model, csch_acth_quick_attr_spice__model, csch_acts_quick_attr_spice__model}
};

int pplg_check_ver_target_spice(int ver_needed) { return 0; }

void pplg_uninit_target_spice(void)
{
	rnd_conf_plug_unreg("plugins/target_spice/", target_spice_conf_internal, cookie);
	rnd_event_unbind_allcookie(spicelib_cookie);
	rnd_remove_actions_by_cookie(spicelib_cookie);
}

int pplg_init_target_spice(void)
{
	RND_API_CHK_VER;

	spicelibmaster = csch_lib_get_master("spicelib", 1);
	be_spicelib_mod.name = "spicelib";
	be_spicelib_mod.realpath = spicelib_mod_realpath;
	be_spicelib_mod.map = spicelib_mod_map;
	be_spicelib_mod.map_local = spicelib_mod_map_local;
	be_spicelib_mod.load = spicelib_mod_load;
	be_spicelib_mod.preview_text = spicelib_mod_preview_text;
	be_spicelib_mod.free = spicelib_mod_free;
	be_spicelib_mod.loc_refresh_from_ext = spicelib_loc_refresh_from_ext;
	be_spicelib_mod.loc_list = spicelib_loc_list;

	csch_lib_backend_reg(spicelibmaster, &be_spicelib_mod);

	rnd_event_bind(CSCH_EVENT_SHEET_POSTLOAD, spicelib_sheet_postload_ev, NULL, spicelib_cookie);

	fgw_eng_reg(&fgw_target_spice_eng);

	RND_REGISTER_ACTIONS(spicelib_action_list, spicelib_cookie);

	rnd_conf_plug_reg(target_spice_conf, target_spice_conf_internal, cookie);
#define conf_reg(field,isarray,type_name,cpath,cname,desc,flags) \
	rnd_conf_reg_field(target_spice_conf, field,isarray,type_name,cpath,cname,desc,flags);
#include "target_spice_conf_fields.h"

	return 0;
}

