/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - GUI
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  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
 */

/* text object dialogs */

#include <libcschem/config.h>

#include <string.h>
#include <genht/htsp.h>
#include <genht/hash.h>

#include <librnd/core/actions.h>
#include <librnd/core/hidlib.h>
#include <librnd/hid/hid_dad.h>

#include <libcschem/concrete.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_grp_child.h>
#include <libcschem/actions_csch.h>

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

/* This doesn't make a plugin dep as only inlines and macros and structs are used */
#include <sch-rnd/conf_core.h>
#define RND_TIMED_CHG_TIMEOUT conf_core.editor.edit_time
#include <librnd/plugins/lib_hid_common/timed_chg.h>

typedef struct {
	RND_DAD_DECL_NOINIT(dlg)
	csch_sheet_t *sheet;
	csch_text_t *text;
	int wattr1, wsubst1, wtext;
	int start, len; /* character offset of start and length of the first %% substitution */
	int got_val; /* if direct attribute value editing is available */
	int gui_active;

	struct {
		rnd_timed_chg_t hdr;
		gds_t str;
	} timed;

	gds_t tmp;
} dyntext_dlg_ctx_t;

/* Returns a pointer to the first template %% block, overwrite the next % with \0
   if out_start and out_len are not NULL, fill them in with index values within
   the original templ string */
static char *dyntext_temp_get_first_attr(char *templ, int *out_start, int *out_len)
{
	char *first, *last;

	first = strchr(templ, '%');
	if (first != NULL) {
		first++;
		last = strchr(first, '%');
		if ((last != NULL) && (last > first+1)) {
			*last = '\0';
			if (out_start != NULL)
				*out_start = first - templ;
			if (out_len != NULL)
				*out_len = last - first;
		}
		else
			first = "";
	}
	else
		first = "";

	return first;
}

static void dyntext_attr_edit(rnd_design_t *hidlib, csch_text_t *text, const char *key)
{
	fgw_arg_t res, args[3];
	csch_oidpath_t idp = {0};
	gds_t op = {0};

	/* prepare first arg */
	csch_oidpath_from_obj(&idp, &text->hdr.parent->hdr);
	gds_append_str(&op, "object:");
	csch_oidpath_to_str_append(&op, &idp);
	csch_oidpath_free(&idp);

	/* call action, save resulting attr key */
	args[1].type = FGW_STR;
	args[1].val.cstr = op.array;
	args[2].type = FGW_STR;
	args[2].val.cstr = key;
	rnd_actionv_bin(hidlib, "attributedialog", &res, 3, args);

	gds_uninit(&op);
}


static void dyntext_dlg_text2dlg(dyntext_dlg_ctx_t *ctx)
{
	char *first = "";

	ctx->got_val = 0;
	ctx->start = ctx->len = -1;
	ctx->tmp.used = 0;
	gds_append_str(&ctx->tmp, ctx->text->text);

	if (ctx->tmp.used > 2)
		first = dyntext_temp_get_first_attr(ctx->tmp.array, &ctx->start, &ctx->len);

	if (strncmp(first, "../A.", 5) == 0) {
		csch_attrib_t *a = csch_attrib_get(&ctx->text->hdr.parent->attr, first+5);
		if ((a != NULL) && (a->val != NULL)) {
			RND_DAD_SET_VALUE(ctx->dlg_hid_ctx, ctx->wattr1, str, a->val);
			rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wattr1, 1);
			ctx->got_val = 1;
		}
	}

	if (!ctx->got_val) {
		RND_DAD_SET_VALUE(ctx->dlg_hid_ctx, ctx->wattr1, str, "");
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wattr1, 0);
	}


	RND_DAD_SET_VALUE(ctx->dlg_hid_ctx, ctx->wsubst1, str, first);
	RND_DAD_SET_VALUE(ctx->dlg_hid_ctx, ctx->wtext, str, ctx->text->text);
}

static void set_text_timed(void *ctx_)
{
	dyntext_dlg_ctx_t *ctx = ctx_;

	sch_rnd_op_text_edit(ctx->sheet, ctx->text, ctx->timed.str.array);
	rnd_gui->invalidate_all(rnd_gui);
	if (ctx->gui_active)
		dyntext_dlg_text2dlg(ctx);
	ctx->timed.str.used = 0;
}

