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

/* Simulation setup, output config edit dialog, #included */

#include <stddef.h>

typedef struct {
	int wbox;    /* widget of the whole box */
	int winp;    /* widget of the main input */
	int winp2;   /* widget of the the secondary input (neg ports) */
	int wopt;    /* the "optional" text */
	int is_int;  /* if input is an integer */
	size_t req_offs;
	const char *conf_name, *conf_name2;
} ocfg_field_t;

#define NUM_FIELDS 8

typedef struct ocfg_dlg_ctx_s {
	RND_DAD_DECL_NOINIT(dlg)
	csch_project_t *prj;
	char *setup_name;
	lht_node_t *noutput, *nanalysis, *npresentation;
	ocfg_field_t fld_analisys[NUM_FIELDS];
	int watype;
	int wptype, wplist;
} ocfg_dlg_ctx_t;

static void outcfg_uninit(ocfg_dlg_ctx_t *ctx)
{
	free(ctx->setup_name);
}

static void outcfg_conf2dlg_analysis(ocfg_dlg_ctx_t *ctx)
{
	sch_sim_analysis_type_t type;
	const sch_sim_analysis_field_tab_t *tab, tab_inval = {0};
	const char *tmp;
	ocfg_field_t *fld;
	rnd_hid_attr_val_t hv;
	int n;

	tmp = get_path_str(ctx->nanalysis, "type", NULL, NULL);
	type = sch_sim_str2analysis_type(tmp);
	tab = sch_sim_get_analysis_fieldreq(type);
	if (tab == NULL)
		tab = &tab_inval;

	hv.lng = type;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->watype, &hv);

	for(n = 0, fld = ctx->fld_analisys; n < NUM_FIELDS; n++, fld++) {
		sch_sim_field_req_t *req = (sch_sim_field_req_t *)((char *)tab + fld->req_offs);
		int hide, opt;

		switch(*req) {
			case SCH_SIMREQ_NO: hide = 1; break;
			case SCH_SIMREQ_MANDATORY: hide = 0; opt = 0; break;
			case SCH_SIMREQ_OPTIONAL: hide = 0; opt = 1; break;
		}

		tmp = get_path_str(ctx->nanalysis, fld->conf_name, NULL, NULL);
		if (fld->is_int) {
			if (tmp != NULL)
				hv.lng = strtol(tmp, NULL, 0);
			else
				hv.lng = 10; /* default val */
		}
		else {
			if (tmp == NULL)
				tmp = "";
			hv.str = tmp;
		}

		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, fld->winp, &hv);

		if (fld->wbox != 0)
			rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, fld->wbox, hide);
		if (fld->wopt != 0)
			rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, fld->wopt, !opt);
	}
}

static int outcfg_dlg2conf_analysis(ocfg_dlg_ctx_t *ctx)
{
	int n, changed = 0;
	const char *santype;
	const char *inp;
	char inp_tmp[128];
	const sch_sim_analysis_field_tab_t *tab;
	ocfg_field_t *fld;

	tab = sch_sim_get_analysis_fieldreq(ctx->dlg[ctx->watype].val.lng);
	if (tab == NULL)
		return 0;

	santype = sch_siman_names[ctx->dlg[ctx->watype].val.lng];

	switch(sch_sim_update_text_node(ctx->nanalysis, "type", santype)) {
		case  0: break;
		case  1: changed = 1; break;
		case -1: rnd_message(RND_MSG_ERROR, "Failed to set analysis conf field: type\n"); break;
	}

	for(n = 0, fld = ctx->fld_analisys; n < NUM_FIELDS; n++, fld++) {
		sch_sim_field_req_t *req = (sch_sim_field_req_t *)((char *)tab + fld->req_offs);
		int hide;

		switch(*req) {
			case SCH_SIMREQ_NO: hide = 1; break;
			case SCH_SIMREQ_MANDATORY: hide = 0; break;
			case SCH_SIMREQ_OPTIONAL: hide = 0; break;
		}

		if (!hide) {
			if (fld->is_int) {
				sprintf(inp_tmp, "%ld", ctx->dlg[fld->winp].val.lng);
				inp = inp_tmp;
			}
			else
				inp = ctx->dlg[fld->winp].val.str;
		}
		else
			inp = NULL;

		switch(sch_sim_update_text_node(ctx->nanalysis, fld->conf_name, inp)) {
			case  0: break;
			case  1: changed = 1; break;
			case -1: rnd_message(RND_MSG_ERROR, "Failed to set analysis conf node %s\n", fld->conf_name); break;
		}
	}

	return changed;
}

