/*
 *                            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., 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
 */

#include <libcschem/config.h>

#include <librnd/core/conf.h>
#include <librnd/core/safe_fs.h>

#include <libcschem/abstract.h>
#include <libcschem/project.h>
#include <libcschem/event.h>
#include "sim.h"
#include "sim_conf.h"
#include "util.h"

#include "actions.h"


int sch_sim_activate(csch_project_t *prj, const char *sim_setup_name, const char *view_name, int compile_now)
{
	long view_id;

	/* this also switches to the first sheet of the project if needed */
	if (sch_sim_get_setup(prj, sim_setup_name, 0) == NULL) {
		rnd_message(RND_MSG_ERROR, "sch_sim_activate: no such simulation setup: '%s'\n", sim_setup_name);
		return -1;
	}

	view_id = csch_view_get_id(prj, view_name);

	if (view_id < 0) {
		rnd_message(RND_MSG_ERROR, "sch_sim_activate: no such view: '%s'\n", view_name);
		return -1;
	}

	rnd_conf_set(RND_CFR_CLI, "plugins/sim/active_setup", 0, sim_setup_name, RND_POL_OVERWRITE);
	csch_view_activate(prj, view_id);

	if (compile_now) /* compile immediately */
		rnd_actionva(rnd_multi_get_current(), "CompileProject", NULL);
	else /* trigger autocompilation restart */
		rnd_event(rnd_multi_get_current(), CSCH_EVENT_SHEET_EDITED, NULL);

	return 0;
}


sch_sim_setup_t *sch_sim_run_prepare(csch_project_t *prj, const char *setup_name)
{
	sch_sim_exec_t *se = sch_sim_get_sim_exec(prj, -1);
	sch_sim_setup_t *ssu;
	lht_node_t *nsetup, *noutput, *n;
	int res = -1;

	if (se == NULL) {
		rnd_message(RND_MSG_ERROR, "sim_dlg_run(): view does not have simulator execution bindings\n");
		return NULL;
	}

	nsetup = sch_sim_get_setup(prj, setup_name, 0);
	if ((nsetup == NULL) || (nsetup->type != LHT_HASH)) {
		rnd_message(RND_MSG_ERROR, "sim_dlg_run(): failed to find setup called '%s'\n", setup_name);
		return NULL;
	}

	noutput = lht_dom_hash_get(nsetup, "output");
	if ((noutput == NULL) || (noutput->type != LHT_LIST)) {
		rnd_message(RND_MSG_ERROR, "sim_dlg_run(): failed to find output list in setup called '%s'\n", setup_name);
		return NULL;
	}

	ssu = se->alloc();
	if (ssu == NULL) {
		rnd_message(RND_MSG_ERROR, "sim_dlg_run(): failed to allocate simulator setup in execution\n");
		return NULL;
	}

	if (se->add_circuit(ssu) != 0) {
		rnd_message(RND_MSG_ERROR, "sim_dlg_run(): failed to add the circuit to the simulation setup\n");
		goto error;
	}


	res = 0;

	/* add output nodes */
	for(n = noutput->data.list.first; n != NULL; n = n->next) {
		int erra, errp;
		sch_sim_analysis_t *analysis;
		sch_sim_presentation_t *pres;

		if (n->type != LHT_HASH) {
			rnd_message(RND_MSG_ERROR, "sim_dlg_run(): output '%s' in sim setup '%s' is not a hash (ignoring node)\n", n->name, setup_name);
			continue;
		}

		analysis = calloc(sizeof(sch_sim_analysis_t), 1);
		pres = calloc(sizeof(sch_sim_presentation_t), 1);
		erra = sch_sim_analysis_build(analysis, prj->abst, n, 0);
		if (erra != 0)
			rnd_message(RND_MSG_ERROR, "sim_dlg_run(): output '%s' in sim setup '%s': failed to parse analysis\n", n->name, setup_name);
		errp = sch_sim_presentation_build(pres, prj->abst, n, 0);
		if (errp != 0)
			rnd_message(RND_MSG_ERROR, "sim_dlg_run(): output '%s' in sim setup '%s': failed to parse presentation\n", n->name, setup_name);

		if (erra || errp) {
			sch_sim_analysis_free(analysis);
			sch_sim_presentation_free(pres);
			free(analysis);
			free(pres);
			res = -1;
		}
		else if (se->add_output(ssu, analysis, pres) != 0) {
			rnd_message(RND_MSG_ERROR, "sim_dlg_run(): output '%s' in sim setup '%s': failed to add output to the simulation\n", n->name, setup_name);
			res = -1;
		}
	}


	error:;
	if (res != 0) {
		se->free(ssu);
		return NULL;
	}
	return ssu;
}