RND_INLINE void dyntext_build_timed_subst(dyntext_dlg_ctx_t *ctx, const char *str1, const char *str2)
{
	ctx->timed.str.used = 0;
	gds_append_len(&ctx->timed.str, ctx->text->text, ctx->start);
	if (str1 != NULL)
		gds_append_str(&ctx->timed.str, str1);
	if (str2 != NULL)
		gds_append_str(&ctx->timed.str, str2);
	gds_append_str(&ctx->timed.str, ctx->text->text + ctx->start + ctx->len);
}

static void dyntext_subst1_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	dyntext_dlg_ctx_t *ctx = caller_data;

	if (ctx->start < 0)
		return;

	dyntext_build_timed_subst(ctx, ctx->dlg[ctx->wsubst1].val.str, NULL);
	rnd_timed_chg_schedule(&ctx->timed.hdr);
}

static void dyntext_raw_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	dyntext_dlg_ctx_t *ctx = caller_data;

	ctx->timed.str.used = 0;
	gds_append_str(&ctx->timed.str, ctx->dlg[ctx->wtext].val.str);
	rnd_timed_chg_schedule(&ctx->timed.hdr);
}

static void dyntext_pick_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	dyntext_dlg_ctx_t *ctx = caller_data;
	fgw_arg_t res, args[2];
	csch_oidpath_t idp = {0};
	gds_t op = {0};
	const char *key = NULL;

	/* prepare first arg */
	csch_oidpath_from_obj(&idp, &ctx->text->hdr.parent->hdr);
	gds_append_str(&op, "object:");
	csch_oidpath_to_str_append(&op, &idp);
	csch_oidpath_free(&idp);

	/* call action, save resulting attr key */
	args[1].type = FGW_STR;
	args[1].val.cstr = op.array;
	if (rnd_actionv_bin(&ctx->sheet->hidlib, "attributepick", &res, 2, args) == 0)
		key = res.val.cstr;

	/* clean up action call */
	fgw_arg_free(&rnd_fgw, &res);
	gds_uninit(&op);

	if ((key == NULL) || (*key == '\0'))
		return; /* cancel */

	/* build and update */
	dyntext_build_timed_subst(ctx, "../A.", key);
	sch_rnd_op_text_edit(ctx->sheet, ctx->text, ctx->timed.str.array);
	rnd_gui->invalidate_all(rnd_gui);
	dyntext_dlg_text2dlg(ctx);
	ctx->timed.str.used = 0;
}


static void dyntext_attr1_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	csch_source_arg_t *src;
	dyntext_dlg_ctx_t *ctx = caller_data;
	const char *key = ctx->tmp.array + ctx->start + 5;
	const char *val = ctx->dlg[ctx->wattr1].val.str;

	src = csch_attrib_src_c(NULL, 0, 0, "dlg_text user input");
	csch_attr_modify_str(ctx->sheet, ctx->text->hdr.parent, CSCH_ATP_USER_DEFAULT, key, val, src, 1);
	ctx->text->bbox_calced = 0;
	csch_text_update(ctx->sheet, ctx->text, 1);
	csch_sheet_set_changed(ctx->sheet, 1); /* get drawing area recalculated */
	rnd_gui->invalidate_all(rnd_gui);
	dyntext_dlg_text2dlg(ctx);
	ctx->timed.str.used = 0;
}

static void dyntext_attredit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	dyntext_dlg_ctx_t *ctx = caller_data;

	if (ctx->got_val) { /* we have a concrete model attribute to edit directly */
		static rnd_dad_retovr_t retovr;
		dyntext_attr_edit(&ctx->sheet->hidlib, ctx->text, ctx->tmp.array + ctx->start + 5);
		rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
		return;
	}
	if (ctx->len > 5) { /* may be an abstract attr */
		const char *first = ctx->tmp.array + ctx->start;
		if (strncmp(first, "../a.", 5) == 0) {
			TODO("invoke AbstractDialog with the given component and attribute");
			rnd_message(RND_MSG_ERROR, "Attribute of the abstract model is referenced\nThe abstract model can not be edited directly, you'll need to find\nwhich concrete model attribute is compiled into abstract attribute\n%s and edit that.\n", first);
			return;
		}
	}

	rnd_message(RND_MSG_ERROR, "No accessible attribute referenced in text template\nso I don't know what attribute to edit.\n");
}


