/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2024)
 *
 *  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 <librnd/core/error.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_fs.h>
#include <librnd/core/compat_lrealpath.h>
#include <librnd/core/misc_util.h>
#include <genht/hash.h>

#include "concrete.h"
#include "cnc_grp.h"
#include "project.h"
#include "plug_library.h"

#include "hierarchy.h"

#define HSEP '/'
#define MAX_DEPTH 32

csch_sheet_t *(*csch_app_load_sheet_cb)(csch_project_t *proj, const char *path) = NULL;


csch_sheet_t *csch_hier_find_by_uuid(const csch_sheet_t *sheet, const char *uuid, int add_to_prj_as_aux)
{
	rnd_message(RND_MSG_ERROR, "cschem/child/uuid not yet implemented");
	TODO("hierarchical: implement me\n");
	return NULL;
}

/* sname is sheet's loadname (short name), name:nlen is the requested name and its strlen() */
RND_INLINE int sheet_name_eq(const char *sname, const char *name, long nlen)
{
	long slen = strlen(sname);

	if (slen < nlen) return 0; /* sheet name too short, can't match */

	if (strcmp(sname+slen-nlen, name) == 0) {
		if (slen == nlen)
			return 1;
		if ((slen > nlen) && (sname[slen-nlen-1] == '/')) /* avoid suffix match */
			return 1;
	}

	/* ignore .rs ending */
	if ((slen >= nlen+3) && (strcmp(sname+slen-3, ".rs") == 0)) {
		if (strncmp(sname+slen-nlen-3, name, nlen) == 0) {
			if ((slen-3 > nlen) && (sname[slen-nlen-3-1] == '/')) /* avoid suffix match */
				return 1;
			if (slen-3 == nlen)
				return 1; /* full length match */
		}
	}

	/* ignore .lht/sch ending for historic reasons */
	if ((slen >= nlen+4) && ((strcmp(sname+slen-4, ".lht") == 0) || (strcmp(sname+slen-4, ".sch") == 0))) {
		if (strncmp(sname+slen-nlen-4, name, nlen) == 0) {
			if ((slen-4 > nlen) && (sname[slen-nlen-4-1] == '/')) /* avoid suffix match */
				return 1;
			if (slen-4 == nlen)
				return 1; /* full length match */
		}
	}

	return 0;
}

csch_sheet_t *sch_rnd_multi_load(const char *fn, const char *fmt, int *is_project);

static csch_sheet_t *hlib_find_by_name(csch_project_t *proj, csch_lib_t *lib, const char *name, long nlen, int add_to_prj_as_aux)
{
	long n;

	if (lib == NULL)
		return NULL;

	switch(lib->type) {
		case CSCH_SLIB_STATIC:
			if (sheet_name_eq(lib->name, name, nlen)) {
				csch_sheet_t *res;

				rnd_message(RND_MSG_INFO, "Attempting to load '%s' for sheet named '%s'...\n", lib->realpath, name);
				res = csch_app_load_sheet_cb(proj, lib->realpath);
				return res;
			}
			break;

		case CSCH_SLIB_PARAMETRIC:
		case CSCH_SLIB_invalid:
			return NULL;

		case CSCH_SLIB_DIR:
			for(n = 0; n < lib->children.used; n++) {
				csch_sheet_t *res = hlib_find_by_name(proj, lib->children.array[n], name, nlen, add_to_prj_as_aux);
				if (res != NULL)
					return res;
			}
			break;
	}

	return NULL;
}

