/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - spice model libs
 *  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., 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
 */

/*** spice model library handling ***/

static char *spicelib_lib_lookup(ldch_ctx_t *ctx, const char *load_name, ldch_low_parser_t *low, ldch_high_parser_t *high, void *low_call_ctx, void *high_call_ctx)
{
/*	csch_hook_call_ctx_t *cctx = high_call_ctx;     for cctx->project */
/*	fgw_obj_t *obj = ctx->user_data;*/
	csch_lib_t *le;

	/* if full path is known */
	if (strchr(load_name, '/') != NULL)
		return rnd_strdup(load_name);

	TODO("we shouldn't search master, only sheet's devmap libs, but the"
	     "abstract model doesn't have sheets");
	le = csch_lib_search_master(spicelibmaster, load_name, CSCH_SLIB_STATIC);
	if (le == NULL) {
		char *load_name2 = rnd_concat(load_name, ".mod", NULL);
		le = csch_lib_search_master(spicelibmaster, load_name2, CSCH_SLIB_STATIC);
		free(load_name2);
	}
	if (le == NULL) {
		char *load_name2 = rnd_concat(load_name, ".prm", NULL);
		le = csch_lib_search_master(spicelibmaster, load_name2, CSCH_SLIB_STATIC);
		free(load_name2);
	}
/*	rnd_trace("spicelib lookup: %s -> %p\n", load_name, le);*/

	return le == NULL ? NULL : rnd_strdup(le->realpath);
}

/* effectively a test-parse */
csch_lib_type_t spicelib_mod_file_type(rnd_design_t *hl, const char *fn)
{
	FILE *f;
	int n;
	csch_lib_type_t res = CSCH_SLIB_invalid;

	f = rnd_fopen(hl, fn, "r");
	if (f == NULL)
		return res;

	for(n = 0; n < 64; n++) {
		char *s, line[1024];
		s = fgets(line, sizeof(line), f);
		if (s == NULL) break;
		if ((strstr(s, ".model") != NULL) || (strstr(s, ".MODEL") != NULL)) {
			res = CSCH_SLIB_STATIC;
			break;
		}
	}
	fclose(f);
	return res;
}


static char *spicelib_mod_realpath(rnd_design_t *hl, const char *root)
{
	/* accept only non-prefixed paths for now */
	if (strchr(root, '@') != NULL)
		return NULL;

	return csch_lib_fs_realpath(hl, root);
}

static int spicelib_mod_map(rnd_design_t *hl, csch_lib_t *root_dir)
{
	gds_t tmp = {0};
	gds_append_str(&tmp, root_dir->realpath);
	csch_lib_fs_map(hl, &be_spicelib_mod, root_dir, &tmp, spicelib_mod_file_type);
	gds_uninit(&tmp);
	return 0;
}



/* Load (and cache-parse) a spicelib from the external libs */
static const char *spicelib_get_extlib(spicelib_ctx_t *ctx, const char *spicelib_name, csch_hook_call_ctx_t *cctx, int save_in_local, csch_project_t *for_project)
{
	ldch_data_t *data;
	spicelib_t *spicelib;
	gds_t *res;
	long n;

	data = ldch_load_(&ctx->spicelibs, spicelib_name, ctx->low_parser, ctx->high_parser, NULL, cctx);
	if (data == NULL)
		return NULL;

	spicelib = (spicelib_t *)&data->payload;
	res = spicelib->text;

	if (save_in_local) {
/*		rnd_trace("spicelib: inserting %s in local libs:\n", spicelib_name);*/
		if (for_project != NULL) {
			for(n = 0; n < for_project->hdr.designs.used; n++) {
				csch_sheet_t *sheet = for_project->hdr.designs.array[n];
				spicelib_set_in_loclib(sheet, spicelib_name, res);
			}
		}
		else {
			for(n = 0; n < ctx->ssyms.used; n++) {
				csch_cgrp_t *sym = ctx->ssyms.array[n];
/*			rnd_trace("         %s\n", sym->hdr.sheet->hidlib.fullpath);*/
				rnd_message(RND_MSG_INFO, "spicelib: inserting %s in local lib of %s\n", spicelib_name, sym->hdr.sheet->hidlib.fullpath);
				spicelib_set_in_loclib(sym->hdr.sheet, spicelib_name, res);
			}
		}
	}

	return res == NULL ? NULL : res->array;
}

