/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2019, 2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2023)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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 "config.h"

#include <genht/htip.h>
#include <genht/htsp.h>
#include <genht/hash.h>

#include <librnd/core/compat_misc.h>
#include <librnd/core/error.h>

#include "hierarchy.h"

#include "abstract.h"

static const char *atype_names[] = {
	"<invalid>",
	"net",
	"busnet",
	"buschan",
	"port",
	"busport",
	"component",
	"hub"
};

const char *csch_atype_name(csch_atype_t type)
{
	if ((type <= 0) || (type >= (sizeof(atype_names)/sizeof(atype_names[0]))))
		return atype_names[CSCH_ATYPE_INVALID];
	return atype_names[type];
}

void csch_ahdr_uninit(csch_ahdr_t *hdr)
{
	csch_attrib_uninit(&hdr->attr);
	vtp0_uninit(&hdr->srcs);
}

void csch_anet_free_fields(csch_anet_t *net)
{
	vtp0_uninit(&net->conns);
	free(net->name);
	free(net->name_loc);
	csch_ahdr_uninit(&net->hdr);
}

void csch_aport_free_fields(csch_aport_t *port)
{
	free(port->name);
	csch_ahdr_uninit(&port->hdr);
}


void csch_acomp_free_fields(csch_acomp_t *comp)
{
	htsp_entry_t *e;

	for(e = htsp_first(&comp->ports); e != NULL; e = htsp_next(&comp->ports, e)) {
		csch_aport_free_fields(e->value);
		free(e->value); e->value = NULL;
	}
	for(e = htsp_first(&comp->busports); e != NULL; e = htsp_next(&comp->busports, e)) {
		abort();
	}

	htsp_uninit(&comp->ports);
	htsp_uninit(&comp->busports);
	free(comp->name);
	free(comp->name_loc);
	csch_ahdr_uninit(&comp->hdr);
}


void csch_abstract_init(csch_abstract_t *abs)
{
	memset(abs, 0, sizeof(csch_abstract_t));
	htsp_init(&abs->nets, strhash, strkeyeq);
	htsp_init(&abs->comps, strhash, strkeyeq);
	htip_init(&abs->aid2obj, longhash, longkeyeq);
	abs->hroot = csch_hlevel_new(NULL, NULL, NULL);
	abs->next_aid = 1;
}

void csch_abstract_uninit(csch_abstract_t *abs)
{
	htsp_entry_t *e;
	long n;

	csch_hlevel_free_tree(abs->hroot);

	for(e = htsp_first(&abs->nets); e != NULL; e = htsp_next(&abs->nets, e)) {
		csch_anet_free_fields(e->value);
		free(e->value); e->value = NULL;
	}

	for(e = htsp_first(&abs->comps); e != NULL; e = htsp_next(&abs->comps, e)) {
		csch_acomp_free_fields(e->value);
		free(e->value); e->value = NULL;
	}

	for(n = 0; n < abs->orphaned_ports.used; n++) {
		csch_aport_free_fields(abs->orphaned_ports.array[n]);
		free(abs->orphaned_ports.array[n]);
	}

	htsp_uninit(&abs->nets);
	htsp_uninit(&abs->comps);
	htip_uninit(&abs->aid2obj);
	vtp0_uninit(&abs->orphaned_ports);

	memset(abs, 0, sizeof(csch_abstract_t));
}


/* Initialize an object by setting the common header fields; the only purpose
   of this function is to get compiler warnings at every caller when the header
   is extended (because the number of args would be extended here) */
RND_INLINE void csch_aobj_init(csch_abstract_t *abs, csch_ahdr_t *dst, csch_atype_t type)
{
	dst->type = type;
	csch_attrib_init(&dst->attr);
	dst->abst = abs;

	/* assign unique aid */
	dst->aid = abs->next_aid;
	abs->next_aid++;
	if (abs->next_aid < 1)
		abs->next_aid = 1;
	htip_set(&abs->aid2obj, dst->aid, dst);

	switch(type) {
		case CSCH_ATYPE_INVALID:
		case CSCH_ATYPE_BUSNET:
		case CSCH_ATYPE_BUSCHAN:
		case CSCH_ATYPE_BUSPORT:
		case CSCH_ATYPE_HUB:
			break;
		case CSCH_ATYPE_PORT: abs->new_ports = 1; break;
		case CSCH_ATYPE_NET:  abs->new_nets = 1; break;
		case CSCH_ATYPE_COMP: abs->new_comps = 1; break;
	}
}