csch_sheet_t *csch_hier_find_by_name(const csch_sheet_t *sheet, const char *name, int add_to_prj_as_aux)
{
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;
	long n, nlen = strlen(name);
	csch_lib_master_t *master;
	htsp_entry_t *e;

	/* search in project first */
	for(n = 0; n < prj->hdr.designs.used; n++) {
		csch_sheet_t *sheet = prj->hdr.designs.array[n];
		if (sheet_name_eq(sheet->hidlib.loadname, name, nlen))
			return sheet;
	}

	rnd_message(RND_MSG_INFO, "Hierarchic: sheet named '%s' not found in the project, searching the hlibrary...\n", name);

	/* search the hlibrary */
	if (csch_app_load_sheet_cb == NULL)
		return NULL;
	master = csch_lib_get_master("hlibrary", 0);
	if (master == NULL)
		return NULL;

	for(e = htsp_first(&master->roots); e != NULL; e = htsp_next(&master->roots, e)) {
		csch_sheet_t *res = hlib_find_by_name((csch_project_t *)sheet->hidlib.project, e->value, name, nlen, add_to_prj_as_aux);
		if (res != NULL)
			return res;
	}

	return NULL;
}

csch_sheet_t *csch_hier_find_by_path(const csch_sheet_t *sheet, const char *path, int add_to_prj_as_aux)
{
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;
	csch_sheet_t *res = NULL;
	char *dirname, *new_path, *nr_path;
	long n;

	/* refuse invalid and absolute path */
	if ((path == NULL) || (path[0] == '/'))
		return NULL;

	dirname = rnd_dirname_real(sheet->hidlib.fullpath);
	new_path = rnd_concat(dirname, "/", path, NULL);
	free(dirname);
	nr_path = rnd_lrealpath(new_path);

	if (nr_path == NULL) {
		rnd_message(RND_MSG_ERROR, "Hierarchic: sheet with path '%s' not found on the file system...\n", new_path);
		free(new_path);
		goto done;
	}
	free(new_path);

	/* search in project first */
	for(n = 0; n < prj->hdr.designs.used; n++) {
		csch_sheet_t *sh = prj->hdr.designs.array[n];
		if (strcmp(sh->hidlib.fullpath, nr_path) == 0) {
			res = sh;
			goto done;
		}
	}

	rnd_message(RND_MSG_INFO, "Hierarchic: sheet with path '%s' not found in the project, loading from the file system...\n", path);
	res = csch_app_load_sheet_cb(prj, nr_path);

	done:;
	free(nr_path);
	return res;
}

RND_INLINE const char *get_sym_attr(csch_cgrp_t *sym, const char *key)
{
	const csch_attrib_t *aname;

	if (sym->hdr.type == CSCH_CTYPE_GRP_REF)
		aname = csch_cgrp_ref_get_attr(sym, key);
	else
		aname = csch_attrib_get(&sym->attr, key);

	if (aname == NULL)
		return NULL;

	return aname->val;
}

int csch_hier_find_sym_child(const csch_sheet_t *sheet, csch_cgrp_t *sym, csch_sheet_t **child_out, int add_to_prj_as_aux)
{
	const char *type, *val;
	csch_sheet_t *child = NULL;

	val = get_sym_attr(sym, "cschem/child/uuid");
	if (val != NULL) {
		child = csch_hier_find_by_uuid(sheet, val, add_to_prj_as_aux);
		type = "uid";
		goto yes;
	}

	val = get_sym_attr(sym, "cschem/child/name");
	if (val != NULL) {
		child = csch_hier_find_by_name(sheet, val, add_to_prj_as_aux);
		type = "name";
		goto yes;
	}

	val = get_sym_attr(sym, "cschem/child/path");
	if (val != NULL) {
		child = csch_hier_find_by_path(sheet, val, add_to_prj_as_aux);
		type = "path";
		goto yes;
	}

	/* no (known) cschem/child/ attribute */
	return 0;

	yes:;
	if (child == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to find hierarchic child-sheet referenced by %s '%s'\n", type, val);
		return -1;
	}
	*child_out = child;
	return 1;
}

int csch_hier_find_child(const csch_sheet_t *sheet, const char *chuuid, const char *chname, const char *chpath,  csch_sheet_t **child_out, int add_to_prj_as_aux)
{
	const char *type, *val;
	csch_sheet_t *child = NULL;

	if (chuuid != NULL) {
		child = csch_hier_find_by_uuid(sheet, chuuid, add_to_prj_as_aux);
		type = "uid";
		val = chuuid;
		goto yes;
	}

	if (chname != NULL) {
		child = csch_hier_find_by_name(sheet, chname, add_to_prj_as_aux);
		type = "name";
		val = chname;
		goto yes;
	}

	if (chpath != NULL) {
		child = csch_hier_find_by_path(sheet, chpath, add_to_prj_as_aux);
		type = "path";
		val = chpath;
		goto yes;
	}

	/* no (known) cschem/child/ attribute */
	return 0;

	yes:;
	if (child == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to find hierarchic child-sheet referenced by %s '%s'\n", type, val);
		return -1;
	}
	*child_out = child;
	return 1;
}


