/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - GUI - library dialog
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *  (icons copied from pcb-rnd by the author)
 *
 *  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
 */

/* symbol library dialog */

#include <libcschem/config.h>

#include <ctype.h>
#include <genht/htip.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/hid/tool.h>

#include <libcschem/concrete.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/operation.h>
#include <libcschem/plug_library.h>
#include <libcschem/util_wirenet.h>

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

#include "adialogs_conf.h"

#define MAX_PARAMS 128

typedef struct library_dlg_ctx_s library_ctx_t;
typedef csch_lib_t library_ent_t;

#include "../../../src_3rd/rnd_inclib/dialogs/dlg_library_param.h"

typedef struct library_dlg_ctx_s {
	RND_DAD_DECL_NOINIT(dlg)
	csch_sheet_t *sheet;
	csch_lib_master_t *master;
	char *title;
	int wtree, wfilt, wpreview, wpend, wnopend, wprev_text, wedit;
	int wlocframe, wloc_del, wloc_refresh, wloc_edit, wloc_list;

	rnd_hidval_t timer;
	unsigned timer_active:1;
	unsigned active:1;
	unsigned last_clicked:1;

	library_param_ctx_t param; /* for the parametric */

	int modal;
	char *last_path;
	csch_sheet_t prsh; /* temp sheet for preview */
} library_dlg_ctx_t;

static htip_t sheetlib2dlg;


/* XPM */
static const char *xpm_edit_param[] = {
"16 16 4 1",
"@ c #000000",
"* c #7A8584",
"+ c #6EA5D7",
"  c None",
"                ",
"  @          @  ",
"  @ ++++++++ @  ",
" @  +      +  @ ",
" @  + @@@@ +  @ ",
" @  +      +  @ ",
"@   + @@   +   @",
"@   +      +   @",
"@   + @ @ @+   @",
"@   +      +   @",
" @  +   @@ +  @ ",
" @  + @@@@ +  @ ",
" @  +      +  @ ",
"  @ ++++++++ @  ",
"  @          @  ",
"                "
};


/* XPM */
static const char *xpm_refresh[] = {
"16 16 4 1",
"@ c #000000",
"* c #7A8584",
"+ c #6EA5D7",
"  c None",
"   @            ",
"   @@@@@@@      ",
"   @@@    @@    ",
"   @@@@     @   ",
"             @  ",
"             @  ",
"              @ ",
" @            @ ",
" @            @ ",
" @              ",
"  @             ",
"  @             ",
"   @     @@@@   ",
"    @@    @@@   ",
"      @@@@@@@   ",
"            @   ",
};

/* XPM */
static const char *xpm_loc_count[] = {
"16 16 4 1",
"@ c #000000",
"* c #7A8584",
"+ c #6EA5D7",
"  c None",

"                ",
"   @@@@  @@@@   ",
"   @++@  @++@   ",
"@@@@++@@@@++@@@@",
"@++++++++++++++@",
"@++++++++++++++@",
"@@@@++@@@@++@@@@",
"   @++@  @++@   ",
"   @++@  @++@   ",
"@@@@++@@@@++@@@@",
"@++++++++++++++@",
"@++++++++++++++@",
"@@@@++@@@@++@@@@",
"   @++@  @++@   ",
"   @@@@  @@@@   ",
"                "
};

/* XPM */
static const char *xpm_loc_del[] = {
"16 16 4 1",
"@ c #000000",
"* c #7A8584",
"+ c #6EA5D7",
"  c None",

"                ",
"                ",
"                ",
"   @@      @@   ",
"   @+@    @+@   ",
"    @+@  @+@    ",
"     @+@@+@     ",
"      @++@      ",
"      @++@      ",
"     @+@@+@     ",
"    @+@  @+@    ",
"   @+@    @+@   ",
"   @@      @@   ",
"                ",
"                ",
"                "
};


/* XPM */
static const char *xpm_loc_refresh[] = {
"16 16 4 1",
"@ c #000000",
"* c #7A8584",
"+ c #6EA5D7",
"  c None",
"   @            ",
"   @@@@@@@      ",
"   @@@    @@    ",
"   @@@@     @   ",
"             @  ",
"     @@@@@   @  ",
"     @        @ ",
" @   @@@      @ ",
" @   @@@      @ ",
" @   @          ",
"  @  @@@@@      ",
"  @             ",
"   @     @@@@   ",
"    @@    @@@   ",
"      @@@@@@@   ",
"            @   ",
};

