/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - standard cschem forge
 *  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
 */

/* stance configuration dialog */

#include <libcschem/config.h>

#include <genht/htpp.h>
#include <genht/hash.h>
#include <genvector/vtl0.h>
#include <genlist/gendlist.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>

/* 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>

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

#include <plugins/sch_dialogs/quick_attr_util.h>

#include "std_forge.h"

/*** test benches ***/

typedef struct {
	csch_sheet_t *sheet;
	csch_oidpath_t idp;
	long wchk[1]; /* over-allocated for num_benches */
} test_bench_obj_t;

typedef struct {
	long wtoggle;
	char *name; /* strdup'd because the underlying stance.test_bench_values conf node value can be replaced any time */
} test_bench_row_t;

typedef struct test_bench_dlg_ctx_s {
	RND_DAD_DECL_NOINIT(dlg)
	long num_objs, num_benches;
	test_bench_obj_t **obj;
	unsigned modal:1;

	/* timed update */
	unsigned inhibit_pend:1; /* don't start the timer on the same dialog for our own mods */
	rnd_timed_chg_t pend;

	gdl_elem_t link;
	test_bench_row_t row[1]; /* over-allocated for num_benches */
} test_bench_dlg_ctx_t;

static gdl_list_t test_bench_dlgs;

/* Returns 1 if dsg affects ctx, 0 otherwise */
static int sheet_in_dlg(test_bench_dlg_ctx_t *ctx, rnd_design_t *dsg)
{
	long n;
	for(n = 0; n < ctx->num_objs; n++) {
		if (&ctx->obj[n]->sheet->hidlib == dsg)
			return 1;
	}
	return 0;
}

static const char *sym_name(csch_cgrp_t *obj)
{
	const char *name = NULL;

	if (obj != NULL)
		name = csch_attrib_get_str(&obj->attr, "name");
	if (name == NULL)
		name = "?";

	return name;
}

/* Look up bench name in the dialog and return corresponding row index or -1 */
static int bench_name2idx(test_bench_dlg_ctx_t *ctx, const char *name)
{
	int n;
	for(n = 0; n < ctx->num_benches; n++) {
		const char *lab = ctx->row[n].name;
		if (strcmp(lab, name) == 0)
			return n;
	}
	return -1;
}

static void test_bench_free(test_bench_dlg_ctx_t *ctx)
{
	long n;

	rnd_timed_chg_cancel(&ctx->pend);

	if (ctx->modal)
		return; /* will be free'd once at the end, after run() */

	for(n = 0; n < ctx->num_objs; n++) {
		csch_oidpath_free(&ctx->obj[n]->idp);
		free(ctx->obj[n]);
	}
	for(n = 0; n < ctx->num_benches; n++)
		free(ctx->row[n].name);

	if (ctx->link.parent != NULL)
		gdl_remove(&test_bench_dlgs, ctx, link);

	free(ctx);
}

static void test_bench_dlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	test_bench_free(caller_data);
}