/* Look up and return component attributes for the component's spicelib name:
   - first look in the local lib; if present, load from there
   - if not, look in the library and import into the local lib
   - return NULL if both fail
*/
static const char *spicelib_get_from_any_lib_for_comp(spicelib_ctx_t *ctx, csch_acomp_t *comp, csch_attrib_t *adm, void *ucallctx)
{
	csch_cgrp_t *res_sym = NULL;
	const char *spicelib_name, *res = NULL, *loc_dm;
	long n;

	if ((adm == NULL) || (adm->deleted)) return NULL;
	spicelib_name = adm->val;

	if (spicelib_name == NULL) {
		rnd_message(RND_MSG_ERROR, "spice/model attribute should be a string in component %s\n", comp->name);
		return NULL;
	}

	if (*spicelib_name == '\0') /* empty value is okay, it means no spicelib */
		return NULL;

	/* figure which symbol(s) have this spicelib */
	ctx->ssyms.used = 0;
	for(n = 0; n < comp->hdr.srcs.used; n++) {
		csch_cgrp_t *sym = comp->hdr.srcs.array[n];
		const char *cdmn;
		
		if (sym == NULL)
			continue;

		cdmn = csch_attrib_get_str(&sym->attr, "spice/model");
/*rnd_trace("spicelib: comp: %s spicelib: %s sym's: '%s'\n", comp->name, spicelib_name, cdmn);*/
		if ((cdmn == NULL) || (strcmp(cdmn, spicelib_name) != 0))
			continue;

		vtp0_append(&ctx->ssyms, sym);
	}

	if (ctx->ssyms.used == 0) {
		rnd_message(RND_MSG_ERROR, "Spicelib internal error in component %s: no source symbol has the right spicelib attribute\n", comp->name);
		return NULL;
	}

	/* if there are more than one, compare the relevant ones and remember the first */
	for(n = 0; n < ctx->ssyms.used; n++) {
		csch_cgrp_t *sym = ctx->ssyms.array[n];

		loc_dm = spicelib_get_from_loclib(sym->hdr.sheet, spicelib_name);
		if (res != NULL) {
			if (rnd_strcasecmp(res, loc_dm) != 0) {
				rnd_message(RND_MSG_ERROR, "Spicelib local lib sync error sheet %s and %s has different copy of spicelib %s\nPlease sync your local libs before compiling!\n", sym->hdr.sheet->hidlib.fullpath, res_sym->hdr.sheet->hidlib.fullpath, spicelib_name);
				return NULL;
			}
		}
		else {
			res = loc_dm;
			if (res != NULL)
				res_sym = sym;
		}
	}

	if (res != NULL) {
/*		rnd_trace("spicelib: served %s from local lib of sheet %s\n", spicelib_name, res_sym->hdr.sheet->hidlib.fullpath);*/
		return res;
	}

/*	rnd_trace("spicelib: %s is not in our local libs\n", spicelib_name);*/
	res = spicelib_get_extlib(ctx, spicelib_name, ucallctx, 1, NULL);
	if (res == NULL) {
		rnd_message(RND_MSG_ERROR, "Spicelib %s not found in the library for component %s\n", spicelib_name, comp->name);
		return NULL;
	}

	return res;
}

static const char *spicelib_get_from_any_lib_for_project(spicelib_ctx_t *ctx, csch_project_t *prj, const char *model_name, void *ucallctx)
{
	const char *res = NULL;
	csch_sheet_t *res_sheet = NULL;
	long n;

	/* if there are more than one, compare the relevant ones and remember the first */
	for(n = 0; n < prj->hdr.designs.used; n++) {
		csch_sheet_t *sheet = prj->hdr.designs.array[n];
		const char *loc_dm;

		loc_dm = spicelib_get_from_loclib(sheet, model_name);
		if (res != NULL) {
			if (rnd_strcasecmp(res, loc_dm) != 0) {
				rnd_message(RND_MSG_ERROR, "Spicelib local lib sync error sheet %s and %s has different copy of spicelib %s\nPlease sync your local libs before compiling!\n", sheet->hidlib.fullpath, res_sheet->hidlib.fullpath, model_name);
				return NULL;
			}
		}
		else {
			res = loc_dm;
			if (res != NULL)
				res_sheet = sheet;
		}
	}

	if (res != NULL) {
/*		rnd_trace("spicelib: served %s from local lib of sheet %s\n", model_name, res_sheet->hidlib.fullpath);*/
		return res;
	}

/*	rnd_trace("spicelib: %s is not in our local libs\n", model_name);*/
	res = spicelib_get_extlib(ctx, model_name, ucallctx, 1, prj);
	if (res == NULL) {
		rnd_message(RND_MSG_ERROR, "Spicelib %s not found in the library (search scope: project)\n", model_name);
		return NULL;
	}

	return res;
}