/* XPM */
static const char *xpm_loc_edit[] = {
"16 16 4 1",
"@ c #000000",
"* c #7A8584",
"+ c #6EA5D7",
"  c None",
"                ",
"                ",
"  ++++++++++++  ",
"  +          +  ",
"  + @@@ @ @@ +  ",
"  +          +  ",
"  + @@  @@ @ +  ",
"  +          +  ",
"  + @ @ @@@ @+  ",
"  +          +  ",
"  +   @@  @@ +  ",
"  + @@@@@ @@ +  ",
"  +          +  ",
"  ++++++++++++  ",
"                ",
"                "
};

/***/

static void library_filter_reapply(void *hid_ctx, library_dlg_ctx_t *ctx);

static long sheetlib_id(const csch_sheet_t *sheet, const csch_lib_master_t *master)
{
	if (master->uid > 255)
		rnd_message(RND_MSG_ERROR, "Internal error: library type ID %d too large\nPlease report this bug.\n*** SAVE AND EXIT ASAP ***\n");
	if (sheet != NULL) return (sheet->uid << 8) + master->uid;
	return -master->uid;
}

static library_dlg_ctx_t *sheetlib_lookup(const csch_sheet_t *sheet, const csch_lib_master_t *master)
{
	return htip_get(&sheetlib2dlg, sheetlib_id(sheet, master));
}

static void sheetlib_set(const csch_sheet_t *sheet, const csch_lib_master_t *master, library_dlg_ctx_t *ctx)
{
	htip_set(&sheetlib2dlg, sheetlib_id(sheet, master), ctx);
}

static void sheetlib_del(const csch_sheet_t *sheet, const csch_lib_master_t *master)
{
	htip_pop(&sheetlib2dlg, sheetlib_id(sheet, master));
}


static csch_cgrp_t *first_grp(const csch_cgrp_t *src)
{
	htip_entry_t *e;
	for(e = htip_first(&src->id2obj); e != NULL; e = htip_next(&src->id2obj, e)) {
		csch_cgrp_t *obj = e->value;
		if (csch_obj_is_grp(&obj->hdr))
			return obj;
	}
	return NULL;
}

static void library_update_preview_sym(library_dlg_ctx_t *ctx, csch_lib_t *l, const char *parametric)
{
	rnd_box_t bbox;
	rnd_hid_attr_val_t hv;
	gds_t tmp = {0};
	char *param = NULL;

	sch_rnd_buffer_clear(&ctx->prsh);
	if (parametric != NULL) {
		char *name, *end;

		gds_append_str(&tmp, parametric);
		name = tmp.array;
		param = strchr(name, '(');
		if (param == NULL) {
			rnd_message(RND_MSG_ERROR, "library_update_preview(): internal error: parametric without parameters '%s'\n", parametric);
			goto error;
		}

		*param = '\0';
		param++;
		end = strrchr(param, ')');
		if (end != NULL)
			*end = '\0';

		if (ctx->sheet != NULL)
			l = csch_lib_search(ctx->sheet->libs.array[ctx->master->uid], name, CSCH_SLIB_PARAMETRIC);
		else
			l = csch_lib_search_master(ctx->master, name, CSCH_SLIB_PARAMETRIC);
		
		if (l == NULL) {
			rnd_message(RND_MSG_ERROR, "library_update_preview(): parametric '%s' not found in the library\n", name);
			goto error;
		}
		tmp.used = 0;
	}
	if (l != NULL) {
		csch_cgrp_t *sym;
		const csch_rtree_box_t *shb;
		csch_coord_t wm, hm;
		csch_lib_load(ctx->sheet, &ctx->prsh, l, param);
		sym = first_grp(&ctx->prsh.direct);

		shb = csch_sheet_bbox(&ctx->prsh);
		wm = (shb->x2 - shb->x1) / 4;
		hm = (shb->y2 - shb->y1) / 4;

		bbox.X1 = C2P(shb->x1 - wm);
		bbox.Y1 = C2P(shb->y1 - hm);
		bbox.X2 = C2P(shb->x2 + wm);
		bbox.Y2 = C2P(shb->y2 + hm);

		rnd_dad_preview_zoomto(&ctx->dlg[ctx->wpreview], &bbox);

		sch_rnd_buffer_clear(SCH_RND_PASTEBUFFER);
		if (sym != NULL) {
			csch_cobj_dup(SCH_RND_PASTEBUFFER, &SCH_RND_PASTEBUFFER->direct, &sym->hdr, 0, 0);
			rnd_tool_select_by_name(&ctx->sheet->hidlib, "buffer");
		}
	}

	if (l != NULL) {
		hv.str = "TODO: fill in tags";
	}
	else
		hv.str = "";

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

	error:;
	gds_uninit(&tmp);
}