static void sch_rnd_test_bench_sch2dlg(test_bench_dlg_ctx_t *ctx)
{
	long n, m;
	vts0_t rev = {0};
	gds_t st = {0};
	rnd_hid_attr_val_t hv;

	gds_enlarge(&st, ctx->num_benches+1);

	for(n = 0; n < ctx->num_objs; n++) {
		csch_cgrp_t *obj = (csch_cgrp_t *)csch_oidpath_resolve(ctx->obj[n]->sheet, &ctx->obj[n]->idp);
		csch_attrib_t *a;

		if (obj == NULL) {
			for(m = 0; m < ctx->num_benches; m++)
				rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->obj[n]->wchk[m], 0);
			continue;
		}


		memset(st.array, 0, ctx->num_benches);
		a = csch_attrib_get(&obj->attr, "forge-if/test_bench");
		if ((a != NULL) && (a->val == NULL) && (a->arr.used > 1)) {
			const char *expr = a->arr.array[0];
			int r = forge_condition_get_test_bench(&rev, "symbol", sym_name(obj), "test_bench", expr);
			rnd_trace("obj update '%s' -> %d!\n", expr, r);
			for(m = 0; m < rev.used; m += 3) {
				int idx;
				const char *var1 = rev.array[m], *var2 = rev.array[m+1], *val = rev.array[m+2];
				if ((strcmp(var1, "stance") == 0) && (strcmp(var2, "test_bench") == 0) && (val[0] != '\0')) {
					idx = bench_name2idx(ctx, val);
					rnd_trace("%s.%s != '%s' %d\n", var1, var2, val);
					if (idx >= 0)
						st.array[idx] = 1;
				}
			}
		}

		for(m = 0; m < ctx->num_benches; m++) {
			hv.lng = st.array[m];
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->obj[n]->wchk[m], &hv);
		}
	}

	vts0_uninit(&rev);
	gds_uninit(&st);
}

static void create_testbench_attrib_arr(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *expr, const char *src_str)
{
	csch_source_arg_t *src = csch_attrib_src_p("std_forge", src_str);

	uundo_freeze_serial(&sheet->undo);
	csch_attr_modify_str(sheet, obj, 250, "forge-if/test_bench", expr, src, 1);
	csch_attr_modify_conv_to_arr(sheet, obj, "forge-if/test_bench", 1);
	csch_attr_arr_modify_ins_before(sheet, obj, "forge-if/test_bench", 1, "sub,^.*$,yes,omit", 1);
	csch_attr_arr_modify_ins_before(sheet, obj, "forge-if/test_bench", 1, "scalar,omit", 1);
	uundo_unfreeze_serial(&sheet->undo);
	uundo_inc_serial(&sheet->undo);
}

static void test_bench_regen_obj(test_bench_dlg_ctx_t *ctx, int obj_idx)
{
	test_bench_obj_t *bobj = ctx->obj[obj_idx];
	csch_sheet_t *sheet = bobj->sheet;
	csch_cgrp_t *obj = (csch_cgrp_t *)csch_oidpath_resolve(sheet, &bobj->idp);
	gds_t tmp = {0};
	int n;

	if (obj == NULL)
		return;

	for(n = 0; n < ctx->num_benches; n++) {
		if (ctx->dlg[bobj->wchk[n]].val.lng) {
			if (tmp.used == 0)
				gds_append_str(&tmp, "(stance.test_bench != \"\")");
			gds_append_str(&tmp, " && (stance.test_bench != \"");
			gds_append_str(&tmp, ctx->row[n].name);
			gds_append_str(&tmp, "\")");
		}
	}


	rnd_trace(" tb expr: '%s'\n", tmp.array);

	if (tmp.used > 0) {
		csch_attrib_t *a = csch_attrib_get(&obj->attr, "forge-if/test_bench");
		if ((a != NULL) && (a->val == NULL) && (a->arr.used > 1)) {
			/* existing array attribute, edit first entry */
			csch_attr_arr_modify_str(sheet, obj, "forge-if/test_bench", 0, tmp.array, 1);
		}
		else
			create_testbench_attrib_arr(sheet, obj, tmp.array, "test bench dialog");
	}
	else
		csch_attr_modify_del(sheet, obj, "forge-if/test_bench", 1);
	

	gds_uninit(&tmp);
}

/* abuse geo fields - for checkbox these are not used by the HID code */
#define OBJ_IDX geo_width
#define BENCH_IDX geo_height

static void test_bench_chkbox_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *achkbox)
{
	test_bench_dlg_ctx_t *ctx = caller_data;
	long wchkbox = achkbox - ctx->dlg;
	int obj_idx = ctx->dlg[wchkbox].OBJ_IDX;
	int bench_idx = ctx->dlg[wchkbox].BENCH_IDX;
	int val = achkbox->val.lng;

	rnd_trace("clicked obj %d bench %d set to %d\n", obj_idx, bench_idx, val);

	ctx->inhibit_pend = 1;
	test_bench_regen_obj(ctx, obj_idx);
	ctx->inhibit_pend = 0;
}

