/*
 *                            COPYRIGHT
 *
 *  cschem - 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 <stdio.h>
#include <stdlib.h>
#include <libfungw/fungw.h>
#include <libcschem/config.h>
#include <libcschem/project.h>
#include <librnd/core/actions.h>
#include <librnd/core/error.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/plugins.h>
#include <librnd/core/misc_util.h>

#include "sim.h"

#include "sim_conf.h"
#include "actions.h"
#include "conf_internal.c"

static const char sim_cookie[] = "sim";

conf_sim_t sch_sim_conf;

const char *sch_siman_names[] = {
	"op",
	"tran_lin",
	"ac_dec",
	"ac_oct",
	"ac_lin",
	"dc_lin",
	"dc_disto_dec",
	"dc_disto_oct",
	"dc_disto_lin",
	"dc_noise_dec",
	"previous",
	NULL
};

const char *sch_siman_description[] = {
	"op: dc operating point",
	"transient (linear)",
	"ac (dec)",
	"ac (oct)",
	"ac (linear)",
	"dc (linear)",
	"dc distortion (dec)",
	"dc distortion (oct)",
	"dc distortion (lin)",
	"dc noise (lin)",
	"previous analysis",
	NULL
};

const char *sch_siman_x_axis_name[] = {
	"n/a (op)",
	"time [s]",
	"freq [Hz]",
	"freq [Hz]",
	"freq [Hz]",
	"input value",
	"freq [Hz]",
	"freq [Hz]",
	"freq [Hz]",
	"n/a (noise)",
	"n/a (prev)",
	NULL
};

const int sch_siman_x_axis_log[] = {
	-1,
	0,
	10,
	8,
	0,
	0,
	10,
	8,
	0,
	0,
	-1,
	-1,
	-1
};

const char *sch_simpres_names[] = {
	"print",
	"plot",
	NULL
};

const char *sch_simmod_type_names[] = {
	"add",
	"omit",
	"edit_attr",
	"disconn",
	"temp",
	NULL
};

const char *sch_simmod_dev_names[] = {
	"V",
	"I",
	"R",
	"C",
	"L",
	NULL
};

const int sch_sim_device_has_ac[] = {
	/* V */ 1,
	/* I */ 1,
	/* R */ 0,
	/* C */ 0,
	/* L */ 0
};

const int sch_sim_device_has_tdf[] = {
	/* V */ 1,
	/* I */ 1,
	/* R */ 0,
	/* C */ 0,
	/* L */ 0
};

const char *sch_simmod_target_type_names[] = {
	"comp",
	"port",
	"net",
	NULL
};

const char *sch_simmod_tdf_names[] = {
	"none",
	"pulse",
	"sin",
	"exp",
	"pwl",
	NULL
};

static const sch_sim_mod_tdf_param_t tdf_none[] = {
	{0}
};

static const sch_sim_mod_tdf_param_t tdf_pulse[] = {
	{"V1",  "Initial value [V,A]", 0},
	{"V2",  "Pulsed value [V,A]", 0},
	{"TD",  "Delay time [sec]", 1},
	{"TR",  "Rise time [sec]", 1},
	{"TF",  "Fall time [sec]", 1},
	{"PW",  "Pulse width [sec]", 1},
	{"PER", "Period [sec]", 1},
	{0}
};

static const sch_sim_mod_tdf_param_t tdf_sin[] = {
	{"VO",    "Offset (vertical) [V,A]", 0},
	{"VA",    "Amplitude [V,A]", 0},
	{"FREQ",  "Frequency [Hz]", 1},
	{"TD",    "Delay [sec]", 1},
	{"THETA", "Damping factor [sec]", 1},
	{0}
};

static const sch_sim_mod_tdf_param_t tdf_exp[] = {
	{"V1",    "Initial value [V,A]", 0},
	{"V2",    "Pulsed value [V,A]", 0},
	{"TD1",   "rise delay time [sec]", 1},
	{"TAU1",  "rise time constant [sec]", 1},
	{"TD2",   "fall delay time [sec]", 1},
	{"TAU2",  "fall time constant [sec]", 1},
	{0}
};