static int is_log(const char *s)
{
	if (s == NULL) return 0;
	return (strcmp(s, "log") == 0);
}

static void outcfg_conf2dlg_presentation(ocfg_dlg_ctx_t *ctx)
{
	sch_sim_presentation_type_t type;
	const char *tmp;
	rnd_hid_attr_val_t hv;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wplist];
	rnd_hid_tree_t *tree = attr->wdata;
	rnd_hid_row_t *r;
	char *cursor_path = NULL, *cell[2];
	lht_node_t *n, *nlst;

	tmp = get_path_str(ctx->npresentation, "type", NULL, NULL);
	type = sch_sim_str2presentation_type(tmp);

	hv.lng = type;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wptype, &hv);

	/*** fill in props ***/

	/* remember cursor */
	r = rnd_dad_tree_get_selected(attr);
	if (r != NULL)
		cursor_path = rnd_strdup(r->cell[0]);

	/* remove existing items and fill up with the new */
	rnd_dad_tree_clear(tree);

	nlst = lht_dom_hash_get(ctx->npresentation, "props");
	if ((nlst != NULL) && (nlst->type == LHT_LIST)) {
		/* fill in the table */
		cell[1] = NULL;

		for(n = nlst->data.list.first; n != NULL; n = n->next) {
			if (n->type == LHT_TEXT)
				cell[0] = rnd_strdup(n->data.text.value);
			else
				cell[0] = rnd_strdup("<invalid node type>");
			r = rnd_dad_tree_append(attr, NULL, cell);
			r->user_data = n;
		}
	}

	/* restore cursor (will work only in non-modal upon project update from elsewhere) */
	if (cursor_path != NULL) {
		rnd_hid_attr_val_t hv;
		hv.str = cursor_path;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wplist, &hv);
		free(cursor_path);
	}

}

static int outcfg_dlg2conf_presentation(ocfg_dlg_ctx_t *ctx)
{
	sch_sim_presentation_type_t type = ctx->dlg[ctx->wptype].val.lng;
	int changed = 0;
	const char *sptype;

	if (ctx->dlg[ctx->wptype].val.lng < 0) {
		rnd_message(RND_MSG_ERROR, "Failed to set presentation conf field: invalid type\n");
		return 0;
	}

	sptype = sch_simpres_names[type];

	switch(sch_sim_update_text_node(ctx->npresentation, "type", sptype)) {
		case  0: break;
		case  1: changed = 1; break;
		case -1: rnd_message(RND_MSG_ERROR, "Failed to set presentation conf field: type\n"); break;
	}

	return changed;
}

static void outcfg_conf2dlg(ocfg_dlg_ctx_t *ctx)
{
	outcfg_conf2dlg_analysis(ctx);
	outcfg_conf2dlg_presentation(ctx);
}

static int setup_is_in_prj(ocfg_dlg_ctx_t *ctx) {
	lht_node_t *nsetup, *nroot;

	nsetup = sch_sim_get_setup(ctx->prj, ctx->setup_name, 0); /* switch to project for get_first_crpol() */
	if (nsetup == NULL)
		return 0;

	nroot = rnd_conf_lht_get_first_crpol(RND_CFR_PROJECT, RND_POL_OVERWRITE, 1);
	if (nsetup->doc != nroot->doc)
		return 0;

	return 1;
}

static int outcfg_dlg2conf(ocfg_dlg_ctx_t *ctx)
{
	int changed = 0;

	if (!setup_is_in_prj(ctx)) {
		rnd_message(RND_MSG_ERROR, "Can not save changes: this sim setup is not in the project file but in another config\n");
		return 0;
	}

	changed |= outcfg_dlg2conf_analysis(ctx);
	changed |= outcfg_dlg2conf_presentation(ctx);

	if (changed)
		sch_sim_flush_prj_file(ctx->prj);

	return changed;
}