static void library_update_preview(library_dlg_ctx_t *ctx, csch_lib_t *l, const char *parametric)
{
	if ((l != NULL) && (l->backend != NULL)) {
		if (l->backend->preview_text != NULL) {
			rnd_hid_attr_val_t hv;
			char *tmp = l->backend->preview_text(ctx->sheet, l, parametric);
			hv.str = tmp == NULL ? "" : tmp;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wprev_text, &hv);
			rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wpreview, 1);
			rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wprev_text, 0);
			free(tmp);
			return;
		}
	}

	/* fallback: assume a simple symbol */
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wpreview, 0);
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wprev_text, 0);
	library_update_preview_sym(ctx, l, parametric);
}


static void create_lib_tree_model_recurse(rnd_hid_attribute_t *attr, csch_lib_t *subtree, rnd_hid_row_t *parent_row)
{
	const char *name;
	char *cell[2];
	rnd_hid_row_t *row;

	name = subtree->name;
	if (*name == '?')
		name++;

	cell[0] = rnd_strdup(name);
	cell[1] = NULL;
	row = rnd_dad_tree_append_under(attr, parent_row, cell);
	row->user_data = subtree;

	if (subtree->type == CSCH_SLIB_DIR) {
		long n;
		for(n = 0; n < subtree->children.used; n++)
			create_lib_tree_model_recurse(attr, subtree->children.array[n], row);
	}
}