static void test_bench_row_toggle_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *abutton)
{
	test_bench_dlg_ctx_t *ctx = caller_data;
	long wbutton = abutton - ctx->dlg;
	int bench_idx = ctx->dlg[wbutton].BENCH_IDX;
	rnd_hid_attribute_t *achk1 = ctx->dlg + ctx->obj[0]->wchk[bench_idx]; /* first checkbox in the row */
	int n, val = !achk1->val.lng;
	rnd_hid_attr_val_t hv;
	csch_sheet_t *sheet = ctx->obj[0]->sheet;

	uundo_freeze_serial(&sheet->undo);
	hv.lng = val;
	for(n = 0; n < ctx->num_objs; n++) {
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->obj[n]->wchk[bench_idx], &hv);
		test_bench_regen_obj(ctx, n);
	}
	uundo_unfreeze_serial(&sheet->undo);
	uundo_inc_serial(&sheet->undo);
}

static void timed_update_cb(void *uctx)
{
	test_bench_dlg_ctx_t *ctx = uctx;
	sch_rnd_test_bench_sch2dlg(ctx);
}

static int sch_rnd_test_bench_dlg(const vtp0_t *objs, int modal)
{
	test_bench_dlg_ctx_t *ctx;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	rnd_conf_listitem_t *item_li;
	const char *item_str;
	int idx;
	long n, i, numo, numb;

	numb = rnd_conflist_length(&conf_core.stance.test_bench_values);
	if (numb < 1) {
		rnd_message(RND_MSG_ERROR, "Please define stance.test_bench values first\nHint: file menu, project, project stances\n");
		return -1;
	}

	for(n = numo = 0; n < objs->used; n++) {
		csch_cgrp_t *g = objs->array[n];
		if (!csch_obj_is_grp(&g->hdr)) continue;
		if (g->role != CSCH_ROLE_SYMBOL) continue;
		numo++;
	}

	if (numo == 0) {
		rnd_message(RND_MSG_ERROR, "No symbols included in target objects.\n");
		return -1;
	}

	ctx = calloc(sizeof(test_bench_dlg_ctx_t) + sizeof(test_bench_row_t)*numb, 1);
	ctx->num_benches = numb;
	ctx->num_objs = numo;
	ctx->obj = malloc(sizeof(test_bench_obj_t *) * ctx->num_objs);
	rnd_timed_chg_init(&ctx->pend, timed_update_cb, ctx);

	gdl_append(&test_bench_dlgs, ctx, link);

	for(n = 0; n < ctx->num_objs; n++) {
		ctx->obj[n] = calloc(sizeof(test_bench_obj_t) + sizeof(long) * ctx->num_benches, 1);
	}

	for(n = numo = 0; n < objs->used; n++) {
		csch_cgrp_t *g = objs->array[n];
		if (!csch_obj_is_grp(&g->hdr)) continue;
		if (g->role != CSCH_ROLE_SYMBOL) continue;
		ctx->obj[numo]->sheet = g->hdr.sheet;
		csch_oidpath_from_obj(&ctx->obj[numo]->idp, &g->hdr);
		numo++;
	}


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

		RND_DAD_BEGIN_TABLE(ctx->dlg, ctx->num_objs+2);
			RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);

			/* header row */
			RND_DAD_LABEL(ctx->dlg, "");
			for(n = 0; n < ctx->num_objs; n++) {
				csch_cgrp_t *grp = (csch_cgrp_t *)csch_oidpath_resolve(ctx->obj[n]->sheet, &ctx->obj[n]->idp);
				RND_DAD_LABEL(ctx->dlg, sym_name(grp));
			}
			RND_DAD_LABEL(ctx->dlg, "");

			/* data rows */
			rnd_conf_loop_list_str(&conf_core.stance.test_bench_values, item_li, item_str, idx) {
				RND_DAD_LABEL(ctx->dlg, item_str);
					ctx->row[idx].name = rnd_strdup(item_str);
				for(n = 0; n < ctx->num_objs; n++) {
					RND_DAD_BOOL(ctx->dlg);
						ctx->obj[n]->wchk[idx] = i = RND_DAD_CURRENT(ctx->dlg);
						ctx->dlg[i].OBJ_IDX = n;
						ctx->dlg[i].BENCH_IDX = idx;
						RND_DAD_CHANGE_CB(ctx->dlg, test_bench_chkbox_cb);
				}
				if (ctx->num_objs > 1) {
					RND_DAD_BUTTON(ctx->dlg, "Tgl!");
						RND_DAD_HELP(ctx->dlg, "Toggle all in row: set them all on or all off");
						ctx->row[idx].wtoggle = i = RND_DAD_CURRENT(ctx->dlg);
						ctx->dlg[i].BENCH_IDX = idx;
						RND_DAD_CHANGE_CB(ctx->dlg, test_bench_row_toggle_cb);
				}
				else
					RND_DAD_LABEL(ctx->dlg, "");
			}

		RND_DAD_END(ctx->dlg);

		RND_DAD_LABEL(ctx->dlg, "A cell marked means the given symbol (column)");
		RND_DAD_LABEL(ctx->dlg, "is included in the given test bench (row).");

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

	RND_DAD_DEFSIZE(ctx->dlg, 600, 300);
	RND_DAD_NEW("TestBenchesDialog", ctx->dlg, "Test benches quick edit", ctx, modal, test_bench_dlg_close_cb); /* type=local */

	sch_rnd_test_bench_sch2dlg(ctx);

	if (modal) {
		ctx->modal = 1;
		RND_DAD_RUN(ctx->dlg);
		ctx->modal = 0; /* permit free() */
		test_bench_free(ctx);
	}

	return 0;
}