static const sch_sim_mod_tdf_param_t tdf_pwl[] = {
	{"TAB",   "space sep list of time value pairs [sec V,A]", 0},
	{"r",     "repeat initial section [sec]", 1}, /* empty or -1: no repeat; else */
	{"td",    "delay the whole signal [sec]", 1},
	{0}
};


const sch_sim_mod_tdf_param_t *sch_sim_mod_tdf_params[] = {
	tdf_none,
	tdf_pulse,
	tdf_sin,
	tdf_exp,
	tdf_pwl,
	NULL
};

sch_sim_exec_t *sch_sim_get_sim_exec(csch_project_t *proj, int viewid)
{
	csch_view_t *view;
	void **v;
	int argc = 1;
	long n;
	fgw_arg_t argv[2], ares;

	if (viewid < 0)
		viewid = proj->curr;

	v = vtp0_get(&proj->views, viewid, 0);
	if (v == NULL)
		return NULL;
	view = *v;

	argv[1].type = 0; /* avoid invalid memory handling in corner case */

	for(n = 0; n < view->engines.used; n++) {
		csch_view_eng_t *eng = view->engines.array[n];
		fgw_func_t *hk = fgw_func_lookup_in(eng->obj, "sim_exec_get");

		if (hk == NULL)
			continue;

		ares.type = FGW_PTR | FGW_STRUCT;
		ares.val.ptr_void = NULL;
		argv[0].type = FGW_FUNC;
		argv[0].val.argv0.func = hk;
		if (hk->func(&ares, argc, argv) == 0) {
			if ((ares.type & (FGW_PTR | FGW_STRUCT)) == (FGW_PTR | FGW_STRUCT)) {
				void *tmp = ares.val.ptr_void;
				ares.val.ptr_void = NULL;
				fgw_argv_free(&view->fgw_ctx, argc, argv);
				fgw_arg_free(&view->fgw_ctx, &ares);
				return tmp;
			}
			fgw_arg_free(&view->fgw_ctx, &ares);
		}
	}

	fgw_argv_free(&view->fgw_ctx, argc, argv);
	return NULL;
}

sch_sim_analysis_type_t sch_sim_str2analysis_type(const char *stype)
{
	if ((stype == NULL) || (*stype == '\0'))
		return SCH_SIMAN_invalid;

	if ((stype[0] == 'o') && (stype[1] == 'p') && (stype[2] == '\0'))
		return SCH_SIMAN_OP;

	if (stype[0] == 't') {
		stype++;
		if (strcmp(stype, "ran") == 0) return SCH_SIMAN_TRAN_LIN;
		if (strcmp(stype, "ran_lin") == 0) return SCH_SIMAN_TRAN_LIN;
		return SCH_SIMAN_invalid;
	}

	if ((stype[0] == 'a') && (stype[1] == 'c') && (stype[2] == '_')) {
		stype += 3;
		if (strcmp(stype, "dec") == 0) return SCH_SIMAN_AC_DEC;
		if (strcmp(stype, "oct") == 0) return SCH_SIMAN_AC_OCT;
		if (strcmp(stype, "lin") == 0) return SCH_SIMAN_AC_LIN;
		return SCH_SIMAN_invalid;
	}

	if ((stype[0] == 'd') && (stype[1] == 'c') && (stype[2] == '_')) {
		stype += 3;
		if (strcmp(stype, "lin") == 0) return SCH_SIMAN_DC_LIN;
		if (stype[0] == 'd') {
			stype++;
			if (strcmp(stype, "isto_dec") == 0) return SCH_SIMAN_DC_DISTO_DEC;
			if (strcmp(stype, "isto_oct") == 0) return SCH_SIMAN_DC_DISTO_OCT;
			if (strcmp(stype, "isto_lin") == 0) return SCH_SIMAN_DC_DISTO_LIN;
			return SCH_SIMAN_invalid;
		}
		if (strcmp(stype, "noise_dec") == 0) return SCH_SIMAN_DC_NOISE_DEC;

		return SCH_SIMAN_invalid;
	}

	if ((stype[0] == 'p') && (strcmp(stype, "previous") == 0))
		return SCH_SIMAN_PREVIOUS;

	return SCH_SIMAN_invalid;
}

