/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - high level sim (non-GUI)
 *  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
 */

#include <stdio.h>
#include <stdlib.h>
#include <libcschem/config.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/safe_fs.h>
#include <liblihata/tree.h>
#include <libcschem/project.h>
#include <sch-rnd/multi.h>

#include "sim.h"

#include "sim_conf.h"

#define MKDIR_ND(outnode, parent, ntype, nname, errinstr) \
do { \
	lht_node_t *nnew; \
	lht_err_t err; \
	char *nname0 = (char *)nname; \
	if (parent->type == LHT_LIST) nname0 = rnd_concat(nname, ":0", NULL); \
	nnew = lht_tree_path_(parent->doc, parent, nname0, 1, 1, &err); \
	if (parent->type == LHT_LIST) free(nname0); \
	if ((nnew != NULL) && (nnew->type != ntype)) { \
		rnd_message(RND_MSG_ERROR, "Internal error: invalid existing node type for %s: %d, rule is NOT saved\n", nname, nnew->type); \
		errinstr; \
	} \
	else if (nnew == NULL) { \
		nnew = lht_dom_node_alloc(ntype, nname); \
		switch(parent->type) { \
			case LHT_HASH: err = lht_dom_hash_put(parent, nnew); break; \
			case LHT_LIST: err = lht_dom_list_append(parent, nnew); break; \
			default: \
				rnd_message(RND_MSG_ERROR, "Internal error: invalid parent node type for %s: %d, rule is NOT saved\n", parent->name, parent->type); \
				errinstr; \
		} \
	} \
	outnode = nnew; \
} while(0)


lht_node_t *sch_sim_get_setup(csch_project_t *prj, const char *name, int create)
{
	rnd_design_t *dsg = rnd_multi_get_current();
	gdl_iterator_t it;
	rnd_conf_listitem_t *i;

	if (name == NULL)
		return NULL;

	if ((dsg == NULL) || (dsg->project != &prj->hdr)) {
		rnd_trace("switching to project...\n");
		sch_rnd_multi_switch_to(prj->hdr.designs.array[0]);
		dsg = rnd_multi_get_current();
		if (dsg->project != &prj->hdr) {
			rnd_message(RND_MSG_ERROR, "sch_sim_get_setup(): failed to switch to project\n");
			return NULL;
		}
	}

	rnd_conflist_foreach(&SCH_SIM_CONF_SETUPS_CONLIST, &it, i) {
		lht_node_t *nsetup = i->prop.src;
		if ((nsetup != NULL) && (strcmp(nsetup->name, name) == 0))
			return nsetup;
	}

	if (create) {
		lht_node_t *nd = rnd_conf_lht_get_first_crpol(RND_CFR_PROJECT, RND_POL_OVERWRITE, 1);
		if (nd == NULL) {
			rnd_message(RND_MSG_ERROR, "Internal error: sch_sim_get_setup() failed to create root\n");
			return NULL;
		}
		MKDIR_ND(nd, nd, LHT_HASH, "plugins", goto error);
		MKDIR_ND(nd, nd, LHT_HASH, "sim", goto error);
		MKDIR_ND(nd, nd, LHT_LIST, "setups", goto error);
		MKDIR_ND(nd, nd, LHT_HASH, name, goto error);
		return nd;
	}

	return NULL;

	error:;
	rnd_message(RND_MSG_ERROR, "Internal error: sch_sim_get_setup() failed to create the config subtree\n");
	return NULL;
}

lht_node_t *sch_sim_get_output(csch_project_t *prj, const char *setup_name, const char *output_name, int create)
{
	lht_node_t *n, *noutput, *nsetup = sch_sim_get_setup(prj, setup_name, create);

	if ((nsetup == NULL) || (nsetup->type != LHT_HASH))
		return NULL;

	noutput = lht_dom_hash_get(nsetup, "output");
	if ((noutput == NULL) || (noutput->type != LHT_LIST))
		return NULL;

	for(n = noutput->data.list.first; n != NULL; n = n->next)
		if ((n->type == LHT_HASH) && (n->name != NULL) && (strcmp(n->name, output_name) == 0))
			return n;

	if (create) {
		n = lht_dom_node_alloc(LHT_HASH, output_name);
		lht_dom_list_append(noutput, n);
		return n;
	}

	return NULL;
}


int sch_sim_flush_prj_file(csch_project_t *prj)
{
	rnd_design_t *dsg = prj->hdr.designs.array[0];
	const char *try, *pfn;

	rnd_conf_makedirty(RND_CFR_PROJECT);
	rnd_conf_update(NULL, -1);

	if ((dsg->loadname == NULL) || (*dsg->loadname == '<')) {
		rnd_message(RND_MSG_ERROR, "Failed to determine project file name (save sheet first!)\n");
		return -1;
	}

	pfn = rnd_conf_get_project_conf_name(prj->hdr.fullpath, dsg->loadname, &try);
	if (pfn == NULL) {
		FILE *f = rnd_fopen(dsg, try, "a");
		if (f == NULL) {
			rnd_message(RND_MSG_ERROR, "Failed to create project file:\n%s\n", try);
			return -1;
		}
		fclose(f);
	}

	rnd_conf_save_file(dsg, prj->hdr.fullpath, dsg->loadname, RND_CFR_PROJECT, NULL);
	return 0;
}

int sch_sim_update_text_node(lht_node_t *parent_hash, const char *chname, const char *newval)
{
	lht_node_t *dst;

	if ((newval != NULL) && (*newval == '\0'))
		newval = NULL;

	if ((parent_hash == NULL) || (parent_hash->type != LHT_HASH))
		return -1;

	dst = lht_dom_hash_get(parent_hash, chname);

	if (dst == NULL) {
		if (newval != NULL) {
			/* need to create the new node */
			dst = lht_dom_node_alloc(LHT_TEXT, chname);
			dst->data.text.value = rnd_strdup(newval);
			lht_dom_hash_put(parent_hash, dst);
		}
		return 1;
	}
	else {
		const char *oldval = dst->data.text.value;
		if (newval != NULL) {
			if ((oldval == NULL) || (strcmp(oldval, newval) != 0)) {
				free(dst->data.text.value);
				dst->data.text.value = rnd_strdup(newval);
				return 1;
			}
		}
		else
			lht_tree_del(dst);
	}

	return 0;
}

lht_node_t *sch_sim_lht_dom_hash_ensure(lht_node_t *parent_hash, lht_node_type_t type, const char *node_name)
{
	lht_node_t *n;

	if ((parent_hash == NULL) || (parent_hash->type != LHT_HASH))
		return NULL;

	n = lht_dom_hash_get(parent_hash, node_name);
	if (n != NULL) {
		if (n->type == type)
			return n;
		return NULL;
	}

	n = lht_dom_node_alloc(type, node_name);
	lht_dom_hash_put(parent_hash, n);
	return n;
}

void sch_sim_lht_dom_hash_clean(lht_node_t *hash)
{
	lht_dom_iterator_t it;
	lht_node_t *n;

	while((n = lht_dom_first(&it, hash)) != NULL)
		lht_tree_del(n);
}