/* Returns 0 on success and loads lst with objs */
static int get_objlist(csch_sheet_t *sheet, const char *cmd, vtp0_t *lst, const char *actname, const char *prompt)
{
	if (rnd_strncasecmp(cmd, "object", 6) == 0) {
		if (cmd[6] == ':') {
			csch_chdr_t *obj;
			csch_oidpath_t idp = {0};

			if (csch_oidpath_parse(&idp, cmd+7) != 0) {
				rnd_message(RND_MSG_ERROR, "%s(): Failed to convert object ID: '%s'\n", actname, cmd+7);
				return FGW_ERR_ARG_CONV;
			}
			obj = csch_oidpath_resolve(sheet, &idp);
			csch_oidpath_free(&idp);
			vtp0_append(lst, obj);
		}
		else {
			csch_chdr_t *obj;
			rnd_coord_t x, y;

			rnd_hid_get_coords(prompt, &x, &y, 0);
			obj = sch_rnd_search_first_gui_inspect(sheet, sch_rnd_crosshair_x, sch_rnd_crosshair_y);
			if (obj == NULL)
				return 0;
			vtp0_append(lst, obj);
		}

	}
	else if (rnd_strcasecmp(cmd, "selected") == 0) {
		csch_search_all_selected(sheet, NULL, lst, 1);
	}
	else {
		rnd_message(RND_MSG_ERROR, "%s() invalid first argument\n", actname);
		return -1;
	}
	return 0;
}