int sch_sim_exec(csch_project_t *prj, sch_sim_setup_t *ssu)
{
	sch_sim_exec_t *se = sch_sim_get_sim_exec(prj, -1);
	return se->exec(prj, ssu);
}

void sch_sim_free(csch_project_t *prj, sch_sim_setup_t *ssu)
{
	sch_sim_exec_t *se = sch_sim_get_sim_exec(prj, -1);
	se->free(ssu);
}

static const char *x_axis_name(lht_node_t *nantype, const char **prev)
{
	static const char *unknown = "UNKNOWN";
	sch_sim_analysis_type_t ty;

	if ((nantype == NULL) || (nantype->type != LHT_TEXT))
		return unknown;

	ty = sch_sim_str2analysis_type(nantype->data.text.value);
	if (ty == SCH_SIMAN_invalid)
		return unknown;

	if (ty == SCH_SIMAN_PREVIOUS) {
		if (*prev == NULL)
			*prev = unknown;
		return *prev;
	}

	*prev = sch_siman_x_axis_name[ty];
	return *prev;
}


int sch_sim_save_text(rnd_design_t *dsg, sch_sim_setup_t *ssu, const char *setup_name, const char *ofn)
{
	csch_project_t *prj = (csch_project_t *)dsg->project;
	sch_sim_exec_t *se = sch_sim_get_sim_exec(prj, -1);
	FILE *f;
	lht_node_t *nsetup, *nout, *no;
	int idx;
	const char *prev_xname = NULL;

	nsetup = sch_sim_get_setup(prj, setup_name, 0);
	if ((nsetup == NULL) || (nsetup->type != LHT_HASH)) {
		rnd_message(RND_MSG_ERROR, "sim_save: no such sim setup: %s\n", setup_name);
		return -1;
	}

	nout = lht_dom_hash_get(nsetup, "output");
	if ((nout == NULL) || (nout->type != LHT_LIST)) {
		rnd_message(RND_MSG_ERROR, "sim_save: invalid output node in setup: %s\n", setup_name);
		return -1;
	}

	f = rnd_fopen(dsg, ofn, "w");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "sim_save: failed to open %s for write\n", ofn);
		return -1;
	}

	fprintf(f, "Simulation setup: %s\n", setup_name);
	for(no = nout->data.list.first, idx = 0; no != NULL; no = no->next, idx++) {
		lht_node_t *npres, *nanalysis, *nantype, *n;
		lht_dom_iterator_t it;

		fprintf(f, "\n Output: %s\n", no->name);

		if (no->type != LHT_HASH) continue;

		nanalysis = lht_dom_hash_get(no, "analysis");
		nantype = NULL;
		if ((nanalysis != NULL) && (nanalysis->type == LHT_HASH)) {
			nantype = lht_dom_hash_get(nanalysis, "type");
			fprintf(f, "  analysis\n");
			fprintf(f, "   config begin\n");
			for(n = lht_dom_first(&it, nanalysis); n != NULL; n = lht_dom_next(&it))
				if (n->type == LHT_TEXT)
					fprintf(f, "    %s=%s\n", n->name, n->data.text.value);
			fprintf(f, "   config end\n");
		}

		npres = lht_dom_hash_get(no, "presentation");
		if ((npres != NULL) && (npres->type == LHT_HASH)) {
			lht_node_t *nprops;

			fprintf(f, "  presentation\n");
			fprintf(f, "   config begin\n");
			for(n = lht_dom_first(&it, npres); n != NULL; n = lht_dom_next(&it))
				if (n->type == LHT_TEXT)
					fprintf(f, "    %s=%s\n", n->name, n->data.text.value);
			fprintf(f, "   config end\n");

			nprops = lht_dom_hash_get(npres, "props");
			if ((nprops != NULL) && (nprops->type == LHT_LIST)) {
				fprintf(f, "   props begin (columns)\n");
				fprintf(f, "    x: %s\n", x_axis_name(nantype, &prev_xname));
				for(n = nprops->data.list.first; n != NULL; n = n->next) {
					if (n->type == LHT_TEXT)
						fprintf(f, "    %s\n", n->data.text.value);
					else
						fprintf(f, "    <invalid node type>\n");
				}
				fprintf(f, "   props end\n");
			}
		}

		if (se != NULL) {
			void *stream = se->result_open(prj, ssu, idx);
			if (stream != NULL) {
				vts0_t cols = {0};
				fprintf(f, "  data begin (first column is position on the x axis, the remaining columns are y values)\n");
				while(se->result_read(ssu, stream, &cols) == 0) {
					long n;
					fprintf(f, "   %s", cols.array[cols.used-1]);
					for(n = 0; n < cols.used-1; n++)
						fprintf(f, "	%s", cols.array[n]);
					fprintf(f, "\n");
				}
				fprintf(f, "  data end\n");
				se->result_close(ssu, stream);
			}
		}

	}

	fclose(f);
	return 0;
}

