/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - standard device mapper
 *  Copyright (C) 2019,2020,2022,2023 Tibor 'Igor2' Palinkas
 *
 *  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
 */

/*** compiler hooks ***/

#include <libcschem/compile.h>

fgw_error_t devmap_term2port(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	csch_acomp_t *comp;*/
	csch_cgrp_t *term, *sym;
	const char *pname;
	const char *slot;
	gds_t tmp;
	csch_hier_path_t *hpath;

	CSCH_HOOK_CONVARG(1, FGW_STR,  devmap_term2port, pname = argv[1].val.cstr);
/*	CSCH_HOOK_CONVARG(2, FGW_AOBJ, devmap_term2port, comp = fgw_aobj(&argv[2]));*/
	CSCH_HOOK_CONVARG(3, FGW_COBJ, devmap_term2port, term = fgw_cobj(&argv[3]));
	CSCH_HOOK_CONVARG(4, FGW_HPATH, devmap_term2port, hpath = fgw_hpath(&argv[4]));

	sym = term->hdr.parent;
	if (sym == NULL)
		return 0;

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

	/* output port name is slot/input port name */
	gds_init(&tmp);
	gds_append_str(&tmp, slot);
	gds_append(&tmp, '/');
	gds_append_str(&tmp, pname);
	res->type = FGW_STR | FGW_DYN;
	res->val.str = tmp.array;
	return 0;
}


fgw_error_t devmap_symbol_joined_comp(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	csch_cgrp_t *sym;*/
	csch_acomp_t *comp;
	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;
	csch_source_arg_t *del_src;


/*	CSCH_HOOK_CONVARG(1, FGW_COBJ, devmap_comp_update, sym = fgw_cobj(&argv[1]));*/
	CSCH_HOOK_CONVARG(2, FGW_AOBJ, devmap_comp_update, comp = fgw_aobj(&argv[2]));

	del_src = csch_attrib_src_p("std_devmp", "symbol_joined_comp");

	csch_attrib_del(&comp->hdr.attr, cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "slot", del_src);
	return 0;
}

fgw_error_t devmap_compile_comp1(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_project_t *prj;
	anymap_ctx_t *actx;
	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;
	csch_acomp_t *comp;
	csch_attribs_t *comp_attrs;
	csch_attrib_t *adevmap;
	csch_source_arg_t *src;
	htsp_entry_t *e;
	int prio;

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

	adevmap = csch_attrib_get(&comp->hdr.attr, "devmap");
	if (adevmap == NULL)
		return 0;


	prj = comp->hdr.abst->prj;
	actx = csch_p4_get_by_project(&p4_devmap, prj);

	comp_attrs = anymap_get_from_any_lib(actx, comp, adevmap, argv[0].val.argv0.user_call_ctx);
	if (comp_attrs == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't find devmap %s for component %s\n", (adevmap->val == NULL ? "<null>" : adevmap->val), comp->name);
		return -1;
	}

	prio = cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL;

	for(e = htsp_first(comp_attrs); e; e = htsp_next(comp_attrs, e)) {
		int orig_prio;
		const char *key = e->key;
		csch_attrib_t *a = e->value;

		src = csch_attrib_src_pa(&comp->hdr, "devmap", "std_devmap", "devmap_compile_comp1");

		/* enforce devmap's prio and compile tha ttribute (need to use the attr
		   compiler so that the '-' and '+' prefixes are respected) */
		orig_prio = a->prio;
		a->prio = prio;
		csch_compile_attribute(&comp->hdr, key, a, src,  "devmap attribute",NULL,NULL,  0, 0, NULL);
		a->prio = orig_prio;
	}
	return 0;
}

