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

/* abstract tree dialog */

#include <libcschem/config.h>

#include <genht/htpp.h>
#include <genht/htpi.h>
#include <genht/hash.h>
#include <genregex/regex_sei.h>

#include <librnd/core/event.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/abstract.h>
#include <libcschem/project.h>
#include <libcschem/actions_csch.h>

#include "abst_attr.h"

typedef struct abst_dlg_ctx_s {
	RND_DAD_DECL_NOINIT(dlg)

	abst_attrdlg_ctx_t right;
	csch_project_t *prj;
	gds_t tmp;

	int wtree, wfilt, wnoanon, wnoomit;

	htip_t aid2row;
} abst_dlg_ctx_t;

static htpp_t prj2dlg;

static void abst_dlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	abst_dlg_ctx_t *ctx = caller_data;
	gds_uninit(&ctx->tmp);
	htpp_pop(&prj2dlg, ctx->prj);
	htip_uninit(&ctx->aid2row);
	free(ctx);
}

static rnd_hid_row_t *put_in_tree(abst_dlg_ctx_t *ctx, rnd_hid_attribute_t *attr, csch_ahdr_t *a);

static char *get_aname_mkdir(abst_dlg_ctx_t *ctx, rnd_hid_attribute_t *attr, csch_ahdr_t *a, rnd_hid_row_t **parent)
{
	rnd_hid_tree_t *tree = attr->wdata;

	switch(a->type) {
		case CSCH_ATYPE_NET:
			{
				static char tmp[] = "net";
				*parent = rnd_dad_tree_mkdirp(tree, tmp, NULL);
				return rnd_strdup(((csch_anet_t *)a)->name);
			}
		case CSCH_ATYPE_COMP:
			{
				static char tmp[] = "comp";
				*parent = rnd_dad_tree_mkdirp(tree, tmp, NULL);
				return rnd_strdup(((csch_acomp_t *)a)->name);
			}
		case CSCH_ATYPE_PORT:
			{
				static char tmp[] = "port";
				csch_aport_t *p = (csch_aport_t *)a;
				csch_acomp_t *c = p->parent;
				if ((c != NULL) && (c->hdr.type == CSCH_ATYPE_COMP)) {
					ctx->tmp.used = 0;
					gds_append_str(&ctx->tmp, "comp/");
					gds_append_str(&ctx->tmp, c->name);
					*parent = htsp_get(&tree->paths, ctx->tmp.array);
					if (*parent == NULL)
						*parent = put_in_tree(ctx, attr, &c->hdr);
					return rnd_strdup(p->name);
				}
				*parent = rnd_dad_tree_mkdirp(tree, tmp, NULL);
				if (p->referer != NULL) {
					gds_t tmp = {0};
					gds_append_str(&tmp, p->referer->name);
					gds_append(&tmp, '-');
					gds_append_str(&tmp, p->name);
					return tmp.array; /* ownership passed */
				}
				return rnd_strdup(p->name);
			}

		case CSCH_ATYPE_BUSNET:
		case CSCH_ATYPE_BUSCHAN:
		case CSCH_ATYPE_BUSPORT:
		case CSCH_ATYPE_HUB:
		case CSCH_ATYPE_INVALID:
			break;
	}

	{
		static char tmp[] = "unknwon";
		*parent = rnd_dad_tree_mkdirp(tree, tmp, NULL);
	}
	return rnd_strdup_printf("%ld", a->aid);
}

static const char *get_aname_dry(const csch_ahdr_t *a, int *type_score)
{
	switch(a->type) {
		case CSCH_ATYPE_NET:   *type_score = 1; return ((csch_anet_t *)a)->name;
		case CSCH_ATYPE_COMP:  *type_score = 2; return ((csch_acomp_t *)a)->name;
		case CSCH_ATYPE_PORT:  *type_score = 3; return ((csch_aport_t *)a)->name;
		case CSCH_ATYPE_BUSNET:
		case CSCH_ATYPE_BUSCHAN:
		case CSCH_ATYPE_BUSPORT:
		case CSCH_ATYPE_HUB:
		case CSCH_ATYPE_INVALID:
			break;
	}

	*type_score = 1024;
	return "unknown";
}