const char csch_acts_TestBenchDialog[] = "TestBenchDialog(object[:oidpath]|selected)";
const char csch_acth_TestBenchDialog[] = "Open the test bench selector for the current object or for selected objects.\n";
fgw_error_t csch_act_TestBenchDialog(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 *cmd;
	vtp0_t lst = {0};

	RND_ACT_CONVARG(1, FGW_STR, TestBenchDialog, cmd = argv[1].val.str);

	if (get_objlist(sheet, cmd, &lst, "TestBenchDialog", "Test bench edit dialog: select object") != 0)
		return FGW_ERR_ARG_CONV;

	sch_rnd_test_bench_dlg(&lst, 0);
	vtp0_uninit(&lst);
	return 0;
}

const char csch_acts_quick_attr_forge__if__test_bench[] = "quick_attr_forge__if__test_bench(objptr)";
const char csch_acth_quick_attr_forge__if__test_bench[] = "Quick Attribute Edit for core data model's symbol connect attribute (attribute based symbol terminal to network connection table)";
fgw_error_t csch_act_quick_attr_forge__if__test_bench(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	vtp0_t lst = {0};
	csch_cgrp_t *grp;

	QUICK_ATTR_GET_GRP(grp, "quick_attr_forge__if__test_bench");

	vtp0_append(&lst, grp);
	sch_rnd_test_bench_dlg(&lst, 1);

	vtp0_uninit(&lst);
	RND_ACT_IRES(1); /* refresh the attribute editor */
	return 0;
}

static void test_bench_modify(csch_sheet_t *sheet, csch_cgrp_t *grp, int del, const char *benchname, gds_t *tmp)
{
	int has = 0;
	vts0_t rev = {0};
	csch_attrib_t *a;
	int m;

	tmp->used = 0;

	if (!csch_obj_is_grp(&grp->hdr))
		return;

	a = csch_attrib_get(&grp->attr, "forge-if/test_bench");
	if ((a != NULL) && (a->val == NULL) && (a->arr.used > 1)) {
		const char *expr = a->arr.array[0];
		forge_condition_get_test_bench(&rev, "symbol", sym_name(grp), "test_bench", expr);
		for(m = 0; m < rev.used; m += 3) {
			const char *var1 = rev.array[m], *var2 = rev.array[m+1], *val = rev.array[m+2];
			if ((strcmp(var1, "stance") == 0) && (strcmp(var2, "test_bench") == 0) && (strcmp(val, benchname) == 0)) {
				has = 1;
				break;
			}
		}
	}

	/* we do not need to do anything if object already has it the way the user wants */
	if (del && !has) goto quit;
	if (!del && has) goto quit;

	if (del) {
		int copied = 0;

		gds_append_str(tmp, "(stance.test_bench != \"\")");

		for(m = 0; m < rev.used; m += 3) {
			const char *var1 = rev.array[m], *var2 = rev.array[m+1], *val = rev.array[m+2];

			if ((strcmp(var1, "stance") == 0) && (strcmp(var2, "test_bench") == 0) && (strcmp(val, benchname) == 0))
				continue;

			if (*val == '\0')
				continue;

			gds_append_str(tmp, " && (stance.test_bench != \"");
			gds_append_str(tmp, val);
			gds_append_str(tmp, "\")");
			copied++;
		}

		/* remove attr if expression became empty */
		if (copied == 0) {
			csch_attr_modify_del(sheet, grp, "forge-if/test_bench", 1);
			goto quit;
		}
	}
	else { /* add */
		if ((a == NULL) || (a->val != NULL) || (a->arr.used <= 1)) {
			/* create from scratch */
			gds_append_str(tmp, "(stance.test_bench != \"\")");
			gds_append_str(tmp, " && (stance.test_bench != \"");
			gds_append_str(tmp, benchname);
			gds_append_str(tmp, "\")");
		}
		else {
			/* extend existing */
			const char *expr = a->arr.array[0];
			gds_append_str(tmp, expr);
			gds_append_str(tmp, " && (stance.test_bench != \"");
			gds_append_str(tmp, benchname);
			gds_append_str(tmp, "\")");
		}
	}
	create_testbench_attrib_arr(sheet, grp, tmp->array, "TestBenchModify() action");

	quit:;
	vts0_uninit(&rev);
}

