/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - alternate function mapper
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2023)
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 ***/

static const char *funcmap_lookup_portfunc(const char *funcmap_name, csch_attribs_t *comp_attrs, const char *port_func, long port_func_len)
{
	csch_attrib_t *ports = csch_attrib_get(comp_attrs, "funcmap/ports");
	long n;

	if (ports == NULL)
		return NULL;

	if (ports->arr.used == 0) {
		rnd_message(RND_MSG_ERROR, "Funcmap '%s': funcmap/ports shall be a non-empty array\n", funcmap_name);
		return NULL;
	}


	if (port_func_len <= 0)
		port_func_len = strlen(port_func);

	for(n = 0; n < ports->arr.used; n++) {
		const char *fnc = ports->arr.array[n];
		while(isspace(*fnc)) fnc++;
		if (strncmp(fnc, port_func, port_func_len) == 0) {
			fnc += port_func_len;
			while(isspace(*fnc)) fnc++;
			if ((fnc[0] == '-') && (fnc[1] == '>')) {
				fnc += 2;
				while(isspace(*fnc)) fnc++;
				return fnc;
			}
		}
	}

	return NULL;
}

static void apply_port_instructions(const char *funcmap_name, csch_hook_call_ctx_t *cctx, csch_aport_t *port, const char *instr)
{
	const char *curr, *next;

	for(curr = instr; curr != NULL; curr = next) {
		long len = -1;
		char *key, *val, *s, *sep;
		csch_source_arg_t *src;

		while(isspace(*curr)) curr++;
		if (*curr == '\0')
			break;

		next = strchr(curr, ';');
		if (next != NULL) {
			len  = next - curr;
			next++;
		}

		funcmap_tmp_gds2.used = 0;
		if (len > 0)
			gds_append_len(&funcmap_tmp_gds2, curr, len);
		else
			gds_append_str(&funcmap_tmp_gds2, curr);

		key = funcmap_tmp_gds2.array;
		sep = strchr(key, '=');
		if (sep == NULL) {
			rnd_message(RND_MSG_ERROR, "Funcmap '%s': invalid instruction: missing '=' in '%s'\n", funcmap_name, key);
			continue;
		}

		*sep = '\0';
		val = sep+1;
		for(s = sep-1; (s > key) && isspace(*s); s--)
			*s = '\0';
		while(isspace(*val)) val++;

		if (*key == '\0') {
			rnd_message(RND_MSG_ERROR, "Funcmap '%s': invalid instruction: missing key in '%s'\n", funcmap_name, instr);
			continue;
		}

		src = csch_attrib_src_pa(&port->hdr, "funcmap", "funcmap", "apply funcmap instructions");
		csch_attrib_set(&port->hdr.attr, cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL, key, val, src, NULL);
	}
}


/* this can be global because of single-thread compilation and the sequence
   of compilation that guarantees compile_port() is called on all ports of
   a component after compile_port1(). */
static csch_attribs_t *comp_attrs;
static const char *comp_funcmap_name;

fgw_error_t funcmap_compile_comp1(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	anymap_ctx_t *actx;
	csch_acomp_t *comp;
	csch_attrib_t *afuncmap;
	csch_project_t *prj;

	comp_attrs = NULL;
	comp_funcmap_name = NULL;

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

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

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

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

	comp_funcmap_name = afuncmap->val;

	return 0;
}

fgw_error_t funcmap_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 *src;
	csch_acomp_t *comp;
	csch_aport_t *port;
	const char *func = NULL, *instr;
	long port_len;

	CSCH_HOOK_CONVARG(1, FGW_AOBJ, funcmap_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 */


	if (comp_attrs == NULL)
		return 0; /* component has no funcmap attribute or lookup failed */

	/* if component has a funcmap but port has no funcmap/name, assign the
	   default automatically: port name */
	func = csch_attrib_get_str(&port->hdr.attr, "funcmap/name");
	if (func == NULL) {
		func = port->name;
		src = csch_attrib_src_pa(&port->hdr, "funcmap", "funcmap", "no funcmap/name, fall back on port name");
		csch_attrib_set(&port->hdr.attr, cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL, "funcmap/name", func, src, NULL);
	}


	port_len = strlen(port->name);
	funcmap_tmp_gds.used = 0;
	gds_append_len(&funcmap_tmp_gds, port->name, port_len);
	gds_append(&funcmap_tmp_gds, '/');
	gds_append_str(&funcmap_tmp_gds, func);

	instr = funcmap_lookup_portfunc(comp_funcmap_name, comp_attrs, funcmap_tmp_gds.array, funcmap_tmp_gds.used);
	if (instr == NULL) {
		if ((func != port->name) && (strcmp(func, port->name) != 0))
			rnd_message(RND_MSG_ERROR, "funcmap: port %s-%s has invalid function '%s' assigned\n", comp->name, port->name, func);
		return -1;
	}

	apply_port_instructions(funcmap_tmp_gds.array, cctx, port, instr);

	return 0;
}