fgw_error_t devmap_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;
	csch_source_arg_t *pm_src;
	csch_acomp_t *comp;
	csch_aport_t *port;
	csch_attrib_t *portmap;
	const char *slot = NULL;
	long n, port_len, slot_len = 0;
	int slot_inited = 0;

	CSCH_HOOK_CONVARG(1, FGW_AOBJ, devmap_comp_update, port = fgw_aobj(&argv[1]));
	assert(port->hdr.type == CSCH_ATYPE_PORT);
	comp = port->parent;
	if ((comp == NULL) || (comp->hdr.type != CSCH_ATYPE_COMP))
		return 0; /* ignore sheet ports */

	portmap = csch_attrib_get(&comp->hdr.attr, "portmap");
	if (portmap == NULL)
		return 0;

	if (portmap->val != NULL) {
		rnd_message(RND_MSG_ERROR, "Portmap of component '%s' is not an array but a string\n", comp->name);
		return -1;
	}

	port_len = strlen(port->name);
	for(n = 0; n < portmap->arr.used; n++) {
		char tmp[256], *ktmp = NULL, *key;
		const char *pm = portmap->arr.array[n], *set = NULL, *sep, *val;
/*		const char *pm_port = pm;*/
		int pm_slotlen = 0;
		int pm_portlen;

		/* parse: find '->' and '/' on the left side of it */
		for(sep = pm; *sep != '\0'; sep++) {
			if ((sep[0] == '-') && (sep[1] == '>')) {
				pm_portlen = sep - pm - 1;
				while((pm_portlen > 0) && isspace(pm[pm_portlen])) pm_portlen--;
				pm_portlen++;
				set = sep + 2;
				break;
			}
			else if ((*sep == '/') && (sep > pm)) {
				if (pm_slotlen == 0)
					pm_slotlen = sep - pm; /* slot spans to the first '/' only */
/*				pm_port = sep + 1;*/
			}
		}

		if (set == NULL) /* no "->" in string */
			continue;

		/* match slot if pm has slash on the left side */
		if (pm_slotlen > 0) {
			if (!slot_inited) {
				vtp0_t *srcs = &port->hdr.srcs;
				long n;

				for(n = 0; n < srcs->used; n++) {
					csch_cgrp_t *cgrp = srcs->array[n];
					const char *stmp;

					if (cgrp->role == CSCH_ROLE_TERMINAL)
						cgrp = cgrp->hdr.parent;

					stmp = csch_attrib_get_str(&cgrp->attr, "-slot");
					if (stmp == NULL)
						stmp = csch_attrib_get_str(&cgrp->attr, "slot");
					if (slot != NULL) {
						if (strcmp(slot, stmp) != 0)
							rnd_message(RND_MSG_ERROR, "std_devmap: incompatible slot names found for component '%s' port '%s': '%s' and '%s'\n", comp->name, port->name, slot, stmp);
					}
					else
						slot = stmp;
				}
				if (slot != NULL)
					slot_len = strlen(slot);
				/* NOTE: slot == NULL is not an error: in inhomogoeneous slotting
				   single-instance slots, like power of a 74xx would have no slot=
				   argument */
				slot_inited = 1;
			}
			
			if ((slot == NULL) || (slot_len != pm_slotlen)) continue;
			if (strncmp(pm, slot, slot_len) != 0) continue;
		}

		/* match left side of "->" with port name, assuming port->name is already in slot/name format */
		if ((pm_portlen != port_len) || (strncmp(pm, port->name, port_len) != 0)) continue;

		/* parse the right side */
		sep = strchr(set, '=');
		if (sep == NULL) {
			rnd_message(RND_MSG_ERROR, "Portmap of component '%s' contains invalid line (missing '='):\n'%s'\n", comp->name, pm);
			continue;
		}
		val = sep+1;
		if (sep - set < sizeof(tmp)-2) {
			strncpy(tmp, set, sep-set);
			tmp[sep-set] = '\0';
			key = tmp;
		}
		else
			key = ktmp = rnd_strndup(set, sep - set);

		while(isspace(*key)) key++; /* eat up spaces after the "->" separator */

		pm_src = csch_attrib_src_pa(&comp->hdr, "portmap", "std_devmap", "applied portmap");
		csch_attrib_set(&port->hdr.attr, cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL, key, val, pm_src, NULL);
		free(ktmp);
	}

	return 0;
}