static rnd_hid_row_t *put_in_tree(abst_dlg_ctx_t *ctx, rnd_hid_attribute_t *attr, csch_ahdr_t *a)
{
	char *cell[3];
	rnd_hid_row_t *parent, *r;

	cell[0] = get_aname_mkdir(ctx, attr, a, &parent);
	cell[1] = NULL;
	cell[2] = NULL;

	if (a->omit)
		cell[1] = rnd_strdup("omitted");

	r = rnd_dad_tree_append_under(attr, parent, cell);
	r->user_data = a;

	htip_set(&ctx->aid2row, a->aid, r);

	return r;
}

static int cmp_aobj(const void *v1, const void *v2)
{
	csch_ahdr_t * const *a1 = v1;
	csch_ahdr_t * const *a2 = v2;
	const char *n1, *n2;
	int res, ts1, ts2;

	n1 = get_aname_dry(*a1, &ts1);
	n2 = get_aname_dry(*a2, &ts2);

	if (ts1 < ts2) return -1;
	if (ts1 > ts2) return +1;

	res = strcmp(n1, n2);
	if (res == 0) return 1;
	return res;
}

static void abst_prj2dlg(abst_dlg_ctx_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_tree_t *tree = attr->wdata;
	rnd_hid_row_t *r;
	char *cursor_path = NULL;
	htip_entry_t *e;

	htip_clear(&ctx->aid2row);

	/* remember cursor */
	r = rnd_dad_tree_get_selected(attr);
	if (r != NULL)
		cursor_path = rnd_strdup(r->path);

	/* remove existing items */
	rnd_dad_tree_clear(tree);

	/* add all non-ghost items recursively */
	if (ctx->prj->abst != NULL) {
		vtp0_t sorting = {0};
		long n;

		/* collect all non-ghost abstract objects in a temp array */
		vtp0_enlarge(&sorting, htip_length(&ctx->prj->abst->aid2obj));
		sorting.used = 0;
		for(e = htip_first(&ctx->prj->abst->aid2obj); e != NULL; e = htip_next(&ctx->prj->abst->aid2obj, e)) {
			csch_ahdr_t *a = e->value;
			if (a->ghost == NULL)
				vtp0_append(&sorting, a);
		}

		/* sort the temp array and add the result in the tree */
		qsort(sorting.array, sorting.used, sizeof(csch_ahdr_t *), cmp_aobj);
		for(n = 0; n < sorting.used; n++)
			put_in_tree(ctx, attr, sorting.array[n]);
		vtp0_uninit(&sorting);

		/* restore cursor */
		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->wtree, &hv);
			free(cursor_path);
		}
	}
}

static csch_ahdr_t *get_dlg_obj(abst_dlg_ctx_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);

	return r == NULL ? NULL : r->user_data;
}

static void abst_select_node_cb(rnd_hid_attribute_t *attrib, void *hid_ctx, rnd_hid_row_t *row)
{
	rnd_hid_tree_t *tree = attrib->wdata;
	abst_dlg_ctx_t *ctx = tree->user_ctx;
	csch_ahdr_t *a = get_dlg_obj(ctx);
	aattr_dlg_sheet2dlg_abstract(&ctx->right, a);
}

static void aattr_select(rnd_hid_attribute_t *attrib, void *hid_ctx, rnd_hid_row_t *row)
{
	rnd_hid_tree_t *tree = attrib->wdata;
	abst_dlg_ctx_t *ctx = tree->user_ctx;
	csch_ahdr_t *a = get_dlg_obj(ctx);

	aattr_dlg_ahist2dlg(&ctx->right, a); /* update history */
}

static void aattr_sources_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	abst_dlg_ctx_t *ctx = caller_data;
	aattr_sources(&ctx->right);
}