sch_sim_mod_type_t sch_sim_str2mod_type(const char *mtype)
{
	if (mtype == NULL)
		return SCH_SIMOD_invalid;

	switch(*mtype) {
		case 'a': if (strcmp(mtype, "add") == 0) return SCH_SIMOD_ADD;
		case 'o': if (strcmp(mtype, "omit") == 0) return SCH_SIMOD_OMIT;
		case 'e': if (strcmp(mtype, "edit_attr") == 0) return SCH_SIMOD_EDIT_ATTR;
		case 'd': if (strcmp(mtype, "disconn") == 0) return SCH_SIMOD_DISCON;
		case 't': if (strcmp(mtype, "temp") == 0) return SCH_SIMOD_TEMP;
	}

	return SCH_SIMOD_invalid;
}

sch_sim_mod_device_t sch_sim_str2mod_device(const char *mdev)
{
	if ((mdev == NULL) || (mdev[0] == '\0') || (mdev[1] != '\0'))
		return SCH_SIMDEV_invalid;

	switch(*mdev) {
		case 'V': return SCH_SIMDEV_V;
		case 'I': return SCH_SIMDEV_I;
		case 'R': return SCH_SIMDEV_R;
		case 'C': return SCH_SIMDEV_C;
		case 'L': return SCH_SIMDEV_L;
	}

	return SCH_SIMDEV_invalid;
}

sch_sim_presentation_type_t sch_sim_str2presentation_type(const char *ptype)
{
	if ((ptype == NULL) || (*ptype == '\0'))
		return SCH_SIMPRES_invalid;

	switch(*ptype) {
		case 'p':
			if (strcmp(ptype, "print") == 0) return SCH_SIMPRES_PRINT;
			if (strcmp(ptype, "plot") == 0) return SCH_SIMPRES_PLOT;
			return SCH_SIMPRES_invalid;
	}

	return SCH_SIMPRES_invalid;
}

sch_sim_mod_tdf_t sch_sim_str2mod_tdf(const char *tdf)
{
	if ((tdf == NULL) || (*tdf == '\0'))
		return SCH_SIMTDF_NONE;

	switch(*tdf) {
		case 'n': if (strcmp(tdf, "none") == 0) return SCH_SIMTDF_NONE;
		case 's': if (strcmp(tdf, "sin") == 0) return SCH_SIMTDF_SIN;
		case 'e': if (strcmp(tdf, "exp") == 0) return SCH_SIMTDF_EXP;
		case 'p':
			if (strcmp(tdf, "pulse") == 0) return SCH_SIMTDF_PULSE;
			if (strcmp(tdf, "pwl") == 0) return SCH_SIMTDF_PWL;
			break;
	}

	return SCH_SIMTDF_invalid;
}

sch_sim_mod_target_type_t sch_sim_str2mod_target_type(const char *typ)
{
	if ((typ == NULL) || (*typ == '\0'))
		return SCH_SIMTT_invalid;

	switch(*typ) {
		case 'c': if (strcmp(typ, "comp") == 0) return SCH_SIMTT_COMP;
		case 'p': if (strcmp(typ, "port") == 0) return SCH_SIMTT_PORT;
		case 'n': if (strcmp(typ, "net") == 0) return SCH_SIMTT_NET;
	}

	return SCH_SIMTT_invalid;
}

#ifndef DOC_ONLY

int pplg_check_ver_sim(int ver_needed) { return 0; }

void pplg_uninit_sim(void)
{
	rnd_conf_plug_unreg("plugins/sim/", sim_conf_internal, sim_cookie);
	sch_sim_uninit_act(sim_cookie);
}

int pplg_init_sim(void)
{
	RND_API_CHK_VER;

	rnd_conf_plug_reg(sch_sim_conf, sim_conf_internal, sim_cookie);
#define conf_reg(field,isarray,type_name,cpath,cname,desc,flags) \
	rnd_conf_reg_field(sch_sim_conf, field,isarray,type_name,cpath,cname,desc,flags);
#include "sim_conf_fields.h"

	sch_sim_init_act(sim_cookie);

	return 0;
}

#endif
