/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - GUI - conditionals
 *  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
 */

/* stance configuration dialog */

#include <libcschem/config.h>

#include <genht/htpp.h>
#include <genht/hash.h>
#include <genvector/vtl0.h>

#include <librnd/core/event.h>
#include <librnd/core/conf.h>
#include <librnd/core/conf_hid.h>
#include <librnd/core/actions.h>
#include <librnd/core/hidlib.h>
#include <librnd/core/rnd_conf.h>
#include <librnd/hid/hid_dad.h>
#include <librnd/hid/hid_dad_tree.h>
#include <librnd/core/rnd_printf.h>

#include <libcschem/project.h>
#include <libcschem/event.h>
#include <libcschem/actions_csch.h>
#include <libcschem/search.h>

#include <sch-rnd/crosshair.h>
#include <sch-rnd/draw.h>
#include <sch-rnd/search.h>
#include <sch-rnd/funchash_core.h>

#include "quick_attr_util.h"

/*** Conditional ***/

typedef enum { CDLG_DNP, CDLG_OMIT } cond_dlg_type_t;

typedef struct cond_dlg_ctx_s {
	RND_DAD_DECL_NOINIT(dlg)

	csch_sheet_t *sheet;
	csch_oidpath_t idp;
	const char *attr_name;
	cond_dlg_type_t type;

	int wcond, wscript;

	void *obj; /* for the hash */
} cond_dlg_ctx_t;

static htpp_t obj2dlg;

static void cond_apply(cond_dlg_ctx_t *ctx)
{
	vts0_t lst = {0};
	rnd_hid_attribute_t *atxt = &ctx->dlg[ctx->wscript];
	rnd_hid_text_t *txt = atxt->wdata;
	char *tmp = txt->hid_get_text(atxt, ctx->dlg_hid_ctx), *s, *next;
	csch_source_arg_t *src = csch_attrib_src_c(NULL, 0, 0, "ConditionalDialog input");
	csch_cgrp_t *obj = (csch_cgrp_t *)csch_oidpath_resolve(ctx->sheet, &ctx->idp);

	if (obj == NULL) {
		rnd_message(RND_MSG_ERROR, "ConditionalDialog: can't apply: object does not exist\n");
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wcond, 0);
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wscript, 0);
		return;
	}

	vts0_append(&lst, (char *)ctx->dlg[ctx->wcond].val.str);
	for(s = tmp; (s != NULL) && (*s != '\0'); s = next) {
		next = strpbrk(s, "\r\n");
		if (next != NULL) {
			*next = '\0';
			next++;
		}
		vts0_append(&lst, s);
	}

	/* this can not be csch_cobj_attrib_set() because it is setting a list;
	   it's also attr_name is rather limited so side effects won't play a role */
	csch_attrib_set_arr(&obj->attr, CSCH_ATP_USER_DEFAULT, ctx->attr_name, &lst, src, NULL);
	csch_sheet_set_changed(obj->hdr.sheet, 1);
	free(tmp);
	vts0_uninit(&lst);
}


static void cond_dlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	cond_dlg_ctx_t *ctx = caller_data;

	if (ctx->dlg_ret_override->value == 1)
		cond_apply(ctx);

	csch_oidpath_free(&ctx->idp);

	htpp_pop(&obj2dlg, ctx->obj);
	free(ctx);
}

