/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - back annotation
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund 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 <libcschem/abstract.h>
#include <libcschem/project.h>

#include "backann.h"
#include "check_ba.h"


/*** generic/helper ***/

/* Compare attribute to what the user wants and set ba->status accordingly */
static int check_attr(sch_rnd_ba_t *ba, csch_attribs_t *attrs, const char *key, const char *want)
{
	const char *has = csch_attrib_get_str(attrs, key);
	int match = 0;

	/*rnd_trace("attr has='%s' want='%s'\n", has, want);*/

	if ((want == NULL) || (*want == '\0')) {
		/* user requests a delete */
		match = (has == NULL) || (*has == '\0');
	}
	else {
		/* user requests a value */
		if ((has == NULL) || (*has == '\0'))
			match = 0;
		else
			match = (strcmp(has, want) == 0);
	}

	ba->status = match ? SCH_RND_BAS_DONE : SCH_RND_BAS_PENDING;
	return 0; /* accept entry either way */
}

/* Check if net and term are connected; returns +1 on connected, 0 on not
   connected and -1 on error */
int check_term_net_connected(csch_project_t *prj, const char *netname, const char *compname, const char *termname)
{
	csch_anet_t *net;
	csch_acomp_t *comp;
	csch_aport_t *port;

	if (prj->abst == NULL)
		return -1;

	net = csch_anet_get(prj->abst, netname);
	if (net == NULL)
		return -1;

	comp = csch_acomp_get(prj->abst, compname);
	if (comp == NULL)
		return -1;

	port = csch_aport_get(prj->abst, comp, termname, 0);
	if (port == NULL)
		return -1;

	return port->conn.net == net;
}

/*** insrtuctions ***/
static int check_netinfo(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	int cn = check_term_net_connected(prj, ba->value.netinfo.net, ba->value.netinfo.comp, ba->value.netinfo.term);
	if (cn <= 0) {
		if (ba->optional)
			return 0;
		return -1;
	}

	ba->status = SCH_RND_BAS_DONE;
	return 0;
}

static int check_conn_del(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	int cn = check_term_net_connected(prj, ba->value.conn_del.net, ba->value.conn_del.comp, ba->value.conn_del.term);

	if (cn < 0)
		return -1;

	ba->status = !cn ? SCH_RND_BAS_DONE : SCH_RND_BAS_PENDING;

	return 0;
}

static int check_conn_add(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	int cn = check_term_net_connected(prj, ba->value.conn_add.net, ba->value.conn_add.comp, ba->value.conn_add.term);

	if (cn < 0)
		return -1;

	ba->status = cn ? SCH_RND_BAS_DONE : SCH_RND_BAS_PENDING;

	return 0;
}

static int check_comp_del(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	csch_acomp_t *acomp = csch_acomp_get(prj->abst, ba->value.comp_del.comp);

	ba->status = (acomp == NULL) ? SCH_RND_BAS_DONE : SCH_RND_BAS_PENDING;

	return 0;
}

static int check_comp_add(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	csch_acomp_t *acomp = csch_acomp_get(prj->abst, ba->value.comp_del.comp);

	ba->status = (acomp != NULL) ? SCH_RND_BAS_DONE : SCH_RND_BAS_PENDING;

	return 0;
}


static int check_net_attr(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	csch_anet_t *net;

	net = csch_anet_get(prj->abst, ba->value.net_attr.net);
	if (net == NULL)
		return -1;

	return check_attr(ba, &net->hdr.attr, ba->value.net_attr.key, ba->value.net_attr.val);
}

static int check_comp_attr(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	csch_acomp_t *comp;

	comp = csch_acomp_get(prj->abst, ba->value.comp_attr.comp);
	if (comp == NULL)
		return -1;

	return check_attr(ba, &comp->hdr.attr, ba->value.comp_attr.key, ba->value.comp_attr.val);
}

static int check_term_attr(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	csch_acomp_t *comp;
	csch_aport_t *port;

	comp = csch_acomp_get(prj->abst, ba->value.term_attr.comp);
	if (comp == NULL)
		return -1;

	port = csch_aport_get(prj->abst, comp, ba->value.term_attr.term, 0);
	if (port == NULL)
		return -1;

	return check_attr(ba, &port->hdr.attr, ba->value.term_attr.key, ba->value.term_attr.val);
}


int sch_rnd_backann_check_entry(sch_rnd_backann_t *ctx, sch_rnd_ba_t *ba, long *pend)
{
	switch(ba->type) {
		case SCH_RND_BAT_NETINFO:     return check_netinfo(ctx, ba, pend);
		case SCH_RND_BAT_CONN_DEL:    return check_conn_del(ctx, ba, pend);
		case SCH_RND_BAT_CONN_ADD:    return check_conn_add(ctx, ba, pend);
		case SCH_RND_BAT_NET_ATTR:    return check_net_attr(ctx, ba, pend);
		case SCH_RND_BAT_COMP_ATTR:   return check_comp_attr(ctx, ba, pend);
		case SCH_RND_BAT_TERM_ATTR:   return check_term_attr(ctx, ba, pend);
		case SCH_RND_BAT_COMP_DEL:    return check_comp_del(ctx, ba, pend);
		case SCH_RND_BAT_COMP_ADD:    return check_comp_add(ctx, ba, pend);
		case SCH_RND_BAT_invalid:     break;
	}
	return -1;
}

long sch_rnd_backann_check(sch_rnd_backann_t *ctx)
{
	csch_project_t *prj = (csch_project_t *)ctx->sheet->hidlib.project;
	long n, pend = 0;
	int good = 1; pend = 0;

	if (prj->abst == NULL)
		good = 0;

	for(n = 0; n < ctx->list.used; n++) {
		sch_rnd_ba_t *ba = &ctx->list.array[n];
		ba->status = SCH_RND_BAS_UNKNOWN;
		if (good) {
			if (sch_rnd_backann_check_entry(ctx, ba, &pend) != 0)
				good = 0;
		}
		else
			pend++;
	}
	return pend;
}