static void library_sheet2dlg(library_dlg_ctx_t *ctx)
{
	rnd_hid_attribute_t *attr;
	rnd_hid_tree_t *tree;
	rnd_hid_row_t *r;
	char *cursor_path = NULL;

	attr = &ctx->dlg[ctx->wtree];
	tree = attr->wdata;

	/* 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 items recursively */
	if (ctx->sheet == NULL) {
		htsp_entry_t *e;
		for(e = htsp_first(&ctx->master->roots); e != NULL; e = htsp_next(&ctx->master->roots, e))
			create_lib_tree_model_recurse(attr, e->value, NULL);
	}
	else {
		long n;
		csch_lib_root_t *libroot;

		/* local lib */
		libroot = ctx->sheet->local_libs.array[ctx->master->uid];
		if (libroot != NULL)
			create_lib_tree_model_recurse(attr, libroot->roots.array[0], NULL);

		/* external libs */
		libroot = NULL;
		if (ctx->master->uid < ctx->sheet->libs.used)
			libroot = ctx->sheet->libs.array[ctx->master->uid];

		if (libroot != NULL)
			for(n = 0; n < libroot->roots.used; n++)
				create_lib_tree_model_recurse(attr, libroot->roots.array[n], NULL);
	}

	/* 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 void library_tree_unhide(rnd_hid_tree_t *tree, gdl_list_t *rowlist, re_sei_t *preg, vtp0_t *taglist)
{
	rnd_hid_row_t *r, *pr;

	for(r = gdl_first(rowlist); r != NULL; r = gdl_next(rowlist, r)) {
		if ((preg == NULL) || (re_sei_exec(preg, r->cell[0]))) {
			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;
		}
		library_tree_unhide(tree, &r->children, preg, taglist);
	}
}




static rnd_bool library_mouse(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_mouse_ev_t kind, rnd_coord_t x, rnd_coord_t y)
{
	return rnd_false;
}

static void library_expose_grp(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e)
{
	library_dlg_ctx_t *ctx = prv->user_ctx;
	rnd_xform_t xform = {0};

	xform.fallback_pen = &ctx->sheet->direct;
	sch_rnd_draw_sheet(&ctx->prsh, gc, e, &xform);
}

static void library_expose(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e)
{
	TODO("do this only if master has ");
	library_expose_grp(attrib, prv, gc, e);
}


static void timed_update_preview_(library_dlg_ctx_t *ctx, const char *otext)
{
	if (otext != NULL) {
		TODO("load sym from path otext to buffer");
		library_update_preview(ctx, NULL, otext);
		rnd_gui->invalidate_all(rnd_gui);
	}
	else
		sch_rnd_buffer_clear(SCH_RND_PASTEBUFFER);
	ctx->timer_active = 0;
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wpend, 1);
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wnopend, 0);
}

static void timed_update_preview_cb(rnd_hidval_t user_data)
{
	library_dlg_ctx_t *ctx = user_data.ptr;
	const char *otext = ctx->dlg[ctx->wfilt].val.str;
	timed_update_preview_(ctx, otext);
}

static void timed_update_preview(library_dlg_ctx_t *ctx, int active)
{
	if (ctx->timer_active) {
		rnd_gui->stop_timer(rnd_gui, ctx->timer);
		ctx->timer_active = 0;
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wpend, 1);
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wnopend, 0);
	}

	if (active) {
		rnd_hidval_t user_data;
		user_data.ptr = ctx;
		ctx->timer = rnd_gui->add_timer(rnd_gui, timed_update_preview_cb, adialogs_conf.plugins.dialogs.library.preview_refresh_timeout, user_data);
		ctx->timer_active = 1;
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wpend, 0);
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wnopend, 1);
	}
}

static void update_edit_button(library_dlg_ctx_t *ctx)
{
	const char *otext = ctx->dlg[ctx->wfilt].val.str;
	int param_entered = 0, param_selected = 0;
	rnd_hid_row_t *row = rnd_dad_tree_get_selected(&(ctx->dlg[ctx->wtree]));

	if (row != NULL) {
		csch_lib_t *l = row->user_data;
		param_selected = (l != NULL) && (l->type == CSCH_SLIB_PARAMETRIC);
	}

	param_entered = !ctx->param.pactive && (otext != NULL) && (strchr(otext, '(') != NULL);

	rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wedit, param_selected || ((row == NULL) && param_entered));
}

static void library_set_filter(library_ctx_t *ctx, const char *text)
{
	rnd_hid_attr_val_t hv;

	hv.str = text;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wfilt, &hv);
}

static const char *library_get_ent_name(library_ent_t *e) { return e->name; }
static const char *library_ent_path(library_ent_t *e) { return e->realpath; }

#include "../../../src_3rd/rnd_inclib/dialogs/dlg_library_param.c"

static void library_dlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	library_dlg_ctx_t *ctx = caller_data;

	library_param_dialog_close(&ctx->param);

	csch_sheet_uninit(&ctx->prsh);
	sheetlib_del(ctx->sheet, ctx->master);
	free(ctx->title);
	ctx->title = NULL;
	if (!ctx->modal)
		free(ctx);
}

static void library_select(rnd_hid_attribute_t *attrib, void *hid_ctx, rnd_hid_row_t *row)
{
	rnd_hid_attr_val_t hv;
	rnd_hid_tree_t *tree = attrib->wdata;
	library_dlg_ctx_t *ctx = tree->user_ctx;
	int close_param = 1, is_local = 0;
	static csch_lib_t *last = NULL;
	const csch_lib_backend_t *be = NULL;

	ctx->last_clicked = 1;

	timed_update_preview(ctx, 0);

	library_update_preview(ctx, NULL, NULL);
	if (row != NULL) {
		csch_lib_t *l = row->user_data, *lpar;

		if (l->backend != NULL) {
			lpar = l->parent;
			is_local = (lpar != NULL) && (strcmp(lpar->name, "<local>") == 0);
			be = l->backend;
		}

		ctx->last_path = row->path;
		if (l != NULL) {
			if ((l->type == CSCH_SLIB_PARAMETRIC)) {
				if (last != l) { /* first click */
					library_select_show_param_example(ctx, l);
					update_edit_button(ctx);
				}
				else { /* second click */
					ctx->param.lib_ctx = ctx;
					library_param_dialog(&ctx->param, l, ctx->dlg[ctx->wfilt].val.str);
					close_param = 0;
				}
			}
			else if ((l->type == CSCH_SLIB_STATIC)) {
				library_update_preview(ctx, l, NULL);
				update_edit_button(ctx);
				rnd_gui->invalidate_all(rnd_gui);
			}
		}
		last = l;
	}

	if (close_param) {
		ctx->param.lib_ctx = ctx;
		library_param_dialog(&ctx->param, NULL, NULL);
	}

	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wlocframe, !is_local);

	if (be != NULL) {
		if ((be->loc_refresh_from_ext == NULL) && (be->loc_list == NULL) && (be->loc_del == NULL))
			is_local = 0;

		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wloc_list, (be->loc_list == NULL));
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wloc_del, (be->loc_del == NULL));
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wloc_refresh, (be->loc_refresh_from_ext == NULL));
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wloc_edit, (be->loc_edit == NULL));
	}

	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wlocframe, !is_local);

	hv.str = NULL;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wpreview, &hv);
}