static void aattr_attr_src_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	abst_dlg_ctx_t *ctx = caller_data;
	aattr_attr_src(&ctx->right);
}

static void aattr_tree_unhide(rnd_hid_tree_t *tree, gdl_list_t *rowlist, int level, re_sei_t *preg, int noanon, int noomit)
{
	rnd_hid_row_t *r, *pr;

	for(r = gdl_first(rowlist); r != NULL; r = gdl_next(rowlist, r)) {
		int show = 1, recurse = 1, rmatch = 0;
		csch_ahdr_t *a = r->user_data;

		if ((preg != NULL) && (level > 0)) rmatch = re_sei_exec(preg, r->cell[0]);

		if ((level == 1) && noanon && (strncmp(r->cell[0], "anon_", 5) == 0)) show = recurse = 0;
		if (noomit && (a != NULL) && a->omit) show = recurse = 0;
		if (show && (preg != NULL) && !rmatch) { show = 0; }

		if (show) {
			rnd_dad_tree_hide_all(tree, &r->children, 0); /* if this is a node with children, show all children */
			for(pr = r; pr != NULL; pr = rnd_dad_tree_parent_row(tree, pr)) /* also show all parents so it is visible */
				pr->hide = 0;
			if (rmatch)
				recurse = 0; /* always show children of a matched node */
		}
		else
			r->hide = 1;

		if (recurse)
			aattr_tree_unhide(tree, &r->children, level+1, preg, noanon, noomit);
	}
}

static void aattr_filter_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_inp)
{
	abst_dlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_tree_t *tree = attr->wdata;
	rnd_hid_attribute_t *attr_filt = &ctx->dlg[ctx->wfilt];
	const char *text = attr_filt->val.str;
	rnd_hid_attribute_t *attr_noanon = &ctx->dlg[ctx->wnoanon];
	rnd_hid_attribute_t *attr_noomit = &ctx->dlg[ctx->wnoomit];
	int noanon = attr_noanon->val.lng, noomit = attr_noomit->val.lng;
	int have_filter_text;
	re_sei_t *regex = NULL;

	have_filter_text = ((text != NULL) && (*text != '\0'));

	/* hide everything */
	if (have_filter_text) {
		/* need to unhide for expand to work */
		rnd_dad_tree_hide_all(tree, &tree->rows, 0);
		rnd_dad_tree_update_hide(attr);
		rnd_dad_tree_expcoll(attr, NULL, 1, 1);
	}
	rnd_dad_tree_hide_all(tree, &tree->rows, 1);

	if (have_filter_text)
		regex = re_sei_comp(text);

	/* unhide hits and all their parents */
	aattr_tree_unhide(tree, &tree->rows, 0, regex, noanon, noomit);
	rnd_dad_tree_update_hide(attr);

	if (regex != NULL)
		re_sei_free(regex);
}