void csch_hier_path_free(csch_hier_path_t *hpath)
{
	gds_uninit(&hpath->prefix);
	hpath->hlev = NULL;
	hpath->hdepth = -1;
}

void csch_hier_path_init(csch_hier_path_t *hpath, csch_abstract_t *abst)
{
	memset(hpath, 0, sizeof(csch_hier_path_t));
	hpath->hlev = abst->hroot;
}


csch_hlevel_t *csch_hlevel_new(csch_hlevel_t *parent, csch_cgrp_t *sym, const char *name)
{
	csch_hlevel_t *hlev = calloc(sizeof(csch_hlevel_t), 1);
	if (parent != NULL) {
		hlev->parent = parent;
		gdl_append(&parent->children, hlev, link);
	}
	htsp_init(&hlev->nets, strhash, strkeyeq);
	htsp_init(&hlev->comps, strhash, strkeyeq);
	hlev->name = (name == NULL) ? NULL : rnd_strdup(name);
	hlev->hdepth = (parent == NULL) ? 0 : parent->hdepth+1;
	return hlev;
}


void csch_hlevel_free_tree(csch_hlevel_t *hlev)
{
	csch_hlevel_t *f;
	for(f = gdl_first(&hlev->children); f != NULL; f = gdl_first(&hlev->children)) {
		gdl_remove(&hlev->children, f, link);
		csch_hlevel_free_tree(f);
	}
	htsp_uninit(&hlev->nets);
	htsp_uninit(&hlev->comps);
	free(hlev->name);
	free(hlev);
}


int csch_hier_enter(csch_hier_path_t *hpath, csch_hier_temp_t *tmp, csch_cgrp_t *sym, const char *name)
{
	if (hpath->hdepth > MAX_DEPTH) {
		rnd_message(RND_MSG_ERROR, "Hierarchy recursion too deep (%ld)\n(The hierarchy graph shall not contain loops)\n", hpath->hdepth);
		return -1;
	}

	if (hpath->prefix.used > MAX_DEPTH*8) {
		rnd_message(RND_MSG_ERROR, "Hierarchy recursion too deep, prefix too long (%ld)\n(The hierarchy graph shall not contain loops)\n", hpath->prefix.used);
		return -1;
	}

	tmp->prefix_len = hpath->prefix.used;
	gds_append_str(&hpath->prefix, name);
	gds_append(&hpath->prefix, HSEP);
/*	rnd_trace("HIER: enter [%d '%s']\n", hpath->path.used+1, hpath->prefix.array);*/

	hpath->hlev = csch_hlevel_new(hpath->hlev, sym, name);
	hpath->hdepth++;

	return 0;
}

void csch_hier_leave(csch_hier_path_t *hpath, csch_hier_temp_t *tmp)
{
/*	rnd_trace("HIER: leave [%d '%s']\n", hpath->path.used, hpath->prefix.array);*/
	hpath->hlev = hpath->hlev->parent;
	hpath->hdepth--;
	hpath->prefix.used = tmp->prefix_len;
	hpath->prefix.array[hpath->prefix.used] = '\0';
	assert(hpath->hdepth >= 0);
}


void csch_hier_build_path(gds_t *prefix, csch_hlevel_t *hlev)
{
	if ((hlev == NULL) || (hlev->parent == NULL))
		return; /* root path is empty */

	while((hlev != NULL) && (hlev->name != NULL)) {
		gds_insert_len(prefix, 0, "/", 1);
		gds_insert_len(prefix, 0, hlev->name, strlen(hlev->name));
		hlev = hlev->parent;
	}
}