static void library_refresh_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_inp)
{
	library_dlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
/*	rnd_hid_tree_t *tree = attr->wdata;*/
	csch_lib_t *lroot;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);
	const char *rootname;
	char *freeme = NULL;

	if (r == NULL) {
		rnd_message(RND_MSG_ERROR, "Please select a subtree to refresh\n");
		return;
	}

	/* Do not allow <local> refresh */
	for(lroot = r->user_data; lroot->parent != NULL; lroot = lroot->parent) ;
	rootname = lroot->name;
	if (strcmp(rootname, "<local>") == 0) {
		rnd_message(RND_MSG_ERROR, "Can not explicitly refresh <local>, it's refreshed automatically\n");
		return;
	}

	if ((rootname == NULL) || (*rootname == '\0'))
		rootname = lroot->realpath;

	rootname = freeme = rnd_strdup(rootname); /* rehash may change the ptr */

	if (csch_lib_rehash(ctx->sheet, ctx->master, r->user_data) == 0) {
		rnd_message(RND_MSG_INFO, "Refresh library '%s'\n", rootname);
		library_sheet2dlg(ctx);

		r = rnd_dad_tree_get_selected(attr);
		if (r != NULL) {
			library_update_preview(ctx, r->user_data, NULL);
			library_filter_reapply(hid_ctx, ctx);
		}
	}
	else
		rnd_message(RND_MSG_ERROR, "Failed to refresh '%s'\n", rootname);

	free(freeme);
}

static rnd_hid_row_t *find_sym_prefix_(rnd_hid_tree_t *tree, gdl_list_t *rowlist, const char *name, int namelen)
{
	rnd_hid_row_t *r, *pr;

	for(r = gdl_first(rowlist); r != NULL; r = gdl_next(rowlist, r)) {
		csch_lib_t *l = r->user_data;
		if ((rnd_strncasecmp(r->cell[0], name, namelen) == 0) && (l->type == CSCH_SLIB_PARAMETRIC))
			return r;
		pr = find_sym_prefix_(tree, &r->children, name, namelen);
		if (pr != NULL)
			return pr;
	}
	return NULL;
}

static rnd_hid_row_t *find_sym_prefix(library_ctx_t *ctx, const char *name, int namelen)
{
	rnd_hid_attribute_t *attr;
	rnd_hid_tree_t *tree;

	attr = &ctx->dlg[ctx->wtree];
	tree = attr->wdata;

	return find_sym_prefix_(tree, &tree->rows, name, namelen);
}

static void library_edit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_inp)
{
	library_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *attr;
	rnd_hid_row_t *r, *rnew;
	const char *otext = ctx->dlg[ctx->wfilt].val.str;
	char *name = NULL, *sep;
	int namelen;

	attr = &ctx->dlg[ctx->wtree];
	r = rnd_dad_tree_get_selected(attr);

	if (!ctx->last_clicked && (otext != NULL)) {
		get_from_filt:;
		name = rnd_strdup(otext);
		sep = strchr(name, '(');
		if (sep != NULL)
			*sep = '\0';
	}
	else if (r != NULL) {
		csch_lib_t *l = r->user_data;
		name = rnd_strdup(l->name);
		if (name != NULL) {
			rnd_hid_attr_val_t hv;
			hv.str = name;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wfilt, &hv);
		}
	}
	else if (otext != NULL)
		goto get_from_filt;

	if ((name == NULL) || (*name == '\0')) {
		rnd_message(RND_MSG_ERROR, "Failed to figure the name of the parametric symbol\n");
		return;
	}
	namelen = strlen(name);

	if ((r == NULL) || (rnd_strncasecmp(name, r->cell[0], namelen) != 0)) {
		/* no selection or wrong selection: go find the right one */
		rnew = find_sym_prefix(ctx, name, namelen);
	}
	else
		rnew = r;

	if (rnew != NULL) {
		if (r != rnew)
			rnd_dad_tree_jumpto(attr, rnew);
		ctx->param.lib_ctx = ctx;
		library_param_dialog(&ctx->param, rnew->user_data, ctx->dlg[ctx->wfilt].val.str);
	}
	else
		rnd_message(RND_MSG_ERROR, "No such parametric symbol: '%s'\n", name);

	free(name);
}