void sch_rnd_abst_dlg(csch_project_t *prj, long aid, const char *attr_name)
{
	abst_dlg_ctx_t *ctx;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	const char *hdr[] = {"name", "remarks", NULL};
	static const char *tooltip_noanon = "hide anon nets and components";
	static const char *tooltip_noomit = "hide objects that are omitted from the output\n(see also: the omit attribute)";

	ctx = htpp_get(&prj2dlg, prj);
	if (ctx != NULL) {
		TODO("raise?");
		goto jump;
	}

	ctx = calloc(sizeof(abst_dlg_ctx_t), 1);
	ctx->prj = prj;
	htpp_set(&prj2dlg, prj, ctx);
	htip_init(&ctx->aid2row, longhash, longkeyeq);

	RND_DAD_BEGIN_VBOX(ctx->dlg);
		RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
		RND_DAD_BEGIN_HPANE(ctx->dlg, "left-right");
			RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
			RND_DAD_BEGIN_VBOX(ctx->dlg); /* left */
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_TREE(ctx->dlg, 2, 1, hdr); /* top left: tree */
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
					RND_DAD_TREE_SET_CB(ctx->dlg, selected_cb, abst_select_node_cb);
					RND_DAD_TREE_SET_CB(ctx->dlg, ctx, ctx);
					ctx->wtree = RND_DAD_CURRENT(ctx->dlg);

				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_STRING(ctx->dlg);
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_INIT_FOCUS);
						RND_DAD_HELP(ctx->dlg, "filter: display only abstract objects matching this regex\ncase insensitive\n(if empty: display all)");
						RND_DAD_CHANGE_CB(ctx->dlg, aattr_filter_cb);
						ctx->wfilt = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_LABEL(ctx->dlg, " | no-anon:");
						RND_DAD_HELP(ctx->dlg, tooltip_noanon);
					RND_DAD_BOOL(ctx->dlg);
						RND_DAD_CHANGE_CB(ctx->dlg, aattr_filter_cb);
						ctx->wnoanon = RND_DAD_CURRENT(ctx->dlg);
						RND_DAD_HELP(ctx->dlg, tooltip_noanon);
					RND_DAD_LABEL(ctx->dlg, " | no-omit:");
						RND_DAD_HELP(ctx->dlg, tooltip_noomit);
					RND_DAD_BOOL(ctx->dlg);
						RND_DAD_CHANGE_CB(ctx->dlg, aattr_filter_cb);
						ctx->wnoomit = RND_DAD_CURRENT(ctx->dlg);
						RND_DAD_HELP(ctx->dlg, tooltip_noomit);
				RND_DAD_END(ctx->dlg);
			RND_DAD_END(ctx->dlg);

			RND_DAD_BEGIN_VBOX(ctx->dlg); /* right */
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				aattr_dlg_create(&ctx->right, ctx->dlg, prj, NULL);
				RND_DAD_BUTTON_CLOSES(ctx->dlg, clbtn);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

	RND_DAD_END(ctx->dlg);

	RND_DAD_DEFSIZE(ctx->dlg, 300, 400);
	RND_DAD_NEW("AbstDialog", ctx->dlg, "Abstract model", ctx, 0, abst_dlg_close_cb); /* type=local */

	aattr_dlg_init(&ctx->right);
	abst_prj2dlg(ctx);

	jump:;
	if (aid >= 0) {
		rnd_hid_row_t *r = htip_get(&ctx->aid2row, aid);
		if (r != NULL) {
			rnd_hid_attr_val_t hv;
			hv.str = r->path;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wtree, &hv);
			aattr_dlg_sheet2dlg_abstract(&ctx->right, r->user_data);
			if (attr_name != NULL)
				aattr_dlg_select_attr(&ctx->right, attr_name);
		}
	}
}

const char csch_acts_AbstractDialog[] = "AbstractDialog([abst_id [,attr_name]])";
const char csch_acth_AbstractDialog[] = "Bring up the current project's abstract model dialog. If abstract object id, abst_id is specified, left-side cursor is set at that object initially. If attr_name is specified, right-side cursor is set on that attribute.\n";
fgw_error_t csch_act_AbstractDialog(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;
	long aid = -1;
	const char *attr_name;

	RND_ACT_MAY_CONVARG(1, FGW_LONG, AbstractDialog, aid = argv[1].val.nat_long);
	RND_ACT_MAY_CONVARG(2, FGW_STR,  AbstractDialog, attr_name = argv[2].val.str);

	sch_rnd_abst_dlg((csch_project_t *)sheet->hidlib.project, aid, attr_name);
	return 0;
}

void csch_dlg_abst_compiled(csch_project_t *prj)
{
	abst_dlg_ctx_t *ctx = htpp_get(&prj2dlg, prj);
	if (ctx != NULL)
		abst_prj2dlg(ctx);
}


void csch_dlg_abst_init(void)
{
	htpp_init(&prj2dlg, ptrhash, ptrkeyeq);
}

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

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

	htpp_uninit(&prj2dlg);
}