static void outcfg_save_conf_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	ocfg_dlg_ctx_t *ctx = caller_data;
	if (outcfg_dlg2conf(ctx))
		outcfg_conf2dlg(ctx);
}

static void outcfg_prop_new_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	ocfg_dlg_ctx_t *ctx = caller_data;
	rnd_design_t *dsg = ctx->prj->hdr.designs.array[0];
	char *s, *ent;
	lht_node_t *nlst, *n;

	ent = rnd_hid_prompt_for(dsg, "Add new presentation property: a netname or a port name (refdes-pinnum)", "", "Simulation presentation property");
	if (ent == NULL)
		return;

	s = ent;
	while(isspace(*s)) s++;
	if (*s == '\0') {
		free(ent);
		return;
	}

	nlst = lht_dom_hash_get(ctx->npresentation, "props");
	if (nlst == NULL) {
		nlst = lht_dom_node_alloc(LHT_LIST, "props");
		lht_dom_hash_put(ctx->npresentation, nlst);
	}
	n = lht_dom_node_alloc(LHT_TEXT, NULL);
	n->data.text.value = rnd_strdup(s);
	lht_dom_list_append(nlst, n);

	sch_sim_flush_prj_file(ctx->prj);
	outcfg_conf2dlg_presentation(ctx);

	free(ent);
}

static void outcfg_prop_edit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	ocfg_dlg_ctx_t *ctx = caller_data;
	rnd_design_t *dsg = ctx->prj->hdr.designs.array[0];
	char *s, *ent;
	lht_node_t *n;
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->wplist];
	rnd_hid_row_t *row = rnd_dad_tree_get_selected(tattr);

	if (row == NULL) {
		rnd_message(RND_MSG_ERROR, "Select a property first!\n");
		return;
	}

	n = row->user_data;
	if ((n == NULL) || (n->type != LHT_TEXT)) {
		rnd_message(RND_MSG_ERROR, "Invalid property node type, can't edit\n");
		return;
	}

	ent = rnd_hid_prompt_for(dsg, "Edit presentation property: a netname or a port name (refdes-pinnum)", n->data.text.value, "Simulation presentation property");
	if (ent == NULL)
		return;

	s = ent;
	while(isspace(*s)) s++;
	if (*s == '\0') {
		free(ent);
		return;
	}

	free(n->data.text.value);
	n->data.text.value = rnd_strdup(s);

	sch_sim_flush_prj_file(ctx->prj);
	outcfg_conf2dlg_presentation(ctx);

	free(ent);
}

static void outcfg_prop_del_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	ocfg_dlg_ctx_t *ctx = caller_data;
	lht_node_t *n;
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->wplist];
	rnd_hid_row_t *row = rnd_dad_tree_get_selected(tattr);

	if (row == NULL) {
		rnd_message(RND_MSG_ERROR, "Select a property first!\n");
		return;
	}

	n = row->user_data;
	if (n == NULL) {
		rnd_message(RND_MSG_ERROR, "Invalid property node, can't delete\n");
		return;
	}

	lht_tree_del(n);

	sch_sim_flush_prj_file(ctx->prj);
	outcfg_conf2dlg_presentation(ctx);
}


