/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - non-graphical sheet helpers
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust 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., 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
 */

/* Maintain a cache of parsed comp/net/conn objects and compile abstract
   model from them */

#include <libcschem/config.h>

#include <stdio.h>

#include <librnd/core/plugins.h>
#include <librnd/core/error.h>
#include <librnd/core/safe_fs.h>

#include <libcschem/concrete.h>
#include <libcschem/non_graphical.h>
#include <libcschem/compile.h>
#include <libcschem/cnc_obj.h>

#include <sch-rnd/draw.h>

#include "lib_ngrp.h"

/*** context ***/
static void sch_ngrp_free_attribs(sch_ngrp_attrib_t *a)
{
	sch_ngrp_attrib_t *next;
	for(; a != NULL; a = next) {
		next = a->next;
		free(a);
	}
}

static void sch_ngrp_free_objs(sch_ngrp_obj_t *o)
{
	sch_ngrp_obj_t *next;
	for(; o != NULL; o = next) {
		next = o->next;
		sch_ngrp_free_attribs(o->attr_head);
		free(o);
	}
}

static void sch_ngrp_free_conns(sch_ngrp_conn_t *c)
{
	sch_ngrp_conn_t *next;
	for(; c != NULL; c = next) {
		next = c->next;
		free(c);
	}
}

void sch_ngrp_clean_cache(sch_ngrp_t *ctx)
{
	sch_ngrp_free_objs(ctx->comps);
	sch_ngrp_free_objs(ctx->nets);
	sch_ngrp_free_conns(ctx->conns);
	ctx->comps = NULL;
	ctx->nets = NULL;
	ctx->conns = NULL;
}

void sch_ngrp_free(sch_ngrp_t *ctx)
{
	sch_ngrp_clean_cache(ctx);
	vtp0_uninit(&ctx->lines);
	gds_uninit(&ctx->text);
	free(ctx);
}

void sch_ngrp_add_attr(sch_ngrp_attrib_t **head, const char *key, const char *val, int vl, long lineno)
{
	int kl = strlen(key);
	sch_ngrp_attrib_t *a;

	if (vl < 0)
		vl = strlen(val);

	a = malloc(sizeof(sch_ngrp_attrib_t) + kl + vl + 2);
	memcpy(a->key, key, kl+1);
	a->val = a->key+kl+1;
	memcpy(a->val, val, vl);
	a->val[vl] = '\0';

	a->lineno = lineno;
	a->next = *head;
	*head = a;
}

sch_ngrp_obj_t *sch_ngrp_add_obj(sch_ngrp_obj_t **head, const char *name, long lineno)
{
	int nl = strlen(name);
	sch_ngrp_obj_t *o = malloc(sizeof(sch_ngrp_obj_t) + nl + 1);
	memcpy(o->name, name, nl+1);
	o->attr_head = NULL;

	o->lineno = lineno;
	o->next = *head;
	*head = o;

	return o;
}

sch_ngrp_conn_t *sch_ngrp_add_conn(sch_ngrp_conn_t **head, const char *netname, const char *comp, const char *port, long lineno)
{
	int nl = strlen(netname), cl = strlen(comp), pl = strlen(port);
	sch_ngrp_conn_t *c = malloc(sizeof(sch_ngrp_conn_t) + nl + cl + pl + 3);
	memcpy(c->netname, netname, nl+1);
	c->comp = c->netname+nl+1;
	memcpy(c->comp, comp, cl+1);
	c->port = c->comp+cl+1;
	memcpy(c->port, port, pl+1);

	c->lineno = lineno;
	c->next = *head;
	*head = c;

	return c;
}

static void comp_add_attrs(const csch_sheet_t *sheet, csch_ahdr_t *dst, const sch_ngrp_attrib_t *attr_src)
{
	const sch_ngrp_attrib_t *s;

	for(s = attr_src; s != NULL; s = s->next) {
		csch_source_arg_t *src;

		src = csch_attrib_src_c(sheet->hidlib.fullpath, s->lineno, 0, "non-graphical: tEDAx");
		csch_attrib_set(&dst->attr, CSCH_ATP_USER_DEFAULT, s->key, s->val, src, NULL);
	}
}


/*** compile ***/

static csch_cgrp_t *get_src_oid(const csch_sheet_t *sheet_, long *src_oid)
{
	csch_sheet_t *sheet = (csch_sheet_t *)sheet_;
	csch_cgrp_t *cgrp;

	(*src_oid)++;
	cgrp = htip_get(&sheet->direct.id2obj, *src_oid);

	if (cgrp == NULL) {
		cgrp = calloc(sizeof(csch_cgrp_t), 1);
		csch_cobj_init(&cgrp->hdr, sheet, &sheet->direct, *src_oid, 0);
	}

	return cgrp;
}