static int sch_rnd_edit_text_dialog_dynamic(csch_sheet_t *sheet, csch_text_t *text)
{
	static const char help_subst1[] = "Quick edit for the first %% substitution";
	static const char help_raw[] = "Edit text string as-is; presents the template without any transformation";
	static const char spaces[] = "                            ";
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	dyntext_dlg_ctx_t ctx = {0};

	ctx.sheet = sheet;
	ctx.text = text;

	rnd_timed_chg_init(&ctx.timed.hdr, set_text_timed, &ctx);

	RND_DAD_BEGIN_VBOX(ctx.dlg);
		RND_DAD_COMPFLAG(ctx.dlg, RND_HATF_EXPFILL);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_BEGIN_VBOX(ctx.dlg);
				RND_DAD_LABEL(ctx.dlg, spaces);
				RND_DAD_LABEL(ctx.dlg, "First subst val:");
				RND_DAD_LABEL(ctx.dlg, spaces);
			RND_DAD_END(ctx.dlg);

			RND_DAD_STRING(ctx.dlg);
				RND_DAD_HELP(ctx.dlg, help_subst1);
				ctx.wattr1 = RND_DAD_CURRENT(ctx.dlg);
				RND_DAD_CHANGE_CB(ctx.dlg, dyntext_attr1_cb);

			RND_DAD_BEGIN_VBOX(ctx.dlg);
				RND_DAD_LABEL(ctx.dlg, "");
				RND_DAD_BUTTON(ctx.dlg, "Attr. edit");
					RND_DAD_HELP(ctx.dlg, "Invoke the attribute editor");
					RND_DAD_CHANGE_CB(ctx.dlg, dyntext_attredit_cb);
				RND_DAD_LABEL(ctx.dlg, "");
			RND_DAD_END(ctx.dlg);

		RND_DAD_END(ctx.dlg);


		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_BEGIN_VBOX(ctx.dlg);
				RND_DAD_LABEL(ctx.dlg, spaces);
				RND_DAD_LABEL(ctx.dlg, "First subst key:");
					RND_DAD_HELP(ctx.dlg, help_subst1);
				RND_DAD_LABEL(ctx.dlg, spaces);
			RND_DAD_END(ctx.dlg);

			RND_DAD_STRING(ctx.dlg);
				RND_DAD_HELP(ctx.dlg, help_subst1);
				ctx.wsubst1 = RND_DAD_CURRENT(ctx.dlg);
				RND_DAD_CHANGE_CB(ctx.dlg, dyntext_subst1_cb);

			RND_DAD_BEGIN_VBOX(ctx.dlg);
				RND_DAD_LABEL(ctx.dlg, "");
				RND_DAD_BUTTON(ctx.dlg, "Pick...");
					RND_DAD_HELP(ctx.dlg, "Pick an existing attribute from the parent group");
					RND_DAD_CHANGE_CB(ctx.dlg, dyntext_pick_cb);
				RND_DAD_LABEL(ctx.dlg, "");
			RND_DAD_END(ctx.dlg);

		RND_DAD_END(ctx.dlg);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_BEGIN_VBOX(ctx.dlg);
				RND_DAD_LABEL(ctx.dlg, spaces);
				RND_DAD_LABEL(ctx.dlg, "Raw (template):");
					RND_DAD_HELP(ctx.dlg, help_raw);
				RND_DAD_LABEL(ctx.dlg, spaces);
			RND_DAD_END(ctx.dlg);
			RND_DAD_STRING(ctx.dlg);
				RND_DAD_WIDTH_CHR(ctx.dlg, 32);
				RND_DAD_HELP(ctx.dlg, help_raw);
				ctx.wtext = RND_DAD_CURRENT(ctx.dlg);
				RND_DAD_CHANGE_CB(ctx.dlg, dyntext_raw_cb);
		RND_DAD_END(ctx.dlg);

		RND_DAD_TIMING(ctx.dlg, &ctx.timed.hdr, "text");

		RND_DAD_BUTTON_CLOSES(ctx.dlg, clbtn);
	RND_DAD_END(ctx.dlg);

	RND_DAD_NEW("DyntextDialog", ctx.dlg, "Dynamic text edit", &ctx, 1, NULL); /* type=local/modal */

	rnd_timed_chg_timing_init(&ctx.timed.hdr, ctx.dlg_hid_ctx);
	dyntext_dlg_text2dlg(&ctx);

	if (ctx.got_val)
		RND_DAD_STRING_SELECT_REGION(ctx.dlg_hid_ctx, ctx.wattr1, 0, 1<<30);

	ctx.gui_active = 1;
	RND_DAD_RUN(ctx.dlg);
	ctx.gui_active = 0;
	ctx.timed.hdr.wtiming = -1;

	rnd_timed_chg_finalize(&ctx.timed.hdr);

	RND_DAD_FREE(ctx.dlg);

	gds_uninit(&ctx.tmp);
	gds_uninit(&ctx.timed.str);
	return 0;
}