static void cond_obj2dlg(cond_dlg_ctx_t *ctx)
{
	csch_attrib_t *fa;
	rnd_hid_attr_val_t hv;
	gds_t tmp = {0};
	csch_cgrp_t *obj = (csch_cgrp_t *)csch_oidpath_resolve(ctx->sheet, &ctx->idp);

	if (obj == NULL) {
		rnd_message(RND_MSG_ERROR, "ConditionalDialog: can't apply: object does not exist\n");
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wcond, 0);
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wscript, 0);
		return;
	}

	fa = csch_attrib_get(&obj->attr, ctx->attr_name);

	if ((fa == NULL) || (fa->arr.used < 1))
		hv.str = "stance.model != \"standard\"";
	else
		hv.str = fa->arr.array[0];

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

	if ((fa == NULL) || (fa->arr.used < 2)) {
		switch(ctx->type) {
			case CDLG_DNP:  gds_append_str(&tmp, "scalar,dnp\nsub,^.*$,yes,dnp\n"); break;
			case CDLG_OMIT: gds_append_str(&tmp, "scalar,omit\nsub,^.*$,yes,omit\n"); break;
		}
	}
	else {
		long n;
		for(n = 1; n < fa->arr.used; n++) {
			gds_append_str(&tmp, fa->arr.array[n]);
			gds_append(&tmp, '\n');
		}
	}

	hv.str = tmp.array;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wscript, &hv);
	gds_uninit(&tmp);
}

static void cond_apply_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	cond_apply(caller_data);
}