static void library_filter_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_inp__)
{
	library_dlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *attr_inp = &ctx->dlg[ctx->wfilt];
	rnd_hid_attribute_t *attr;
	rnd_hid_tree_t *tree;
	const char *otext;
	char *text, *sep, *para_start;
	int have_filter_text, is_para, is_para_closed = 0;

	ctx->last_clicked = 0;

	attr = &ctx->dlg[ctx->wtree];
	tree = attr->wdata;
	otext = attr_inp->val.str;
	if (otext == NULL) otext = "";
	text = rnd_strdup(otext);
	have_filter_text = (*text != '\0');

	para_start = strchr(otext, '(');
	is_para = (para_start != NULL);
	if (is_para)
		is_para_closed = (strchr(para_start, ')') != NULL);

	sep = strpbrk(text, " ()\t\r\n");
	if (sep != NULL)
		*sep = '\0';

	/* if an '(' is entered, stop new filtering, keep filter as is */
	if (is_para)
		goto skip_filter;

	/* hide or unhide 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);
	}
	else
		rnd_dad_tree_hide_all(tree, &tree->rows, 0);

	if (have_filter_text) { /* unhide hits and all their parents */
		char *tag, *next, *tags = NULL;
		vtp0_t taglist;
		re_sei_t *regex = NULL;

		if (!is_para) {
			tags = strchr(otext, ' ');
			if (tags != NULL) {
				*tags = '\0';
				tags++;
				while(isspace(*tags))
					tags++;
				if (*tags == '\0')
					tags = NULL;
			}
		}

		vtp0_init(&taglist);
		if (tags != NULL) {
			tags = rnd_strdup(tags);
			for (tag = tags; tag != NULL; tag = next) {
				next = strpbrk(tag, " \t\r\n");
				if (next != NULL) {
					*next = '\0';
					next++;
					while (isspace(*next))
						next++;
				}
				vtp0_append(&taglist, tag);
			}
		}

		if ((text != NULL) && (*text != '\0'))
			regex = re_sei_comp(text);

		library_tree_unhide(tree, &tree->rows, regex, &taglist);

		if (regex != NULL)
			re_sei_free(regex);
		vtp0_uninit(&taglist);
		free(tags);
	}

	rnd_dad_tree_update_hide(attr);

	skip_filter:;

	/* parametric symbols need to be refreshed on edit */
	if (is_para_closed)
		timed_update_preview(ctx, 1);

	update_edit_button(ctx);

	free(text);
}

static void library_filter_reapply(void *hid_ctx, library_dlg_ctx_t *ctx)
{
	library_filter_cb(hid_ctx, ctx, NULL);
}


static csch_lib_t *loclib_get_lib(library_dlg_ctx_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);

	if (r == NULL)
		return NULL;

	return r->user_data;
}


static void loclib_del_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	library_dlg_ctx_t *ctx = caller_data;
	csch_lib_t *l = loclib_get_lib(ctx);

	if ((l != NULL) && (l->backend != NULL) && (l->backend->loc_del != NULL))
		l->backend->loc_del(ctx->sheet, l);
}

static void loclib_count_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	library_dlg_ctx_t *ctx = caller_data;
	csch_lib_t *l = loclib_get_lib(ctx);

	if ((l != NULL) && (l->backend != NULL) && (l->backend->loc_list != NULL))
		l->backend->loc_list(ctx->sheet, l);
}

static void loclib_refresh_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	library_dlg_ctx_t *ctx = caller_data;
	csch_lib_t *l = loclib_get_lib(ctx);
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_row_t *r;

	if ((l != NULL) && (l->backend != NULL) && (l->backend->loc_refresh_from_ext != NULL)) {
		l->backend->loc_refresh_from_ext(ctx->sheet, l);

		r = rnd_dad_tree_get_selected(attr);
		library_update_preview(ctx, r->user_data, NULL);
	}
}

static void loclib_edit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn)
{
	library_dlg_ctx_t *ctx = caller_data;
	csch_lib_t *l = loclib_get_lib(ctx);

	if ((l != NULL) && (l->backend != NULL) && (l->backend->loc_edit != NULL))
		l->backend->loc_edit(ctx->sheet, l);
}