int sch_ngrp_compile_sheet(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, const csch_sheet_t *src, sch_ngrp_t *ctx)
{
	sch_ngrp_conn_t *cn;
	sch_ngrp_comp_t *c;
	sch_ngrp_net_t *n;
	long src_oid = 0, tmp_len;
	csch_ascope_t scope;
	gds_t tmp = {0};

	gds_append_len(&tmp, hpath->prefix.array, hpath->prefix.used);
	tmp_len = hpath->prefix.used;

	/* create components */
	for(c = ctx->comps; c != NULL; c = c->next) {
		csch_acomp_t *acomp;
		csch_cgrp_t *cgrp = get_src_oid(src, &src_oid);

		acomp = csch_acomp_get_at(dst, dst->hroot, CSCH_ASCOPE_GLOBAL, c->name);
		if (acomp == NULL)
			acomp = csch_acomp_new(dst, dst->hroot, CSCH_ASCOPE_GLOBAL, c->name, c->name);
		csch_compile_add_source(cgrp, &acomp->hdr);
		comp_add_attrs(src, &acomp->hdr, c->attr_head);
		c->ahdr = &acomp->hdr;
	}

	/* create nets */
	for(n = ctx->nets; n != NULL; n = n->next) {
		csch_anet_t *anet;
		csch_cgrp_t *cgrp;
		const char *orig_name = n->name;

		scope = csch_split_name_scope(&orig_name);
		tmp.used = tmp_len;
		gds_append_str(&tmp, orig_name);

		anet = csch_anet_get_at(dst, hpath->hlev, scope, orig_name);
		if (anet == NULL)
			anet = csch_anet_new(dst, hpath->hlev, scope, tmp.array, orig_name, 1);

		cgrp = get_src_oid(src, &src_oid);;
		csch_compile_add_source(cgrp, &anet->hdr);
		comp_add_attrs(src, &anet->hdr, n->attr_head);
	}

	/* create connections */
	for(cn = ctx->conns; cn != NULL; cn = cn->next) {
		csch_acomp_t *acomp;
		csch_aport_t *aport;
		csch_cgrp_t *cgrp = get_src_oid(src, &src_oid);
		csch_anet_t *anet;
		const char *orig_name;


		acomp = csch_acomp_get(dst, cn->comp);
		if (acomp == NULL)
			acomp = csch_acomp_new(dst, dst->hroot, CSCH_ASCOPE_GLOBAL, cn->comp, cn->comp);
		aport = csch_aport_get(dst, acomp, cn->port, 1);

		orig_name = cn->netname;
		scope = csch_split_name_scope(&orig_name);
		tmp.used = tmp_len;
		gds_append_str(&tmp, orig_name);

		anet = csch_anet_get_at(dst, hpath->hlev, scope, orig_name);
		if (anet == NULL)
			anet = csch_anet_new(dst, hpath->hlev, scope, tmp.array, orig_name, 1);

		csch_compile_add_source(cgrp, &aport->hdr);
		csch_compile_connect_net_to(&anet, &aport->hdr, 1);
	}

#if 0
	/* descend into hierarchic subsheets */
	for(c = ctx->comps; c != NULL; c = c->next) {
		csch_acomp_t *acomp = (csch_acomp_t *)c->ahdr;
		const sch_ngrp_attrib_t *s;
		const char *chuuid = NULL, *chname = NULL, *chpath = NULL;
		csch_sheet_t *child_out = NULL;
		int hres;

		c->ahdr = NULL;

		for(s = c->attr_head; s != NULL; s = s->next) {
			if ((s->key[0] == 'c') && (strncmp(s->key, "cschem/child/", 13) == 0)) {
				const char *key = s->key + 13;
				if (strcmp(key, "uuid") == 0) chuuid = s->val;
				else if (strcmp(key, "name") == 0) chname = s->val;
				else if (strcmp(key, "path") == 0) chpath = s->val;
			}
		}
		
		hres = csch_hier_find_child(src, chuuid, chname, chpath, &child_out, 0);
		if (hres < 0)
			return -1;

		if (hres == 1) {
			TODO("DESCEND: call csch_compile_descend()");
			TODO("then bind ports like in compile.c compile_symbol_child_sheet() at ''Bind sheet ref sym terminals to child sheet terminals''");
		}
	}
#endif


	gds_uninit(&tmp);
	return 0;
}



/*** read ***/

int sch_ngrp_load_file(csch_sheet_t *sheet, sch_ngrp_t *ctx, const char *fn)
{
	long len, got;
	char *s;
	FILE *f;

	len = rnd_file_size(&sheet->hidlib, fn);
	if (len < 0)
		return -1;

	f = rnd_fopen(&sheet->hidlib, fn, "r");
	if (f == NULL)
		return -1;

	if ((len > ctx->text.alloced) || (len < ctx->text.used*0.9)) {
		gds_uninit(&ctx->text);
		gds_enlarge(&ctx->text, len);
	}

	got = fread(ctx->text.array, 1, len, f);
	ctx->text.array[got] = '\0';
	fclose(f);

	ctx->lines.used = 0;
	vtp0_append(&ctx->lines, ctx->text.array);
	for(s = ctx->text.array; *s != '\0'; s++) {
		if ((s[0] == '\n') && (s[1] != '\0'))
			vtp0_append(&ctx->lines, s+1);
	}

	return sch_draw_setup_non_graphical(sheet, ctx->lines.used);
}

char *sch_ngrp_draw_getline(sch_ngrp_t *ctx, long *cnt, void **cookie)
{
	char *nl = *cookie, *res;
	long next;

	if (nl != NULL)
		*nl = '\n'; /* restore previous \n */

	if (*cnt >= ctx->lines.used)
		return NULL;

	*cookie = NULL;
	next = (*cnt) + 1;
	if (next < ctx->lines.used) {
		nl = ctx->lines.array[next];
		nl--;
		if (*nl == '\n') {
			*cookie = nl;
			*nl = '\0'; /* terminate string if it ends in \n */
		}
	}

	res = ctx->lines.array[*cnt];
	(*cnt)++;
	return res;
}

/*** plugin ***/

int pplg_check_ver_lib_ngrp(int ver_needed) { return 0; }

void pplg_uninit_lib_ngrp(void)
{
}

int pplg_init_lib_ngrp(void)
{
	RND_API_CHK_VER;

	return 0;
}