const char csch_acts_SimActivate[] = "SimActivate(setup_name, view_name)";
const char csch_acth_SimActivate[] = "Activate the simulation for the setup_name+veiw_name combo; setup_name is the name of the sim setup to use, view name is the name of a view that has a suitable sim target. Triggers timed autocompile.\n";
fgw_error_t csch_act_SimActivate(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *dsg = RND_ACT_DESIGN;
	csch_project_t *prj = (csch_project_t *)dsg->project;
	const char *setup_name, *view_name;

	RND_ACT_CONVARG(1, FGW_STR, SimActivate, setup_name = argv[1].val.str);
	RND_ACT_CONVARG(2, FGW_STR, SimActivate, view_name = argv[2].val.str);

	RND_ACT_IRES(sch_sim_activate(prj, setup_name, view_name, 0));
	return 0;
}

const char csch_acts_SimRun[] = "SimRun(setup_name, view_name, [output_filename])";
const char csch_acth_SimRun[] = "Activate and run the simulation, see SimActivate()\n";
fgw_error_t csch_act_SimRun(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *dsg = RND_ACT_DESIGN;
	csch_project_t *prj = (csch_project_t *)dsg->project;
	const char *setup_name, *view_name, *outfn = "sim.txt";
	sch_sim_setup_t *ssu;

	RND_ACT_CONVARG(1, FGW_STR, SimRun, setup_name = argv[1].val.str);
	RND_ACT_CONVARG(2, FGW_STR, SimRun, view_name = argv[2].val.str);
	RND_ACT_MAY_CONVARG(3, FGW_STR, SimRun, outfn = argv[3].val.str);

	if (sch_sim_activate(prj, setup_name, view_name, 1) != 0) {
		rnd_message(RND_MSG_ERROR, "Failed to activate simulation\n");
		RND_ACT_IRES(-1);
		return 0;
	}

	RND_ACT_IRES(0);

	ssu = sch_sim_run_prepare(prj, setup_name);
	if (sch_sim_exec(prj, ssu) != 0) {
		rnd_message(RND_MSG_ERROR, "Simulation failed\n");
		RND_ACT_IRES(-1);
	}

	if (sch_sim_save_text(dsg, ssu, setup_name, outfn) == 0)
		rnd_message(RND_MSG_INFO, "Simulation output written to file %s\n", outfn);

	sch_sim_free(prj, ssu);
	return 0;
}

static rnd_action_t sim_action_list[] = {
	{"SimActivate", csch_act_SimActivate, csch_acth_SimActivate, csch_acts_SimActivate},
	{"SimRun", csch_act_SimRun, csch_acth_SimRun, csch_acts_SimRun}
};

void sch_sim_uninit_act(const char *sim_cookie)
{
	rnd_remove_actions_by_cookie(sim_cookie);
}

void sch_sim_init_act(const char *sim_cookie)
{
	RND_REGISTER_ACTIONS(sim_action_list, sim_cookie);
}