int sch_rnd_conditional_dlg(csch_cgrp_t *obj, cond_dlg_type_t type)
{
	cond_dlg_ctx_t *ctx;
	rnd_hid_dad_buttons_t clbtn[] = {{"Cancel", 0}, {"Apply & close", 1}, {NULL, 0}};
	const char *attr_name;
	csch_sheet_t *sheet = obj->hdr.sheet;

	while((obj != NULL) && !csch_obj_is_grp(&obj->hdr)) obj = obj->hdr.parent;

	if ((obj == NULL) || (obj == &sheet->direct)) {
		rnd_message(RND_MSG_ERROR, "sch_rnd_conditional_dlg(): object is not in a group\n");
		return -1;
	}

	if ((obj->role != CSCH_ROLE_SYMBOL) && (obj->role != CSCH_ROLE_WIRE_NET)) {
		rnd_message(RND_MSG_ERROR, "sch_rnd_conditional_dlg(): group shall be a symbol or a wirenet\n");
		return -1;
	}

	switch(type) {
		case CDLG_DNP:   attr_name = "forge-if/dnp"; break;
		case CDLG_OMIT:  attr_name = "forge-if/omit"; break;
		default: return -1;
	}

	ctx = htpp_get(&obj2dlg, obj);
	if (ctx != NULL) {
		TODO("raise?");
		return 0;
	}

	ctx = calloc(sizeof(cond_dlg_ctx_t), 1);
	ctx->obj = obj;
	ctx->sheet = sheet;
	ctx->attr_name = attr_name;
	ctx->type = type;

	htpp_set(&obj2dlg, obj, ctx);
	csch_oidpath_from_obj(&ctx->idp, &obj->hdr);

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

		RND_DAD_BEGIN_HBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Attribute: ");
			RND_DAD_LABEL(ctx->dlg, ctx->attr_name);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_HBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Cond.:");
			RND_DAD_STRING(ctx->dlg);
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				ctx->wcond = RND_DAD_CURRENT(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_HBOX(ctx->dlg);
			RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
			RND_DAD_LABEL(ctx->dlg, "Script:");
			RND_DAD_TEXT(ctx->dlg, "");
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
				ctx->wscript = RND_DAD_CURRENT(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_HBOX(ctx->dlg);
			RND_DAD_BUTTON(ctx->dlg, "Apply");
				RND_DAD_CHANGE_CB(ctx->dlg, cond_apply_cb);

			RND_DAD_BEGIN_VBOX(ctx->dlg); /* spring */
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
			RND_DAD_END(ctx->dlg);

			RND_DAD_BUTTON_CLOSES(ctx->dlg, clbtn);
		RND_DAD_END(ctx->dlg);

	RND_DAD_END(ctx->dlg);

	RND_DAD_DEFSIZE(ctx->dlg, 300, 400);
	RND_DAD_NEW("ConditionalDialog", ctx->dlg, "Conditional forge quick edit", ctx, 0, cond_dlg_close_cb); /* type=local */

	cond_obj2dlg(ctx);
	return 0;
}

void csch_dlg_cond_preunload(csch_sheet_t *sheet)
{
	htpp_entry_t *e;
	rnd_dad_retovr_t retovr = {0};

	for(e = htpp_first(&obj2dlg); e != NULL; e = htpp_next(&obj2dlg, e)) {
		cond_dlg_ctx_t *ctx = e->value;
		if (ctx->sheet == sheet)
			rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
	}
}

const char csch_acts_ConditionalDialog[] = "ConditionalDialog(object, dnp|omit)";
const char csch_acth_ConditionalDialog[] = "Open the conditional helper dialog for an object.\n";
fgw_error_t csch_act_ConditionalDialog(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	const char *stype;
	csch_chdr_t *obj;
	int cmd;
	cond_dlg_type_t type;

	CSCH_ACT_CONVARG_OBJ(1, ConditionalDialog, cmd, obj);
	RND_ACT_MAY_CONVARG(2, FGW_STR, ConditionalDialog, stype = argv[2].val.cstr);

	if (rnd_strcasecmp(stype, "dnp") == 0) type = CDLG_DNP;
	else if (rnd_strcasecmp(stype, "omit") == 0) type = CDLG_OMIT;
	else {
		rnd_message(RND_MSG_ERROR, "ConditionalDialog(): invalid second argument (type)\n");
		return FGW_ERR_ARG_CONV;
	}

	switch(cmd) {
		case F_Object:
			{
				csch_coord_t x, y;

				if (obj == NULL) {
					if (sch_rnd_get_coords("Click on a symbol for editing conditionals", &x, &y, 0) != 0)
						break;
					obj = sch_rnd_search_obj_at(sheet, x, y, sch_rnd_slop);
					if (obj == NULL) {
						rnd_message(RND_MSG_ERROR, "ConditionalDialog(): no symbol under cursor\n");
						break;
					}
				}
			}
			break;
		default:
			rnd_message(RND_MSG_ERROR, "ConditionalDialog(): invalid first argument\n");
			return FGW_ERR_ARG_CONV;
	}

	return sch_rnd_conditional_dlg((csch_cgrp_t *)obj, type);
}

const char csch_acts_quick_attr_forge__if__dnp[] = "quick_attr_forge__if__dnp(objptr)";
const char csch_acth_quick_attr_forge__if__dnp[] = "Quick Attribute Edit for the standard forge-if/dnp attribute";
const char csch_acts_quick_attr_forge__if__omit[] = "quick_attr_forge__if__dnp(objptr)";
const char csch_acth_quick_attr_forge__if__omit[] = "Quick Attribute Edit for the standard forge-if/omit attribute";
fgw_error_t csch_act_quick_attr_forge__if__dnp_omit(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	const char *fname = argv[0].val.argv0.func->name;
	csch_cgrp_t *grp;

	if (strlen(fname) < 22) {
		rnd_message(RND_MSG_ERROR, "csch_act_quick_attr_forge__if__dnp_omit(): called with invalid name '%s' (1)\n", fname);
		return FGW_ERR_ARG_CONV;
	}

	QUICK_ATTR_GET_GRP(grp, "csch_act_quick_attr_forge__if__dnp_omit");

	switch(fname[22]) {
		case 'd': sch_rnd_conditional_dlg(grp, CDLG_DNP); break;
		case 'o': sch_rnd_conditional_dlg(grp, CDLG_OMIT); break;
		default:
			rnd_message(RND_MSG_ERROR, "csch_act_quick_attr_forge__if__dnp_omit(): called with invalid name '%s' (2)\n", fname);
			return FGW_ERR_ARG_CONV;
	}

	return 0;
}



/*** Common ***/

void csch_dlg_cond_init(void)
{
	htpp_init(&obj2dlg, ptrhash, ptrkeyeq);
}

void csch_dlg_cond_uninit(void)
{
	rnd_dad_retovr_t retovr = {0};
	htpp_entry_t *e;

	for(e = htpp_first(&obj2dlg); e != NULL; e = htpp_next(&obj2dlg, e)) {
		cond_dlg_ctx_t *ctx = e->value;
		rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
	}
	htpp_uninit(&obj2dlg);
}
