/*
 *                            COPYRIGHT
 *
 *  sch-rnd - 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
 */

/* DRC checks */

#include <libcschem/drc.h>
#include <genht/htsi.h>

#define SINGLE '>'
#define MULTI ','

RND_INLINE void funcmap_drc_comp(csch_drc_t *drc, csch_project_t *prj, csch_acomp_t *comp)
{
/*	csch_abstract_t *abst = prj->abst;*/
	csch_attribs_t *fmattrs;
	htsp_t func2col, grps;
	csch_attrib_t *grplst;
	int grpcol, c, f;
	htsp_entry_t *e;
	vts0_t hdr = {0};

	if (funcmap_fetch(prj, comp->name, &comp, &fmattrs) != 0)
		return;

	/* collect only strong groups as we are going to verify only those */
	htsp_init(&func2col, strhash, strkeyeq);
	htsp_init(&grps, strhash, strkeyeq);
	grpcol = 1;
	grplst = htsp_get(fmattrs, "funcmap/strong_groups");
	funcmap_comp2dlg_hashgrp(&func2col, grplst, &grpcol);
	funcmap_map_grps(&grps, fmattrs, grplst);
	vts0_append(&hdr, rnd_strdup("<port>"));
	funcmap_comp_gen_hdr_(&hdr, htsp_get(fmattrs, "funcmap/strong_groups"));
	vts0_append(&hdr, rnd_strdup("<nogroup>"));

/*	rnd_trace("drc1 collect:\n");*/
	for(e = htsp_first(&comp->ports); e != NULL; e = htsp_next(&comp->ports, e)) {
		csch_aport_t *port = e->value;
		const char *port_name = e->key;
		const char *func;
		vtl0_t *cols;

		func = csch_attrib_get_str(&port->hdr.attr, "funcmap/name");
		if (func == NULL)
			continue;

		cols = htsp_get(&func2col, func);
		if (cols == NULL)
			continue;

		for(c = 0; c < cols->used; c++) {
			const char *grp_name;
			vts0_t *grp_funcs;
			
			f = cols->array[c];
			if ((f < 0) || (f >= hdr.used)) continue;
			grp_name = hdr.array[f];
			if (grp_name == NULL) continue;
			grp_funcs = htsp_get(&grps, grp_name); /* all functions of the group we are in */
/*			rnd_trace(" port=%s func=%s in %s %p\n", port_name, func, grp_name, grp_funcs);*/
			
			/* mark the function we used up by modifying their string in the grp
			   hash-list (these are strdupd) */
			for(f = 0; f < grp_funcs->used; f++) {
				char *grp_func = grp_funcs->array[f];
				if (strcmp(grp_func, func) == 0) {
					*grp_func = (cols->used == 1) ? SINGLE : MULTI;
/*					rnd_trace("  '%s'\n", grp_func);*/
				}
			}
		}
	}

/*	rnd_trace("drc2 verify:\n");*/
	/* Verify that each strong group has either 0 or all functions used, exactly once */
	for(e = htsp_first(&grps); e != NULL; e = htsp_next(&grps, e)) {
		const char *grp_name = e->key;
		vts0_t *grp_funcs = e->value;
		int has_single = 0, has_multi = 0, has_unbound = 0;

		for(f = 0; f < grp_funcs->used; f++) {
			const char *func = grp_funcs->array[f];
			switch(*func) {
				case SINGLE: has_single++; break;
				case MULTI:  has_multi++; break;
				default:     has_unbound++; break;
			}
		}

/*		rnd_trace(" grp %s: %d %d %d\n", grp_name, has_single, has_multi, has_unbound);*/
		if (has_unbound && has_single) {
			/* some ports in this group are selected in a way we know this
			   port is to be used (has_single), while other ports are unselected
			   (has_unbound). The reason for ignoring multi: attiny24 has SCK used
			   by both USI and SPI; using SCK for SPI would trigger a violation on
			   USI. Instead, ignore SCK in both unless it is unbound. */
			csch_drc_violation_t *v = csch_drc_violation_alloc_append(drc);
			csch_drc_abst_ref_t *r;
			v->title = rnd_strdup("funcmap/strong_grp_partial");
			v->desc = rnd_strdup_printf("Strong function group %s with some functions assigned to ports while other functions not assigned", grp_name);
			r = csch_drc_abst_ref_alloc_append(v);
			r->comp_name = rnd_strdup(comp->name);
		}
	}


	funcmap_map_grps_uninit(&grps);
	funcmap_hashgrps_free(&func2col);
	for(c = 0; c < hdr.used; c++)
		free(hdr.array[c]);
	vts0_uninit(&hdr);
}

static void funcmap_drc_ev(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;
	csch_abstract_t *abst = prj->abst;
	csch_drc_t *drc;
	htsp_entry_t *e;

	if (funcmap_conf.plugins.funcmap.drc_disable)
		return;

	if ((argc < 1) || (argv[1].type != RND_EVARG_PTR) || (abst == NULL))
		return;

	drc = argv[1].d.p;
	if (drc == NULL)
		return;

	for(e = htsp_first(&abst->comps); e != NULL; e = htsp_next(&abst->comps, e))
		funcmap_drc_comp(drc, prj, e->value);

	drc->ran++;
}