const char csch_acts_TestBenchModify[] = "TestBenchModify(object[:oidpath]|selected, add|del, testbench_name)";
const char csch_acth_TestBenchModify[] = "Modify test bench affiliation of object(s), adding or deleting test benches in which the given objects are participating.\n";
fgw_error_t csch_act_TestBenchModify(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 *cmd, *how, *bench;
	vtp0_t lst = {0};
	gds_t tmp = {0};
	long n;
	int del;

	RND_ACT_CONVARG(1, FGW_STR, TestBenchModify, cmd = argv[1].val.str);
	RND_ACT_CONVARG(2, FGW_STR, TestBenchModify, how = argv[2].val.str);
	RND_ACT_CONVARG(3, FGW_STR, TestBenchModify, bench = argv[3].val.str);

	if (rnd_strcasecmp(how, "del") == 0) {
		del = 1;
	}
	else if (rnd_strcasecmp(how, "add") == 0) {
		del = 0;
	}
	else {
		rnd_message(RND_MSG_ERROR, "Invalid second argument in TestBenchModify()\n");
		return FGW_ERR_ARG_CONV;
	}

	if (get_objlist(sheet, cmd, &lst, "TestBenchModify", "Test bench edit dialog: select object") != 0)
		return FGW_ERR_ARG_CONV;

	for(n = 0; n < lst.used; n++)
		test_bench_modify(sheet, lst.array[n], del, bench, &tmp);

	gds_uninit(&tmp);
	vtp0_uninit(&lst);
	return 0;
}

/*** Common ***/

/* trigger gui update if any the test_bench_values stance conf changes */
static void test_bench_change_post(rnd_conf_native_t *cfg, int arr_idx, void *user_data)
{
/*	rnd_design_t *curr = rnd_multi_get_current();*/
}

static void test_bench_preunload(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	test_bench_dlg_ctx_t *ctx, *next;
	for(ctx = gdl_first(&test_bench_dlgs); ctx != NULL; ctx = next) {
		next = gdl_next(&test_bench_dlgs, ctx);
		if (sheet_in_dlg(ctx, hidlib)) {
			rnd_dad_retovr_t retovr = {0};
			gdl_remove(&test_bench_dlgs, ctx, link);
			rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
		}
	}
}

static void test_bench_onchg(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	test_bench_dlg_ctx_t *ctx;
	for(ctx = gdl_first(&test_bench_dlgs); ctx != NULL; ctx = gdl_next(&test_bench_dlgs, ctx))
		if (!ctx->inhibit_pend && sheet_in_dlg(ctx, hidlib))
			rnd_timed_chg_schedule(&ctx->pend);
}

static rnd_conf_hid_id_t test_bench_hid_id;
static const char cookie[] = "test bench gui";
void csch_dlg_test_bench_init(void)
{
	static rnd_conf_hid_callbacks_t cb;
	rnd_conf_native_t *cn = rnd_conf_get_field("stance/test_bench_values");

	rnd_event_bind(CSCH_EVENT_SHEET_PREUNLOAD, test_bench_preunload, NULL, cookie);
	rnd_event_bind(CSCH_EVENT_SHEET_EDITED, test_bench_onchg, NULL, cookie);

	/* get a global conf change callback */
	cb.val_change_post = test_bench_change_post;
	test_bench_hid_id = rnd_conf_hid_reg(cookie, &cb);

	if (cn != NULL) {
		memset(&cb, 0, sizeof(rnd_conf_hid_callbacks_t));
		cb.val_change_post = test_bench_change_post;
		rnd_conf_hid_set_cb(cn, test_bench_hid_id, &cb);
	}
}

void csch_dlg_test_bench_uninit(void)
{
	rnd_event_unbind_allcookie(cookie);
	rnd_conf_hid_unreg(cookie);
}