int dlg_outcfg_edit(csch_project_t *prj, const char *setup_name, const char *output_name)
{
	ocfg_dlg_ctx_t ctx_ = {0}, *ctx = &ctx_;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 1}, {NULL, 0}};
	const char *pres_tbl_hdr[] = {"Properties to include", NULL};
	ocfg_field_t *fld;

	ctx->prj = prj;

	ctx->noutput = sch_sim_get_output(prj, setup_name, output_name, 0);
	if (ctx->noutput == NULL) {
		rnd_message(RND_MSG_ERROR, "Output %s not found.\n", output_name);
		return -1;
	}

	ctx->setup_name = rnd_strdup(setup_name);
	ctx->nanalysis = sch_sim_lht_dom_hash_ensure(ctx->noutput, LHT_HASH, "analysis");
	ctx->npresentation = sch_sim_lht_dom_hash_ensure(ctx->noutput, LHT_HASH, "presentation");
	if ((ctx->nanalysis == NULL) || (ctx->npresentation == NULL)) {
		rnd_message(RND_MSG_ERROR, "Can't find or create output's analysis or presentation subtree.\n");
		return -1;
	}

	fld = ctx->fld_analisys;

	RND_DAD_BEGIN_VBOX(ctx->dlg);
		RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);

		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_FRAME);
			RND_DAD_LABEL(ctx->dlg, "Analysis");
			RND_DAD_ENUM(ctx->dlg, sch_siman_description);
				ctx->watype = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_CHANGE_CB(ctx->dlg, outcfg_save_conf_cb);

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, start);
				fld->conf_name = "start";
				RND_DAD_STRING(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "start of range");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
			RND_DAD_END(ctx->dlg);
			fld++;

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, stop);
				fld->conf_name = "stop";
				RND_DAD_STRING(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "stop: end of range");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, incr);
				fld->conf_name = "incr";
				RND_DAD_STRING(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "increment");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, incr_max);
				fld->conf_name = "incr_max";
				RND_DAD_STRING(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "increment maximum");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, numpt);
				fld->conf_name = "numpt";
				fld->is_int = 1;
				RND_DAD_INTEGER(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_MINVAL(ctx->dlg, 0);
					RND_DAD_MAXVAL(ctx->dlg, 20000);
				RND_DAD_LABEL(ctx->dlg, "number of points per range div");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, port1);
				fld->conf_name = "port1";
				fld->conf_name2 = "port1_neg";
				RND_DAD_STRING(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "port 1 pos");
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "      ");
				RND_DAD_STRING(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "neg");
					fld->winp2 = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, port2);
				fld->conf_name = "port2";
				fld->conf_name2 = "port2_neg";
				RND_DAD_STRING(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "port 2 pos");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "      ");
				RND_DAD_STRING(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "neg");
					fld->winp2 = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;


			RND_DAD_BEGIN_HBOX(ctx->dlg);
				fld->wbox = RND_DAD_CURRENT(ctx->dlg);
				fld->req_offs = offsetof(sch_sim_analysis_field_tab_t, incr);
				fld->conf_name = "src";
				RND_DAD_STRING(ctx->dlg);
					fld->winp = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "source name");
				RND_DAD_LABEL(ctx->dlg, "(optional)");
					fld->wopt = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);
			fld++;

		RND_DAD_END(ctx->dlg);

		RND_DAD_LABEL(ctx->dlg, "");

		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_FRAME);
			RND_DAD_LABEL(ctx->dlg, "Presentation");

			/* type and axes */
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				RND_DAD_ENUM(ctx->dlg, sch_simpres_names);
					ctx->wptype = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_CHANGE_CB(ctx->dlg, outcfg_save_conf_cb);
			RND_DAD_END(ctx->dlg);


			RND_DAD_TREE(ctx->dlg, 1, 0, pres_tbl_hdr);
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
				ctx->wplist = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_HELP(ctx->dlg, "Typically net or port names on which voltages are printed or plotted\nPort names are of refdes-pinnum format, e.g. U37-2");

			RND_DAD_BEGIN_HBOX(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "New...");
					RND_DAD_CHANGE_CB(ctx->dlg, outcfg_prop_new_cb);
				RND_DAD_BUTTON(ctx->dlg, "Edit...");
					RND_DAD_CHANGE_CB(ctx->dlg, outcfg_prop_edit_cb);
				RND_DAD_BUTTON(ctx->dlg, "Remove");
					RND_DAD_CHANGE_CB(ctx->dlg, outcfg_prop_del_cb);
			RND_DAD_END(ctx->dlg);


		RND_DAD_END(ctx->dlg);

		RND_DAD_BUTTON_CLOSES(ctx->dlg, clbtn);

	RND_DAD_END(ctx->dlg);

	RND_DAD_DEFSIZE(ctx->dlg, 300, 300);
	RND_DAD_NEW("SimOutputConfig", ctx->dlg, "Simulation output config", ctx, 1, NULL); /* type=local/modal */

	outcfg_conf2dlg(ctx);

	RND_DAD_RUN(ctx->dlg);
	outcfg_dlg2conf(ctx); /* text field changes are not triggering a conversion, pick them up at the end */
	RND_DAD_FREE(ctx->dlg);

	outcfg_uninit(ctx);

	return 0;
}