csch_ascope_t csch_split_name_scope(const char **name)
{
	const char *nm = *name;

	if (nm[0] == '/') {
		(*name) += 1;
		return CSCH_ASCOPE_GLOBAL;
	}

	/* check this so nm[1] is safe to address */
	if (nm[0] == '\0')
		return CSCH_ASCOPE_AUTO;

	if (nm[1] == '/') {
		if (nm[0] == '.') {
			(*name) += 2;
			return CSCH_ASCOPE_SHEET_LOCAL;
		}
		if (nm[0] == '^') {
			(*name) += 2;
			return CSCH_ASCOPE_SUBTREE_AUTO;
		}
		if (nm[0] == 'v') {
			(*name) += 2;
			return CSCH_ASCOPE_SUBTREE_LOCAL;
		}
	}

	/* no usable prefix */
	return CSCH_ASCOPE_AUTO;
}

#include "abs_net.c"
#include "abs_comp.c"

csch_aport_t *csch_aport_get(csch_abstract_t *abs, csch_acomp_t *comp, const char *name, int alloc)
{
	csch_aport_t *port;

	if (comp != NULL) {
		port = htsp_get(&comp->ports, name);
		if ((port != NULL) || (!alloc))
			return port;
	}

	port = calloc(sizeof(csch_aport_t), 1);
	csch_aobj_init(abs, &port->hdr, CSCH_ATYPE_PORT);
	port->name = rnd_strdup(name);
	port->parent = comp;

	if (comp != NULL)
		htsp_set(&comp->ports, port->name, port);
	else
		vtp0_append(&abs->orphaned_ports, port);
	return port;
}


static void dump_attr(const csch_attribs_t *attr, FILE *f, const char *prefix1, const char *prefix2)
{
	htsp_entry_t *e;
	fprintf(f, "%s%sAttributes:\n", prefix1, prefix2);
	for(e = htsp_first(attr); e != NULL; e = htsp_next(attr, e)) {
		const csch_attrib_t *a = e->value;
		if (a->deleted) continue;
		if (a->val == NULL) {
			long n;
			fprintf(f, "%s%s %s={\n", prefix1, prefix2, a->key);
			for(n = 0; n < a->arr.used; n++)
				fprintf(f, "%s%s  %s\n", prefix1, prefix2, a->arr.array[n]);
			fprintf(f, "%s%s }\n", prefix1, prefix2);
		}
		else
			fprintf(f, "%s%s %s=%s\n", prefix1, prefix2, a->key, a->val);
	}
}

void csch_abstract_dump(const csch_abstract_t *abs, FILE *f, const char *prefix)
{
	htsp_entry_t *e;

	fprintf(f, "%sNets:\n", prefix);
	for(e = htsp_first(&abs->nets); e != NULL; e = htsp_next(&abs->nets, e)) {
		csch_anet_t *net = e->value;
		long n;

		fprintf(f, "%s %s\n", prefix, net->name);
		fprintf(f, "%s  ports:\n", prefix);
		for(n = 0; n < net->conns.used; n++) {
			csch_aport_t *port = net->conns.array[n];
			fprintf(f, "%s   %s-%s\n", prefix, APORT_PARENT_NAME(port), port->name);
		}
		dump_attr(&net->hdr.attr, f, prefix, "  ");
	}

	fprintf(f, "%sComponents:\n", prefix);
	for(e = htsp_first(&abs->comps); e != NULL; e = htsp_next(&abs->comps, e)) {
		htsp_entry_t *pe;
		csch_acomp_t *comp = e->value;

		fprintf(f, "%s %s\n", prefix, comp->name);

		fprintf(f, "%s  Ports:\n", prefix);
		for(pe = htsp_first(&comp->ports); pe != NULL; pe = htsp_next(&comp->ports, pe)) {
			csch_aport_t *port = pe->value;
			fprintf(f, "%s   %s\n", prefix, port->name);
			dump_attr(&port->hdr.attr, f, prefix, "    ");
		}
		dump_attr(&comp->hdr.attr, f, prefix, "  ");
	}
}