static char *sch_rnd_library_dlg(csch_sheet_t *sheet, const char *lib_type_name, int modal)
{
	library_dlg_ctx_t *ctx;
	rnd_hid_dad_buttons_t clbtn[]       = {{"Close", 0}, {NULL, 0}};
	rnd_hid_dad_buttons_t clbtn_modal[] = {{"Use selected", 1}, {"Cancel", 0}, {NULL, 0}};
	csch_lib_master_t *master = csch_lib_get_master(lib_type_name, 0);

	if (master == NULL) {
		rnd_message(RND_MSG_ERROR, "Library dialog: no such library type: %s\n", lib_type_name);
		return NULL;
	}

	ctx = sheetlib_lookup(sheet, master);
	if ((ctx != NULL) && (ctx->active)) {
		TODO("raise?");
		return NULL;
	}

	ctx = calloc(sizeof(library_dlg_ctx_t), 1);
	ctx->modal = modal;

	RND_DAD_BEGIN_VBOX(ctx->dlg);
		RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
		RND_DAD_BEGIN_HPANE(ctx->dlg, "left-right");
			/* left */
			RND_DAD_BEGIN_VBOX(ctx->dlg);
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_TREE(ctx->dlg, 1, 1, NULL);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
					RND_DAD_TREE_SET_CB(ctx->dlg, selected_cb, library_select);
					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 symbols matching this text\n(if empty: display all)");
						RND_DAD_CHANGE_CB(ctx->dlg, library_filter_cb);
						ctx->wfilt = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_PICBUTTON(ctx->dlg, xpm_edit_param);
						RND_DAD_HELP(ctx->dlg, "open GUI to edit the parameters\nof a parametric symbol");
						RND_DAD_CHANGE_CB(ctx->dlg, library_edit_cb);
						ctx->wedit = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_PICBUTTON(ctx->dlg, xpm_refresh);
						RND_DAD_HELP(ctx->dlg, "reload and refresh the currently selected\nmain tree of the library\nYou need to select an entry in the tree first and only that subtree will be rehashed.\nIf you have new roots configured/created not shown in the tree view above,\nre-scan the library (e.g. file/maintenance menu).");
						RND_DAD_CHANGE_CB(ctx->dlg, library_refresh_cb);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_FRAME);
					ctx->wlocframe = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_LABEL(ctx->dlg, "local lib:");
					RND_DAD_PICBUTTON(ctx->dlg, xpm_loc_count);
						RND_DAD_HELP(ctx->dlg, "list sheet symbols referencing this local lib entry");
						RND_DAD_CHANGE_CB(ctx->dlg, loclib_count_cb);
						ctx->wloc_list = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_PICBUTTON(ctx->dlg, xpm_loc_del);
						RND_DAD_HELP(ctx->dlg, "remove selected entry from local lib (and unlocalize all refs on sheet)");
						RND_DAD_CHANGE_CB(ctx->dlg, loclib_del_cb);
						ctx->wloc_del = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_PICBUTTON(ctx->dlg, xpm_loc_refresh);
						RND_DAD_HELP(ctx->dlg, "refresh local version from external lib");
						RND_DAD_CHANGE_CB(ctx->dlg, loclib_refresh_cb);
						ctx->wloc_refresh = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_PICBUTTON(ctx->dlg, xpm_loc_edit);
						RND_DAD_HELP(ctx->dlg, "Edit local lib entry");
						RND_DAD_CHANGE_CB(ctx->dlg, loclib_edit_cb);
						ctx->wloc_edit = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_END(ctx->dlg);
			RND_DAD_END(ctx->dlg);

			/* right */
			RND_DAD_BEGIN_VPANE(ctx->dlg, "right_top-bottom");
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_FRAME);

				RND_DAD_BEGIN_VBOX(ctx->dlg);
					/* right top: preview */
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_TIGHT);
					RND_DAD_PREVIEW(ctx->dlg, library_expose, library_mouse, NULL, NULL, NULL, 100, 100, ctx);
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_PRV_GFLIP);
						ctx->wpreview = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_END(ctx->dlg);

				/* right bottom */
				RND_DAD_BEGIN_VBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
					RND_DAD_LABEL(ctx->dlg, "Refreshing");
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_HIDE);
						ctx->wpend = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_LABEL(ctx->dlg, " ");
						ctx->wnopend = RND_DAD_CURRENT(ctx->dlg);
					TODO("rich text label");
					RND_DAD_LABEL(ctx->dlg, "");
						ctx->wprev_text = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_END(ctx->dlg);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		/* bottom */
		RND_DAD_BUTTON_CLOSES(ctx->dlg, modal ? clbtn_modal : clbtn);
	RND_DAD_END(ctx->dlg);

	/* set up the context */
	ctx->active = 1;
	ctx->sheet = sheet;
	ctx->master = master;
	csch_sheet_init(&ctx->prsh, NULL);

	uundo_freeze_serial(&ctx->prsh.undo);
	library_sheet2dlg(ctx);

	ctx->title = rnd_strdup_printf("%s Library for %s", ctx->master->name, ctx->sheet->hidlib.loadname);
	RND_DAD_NEW("library", ctx->dlg, ctx->title, ctx, modal, library_dlg_close_cb); /* type=local */

	sheetlib_set(sheet, master, ctx);

	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wlocframe, 1);

	if (modal) {
		char *rv = NULL;

		if (RND_DAD_RUN(ctx->dlg) == 1)
			rv = ctx->last_path == NULL ? NULL : rnd_strdup(ctx->last_path);
		RND_DAD_FREE(ctx->dlg);
		free(ctx);
		return rv;
	}

	return NULL;
}