static int sch_rnd_edit_text_dialog_static(csch_sheet_t *sheet, csch_text_t *text)
{
	char *new_str = rnd_hid_prompt_for(&sheet->hidlib, "Edit text object:", text->text, "Edit text object");

	if (new_str != NULL) {
		sch_rnd_op_text_edit(sheet, text, new_str);
		rnd_gui->invalidate_all(rnd_gui);
		free(new_str);
		return 0;
	}
	return -1;
}


int sch_rnd_edit_text_dialog(csch_sheet_t *sheet, csch_text_t *text)
{
	return text->dyntext ? sch_rnd_edit_text_dialog_dynamic(sheet, text) : sch_rnd_edit_text_dialog_static(sheet, text);
}

const char csch_acts_EditText[] = "EditText([object[=idpath]])";
const char csch_acth_EditText[] = "Bring up a text quick edit dialog.\n";
fgw_error_t csch_act_EditText(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;
	csch_chdr_t *obj;
	int cmd;

	CSCH_ACT_CONVARG_OBJ(1, EditText, cmd, obj);

	RND_ACT_IRES(-1);

	switch(cmd) {
		case F_Object:
			{
				csch_cgrp_t *grp_ref;
				csch_text_t *text;
				csch_coord_t x, y;
				int r, attr_only;

				if (obj == NULL) {
					if (sch_rnd_get_coords("Click on text to edit", &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, "EditText(): no text object under cursor\n");
						break;
					}
				}
				if (obj->type != CSCH_CTYPE_TEXT) {
					rnd_message(RND_MSG_ERROR, "EditText(): not a text object\n");
					break;
				}

				grp_ref = csch_grp_ref_get_top(obj->sheet, obj);
				attr_only = (grp_ref != NULL);
				text = (csch_text_t *)obj;
				if (attr_only && text->dyntext && (obj->parent == grp_ref)) {
					char *tmp, *key;
					int edited = 0;

					/* special case: text edit on a direct attribute printout of a group ref */
					tmp = rnd_strdup(text->text);
					key = dyntext_temp_get_first_attr(tmp, NULL, NULL);
					if (*key != '\0') {
						if (strncmp(key, "../A.", 5) == 0) {
							dyntext_attr_edit(hidlib, text, key+5);
							edited = 1;
						}
					}
					free(tmp);
					if (edited)
						break; /* no error */
				}
				if (attr_only) {
					rnd_message(RND_MSG_ERROR, "Can not change text of a group_ref child\nbecause it would change the referenced group's children (probably in local lib)\n");
					break;
				}
				r = sch_rnd_edit_text_dialog(sheet, text);
				RND_ACT_IRES(r);
			}
			break;
		default:
			rnd_message(RND_MSG_ERROR, "EditText(): invalid first argument\n");
	}

	return 0;
}