const char csch_acts_LibraryDialog[] = "LibraryDialog([lib_type_name, [sheet|global, [modal]]])";
const char csch_acth_LibraryDialog[] = "Bring up the library dialog. Default lib_type_name is \"symbol\". \"Sheet\" is the sheet's local library (one dialog per sheet), \"global\" is all symbols mapped for any sheet seen in this session. In modal mode returns the tree path of the selected entry.\n";
fgw_error_t csch_act_LibraryDialog(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;
	int cmd = F_Global, modal = 0;
	const char *lib_type_name = "symbol", *smodal = NULL;
	char *rv = NULL;

	RND_ACT_CONVARG(1, FGW_STR, LibraryDialog, lib_type_name = argv[1].val.cstr);
	RND_ACT_MAY_CONVARG(2, FGW_KEYWORD, LibraryDialog, cmd = fgw_keyword(&argv[2]));
	RND_ACT_MAY_CONVARG(3, FGW_STR, LibraryDialog, smodal = argv[3].val.cstr);

	if ((smodal != NULL) && ((*smodal == 'm') || (*smodal == 'M')))
		modal = 1;

	RND_ACT_IRES(-1);

	switch(cmd) {
		case F_Global:
			rv = sch_rnd_library_dlg(NULL, lib_type_name, modal);
			RND_ACT_IRES(0);
			break;
		case F_Sheet:
			rv = sch_rnd_library_dlg(sheet, lib_type_name, modal);
			RND_ACT_IRES(0);
			break;
		default:
			rnd_message(RND_MSG_ERROR, "Library dialog: invalid first arg\n");
			break;
	}

	if (modal) {
		res->type = FGW_STR | FGW_DYN;
		res->val.str = rv;
	}
	else if (rv != NULL)
		free(rv);

	return 0;
}

void csch_dlg_library_preunload(csch_sheet_t *sheet)
{
	htip_entry_t *e;
	rnd_dad_retovr_t retovr = {0};

	for(e = htip_first(&sheetlib2dlg); e != NULL; e = htip_next(&sheetlib2dlg, e)) {
		library_dlg_ctx_t *ctx = e->value;
		if (ctx->sheet == sheet)
			rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
	}
}

void csch_dlg_library_changed(csch_sheet_t *sheet)
{
	htip_entry_t *e;
	for(e = htip_first(&sheetlib2dlg); e != NULL; e = htip_next(&sheetlib2dlg, e)) {
		library_dlg_ctx_t *ctx = e->value;
		if (ctx->sheet == sheet)
			library_sheet2dlg(ctx);
	}
}

void csch_dlg_library_init(void)
{
	htip_init(&sheetlib2dlg, longhash, longkeyeq);
}

void csch_dlg_library_uninit(void)
{
	rnd_dad_retovr_t retovr = {0};
	htip_entry_t *e;

	for(e = htip_first(&sheetlib2dlg); e != NULL; e = htip_next(&sheetlib2dlg, e)) {
		library_dlg_ctx_t *ctx = e->value;
		rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
	}

	htip_uninit(&sheetlib2dlg);
}
