/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  Copyright (C) 2019 Tibor 'Igor2' Palinkas
 *
 *  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/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include "event.h"
#include "netlist.h"
#include <genvector/vtp0.h>

const char *dlg_netlist_cookie = "netlist dialog";

typedef struct {
	PCB_DAD_DECL_NOINIT(dlg)
	pcb_board_t *pcb;
	pcb_box_t bb_prv;
	int wnetlist, wprev, wtermlist;
	int wsel, wunsel, wfind, wunfind, wrats, wnorats, wripup, waddrats, wrename, wmerge, wattr;
	int active; /* already open - allow only one instance */
} netlist_ctx_t;

netlist_ctx_t netlist_ctx;

static void netlist_close_cb(void *caller_data, pcb_hid_attr_ev_t ev)
{
	netlist_ctx_t *ctx = caller_data;
	PCB_DAD_FREE(ctx->dlg);
	memset(ctx, 0, sizeof(netlist_ctx_t));
	pcb_event(&PCB->hidlib, PCB_EVENT_GUI_LEAD_USER, "cci", 0, 0, 0);
}

/* returns allocated net name for the currently selected net */
static char *netlist_data2dlg_netlist(netlist_ctx_t *ctx)
{
	pcb_hid_attribute_t *attr;
	pcb_hid_tree_t *tree;
	pcb_hid_row_t *r;
	char *cell[4], *cursor_path = NULL;
	pcb_net_t **n, **nets;

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

	/* remember cursor */
	r = pcb_dad_tree_get_selected(attr);
	if (r != NULL)
		cursor_path = pcb_strdup(r->cell[0]);

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

	nets = pcb_netlist_sort(&ctx->pcb->netlist[1]);
	if (nets != NULL) {
		cell[2] = NULL;
		for(n = nets; *n != NULL; n++) {
			cell[0] = pcb_strdup((*n)->name);
			cell[1] = pcb_strdup((*n)->inhibit_rats ? "*" : "");
			pcb_dad_tree_append(attr, NULL, cell);
		}
		free(nets);

		/* restore cursor */
		if (cursor_path != NULL) {
			pcb_hid_attr_val_t hv;
			hv.str = cursor_path;
			pcb_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wnetlist, &hv);
		}
	}
	return cursor_path;
}

static void netlist_data2dlg_connlist(netlist_ctx_t *ctx, pcb_net_t *net)
{
	pcb_hid_attribute_t *attr;
	pcb_hid_tree_t *tree;
	pcb_hid_row_t *r;
	char *cell[2], *cursor_path = NULL;
	pcb_net_term_t *t;

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

	/* remember cursor */
	if (net != NULL) {
		r = pcb_dad_tree_get_selected(attr);
		if (r != NULL)
			cursor_path = pcb_strdup(r->cell[0]);
	}

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

	if (net == NULL)
		return;

	cell[1] = NULL;
	for(t = pcb_termlist_first(&net->conns); t != NULL; t = pcb_termlist_next(t)) {
		cell[0] = pcb_concat(t->refdes, "-", t->term, NULL);
		pcb_dad_tree_append(attr, NULL, cell);
	}

	/* restore cursor */
	if (cursor_path != NULL) {
		pcb_hid_attr_val_t hv;
		hv.str = cursor_path;
		pcb_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wtermlist, &hv);
		free(cursor_path);
	}
}


static void netlist_data2dlg(netlist_ctx_t *ctx)
{
	pcb_net_t *curnet = NULL;
	char *curnetname = netlist_data2dlg_netlist(ctx);

	if (curnetname != NULL)
		curnet = pcb_net_get(ctx->pcb, &ctx->pcb->netlist[PCB_NETLIST_EDITED], curnetname, 0);
	free(curnetname);
	netlist_data2dlg_connlist(ctx, curnet);
}

static void netlist_force_redraw(netlist_ctx_t *ctx)
{
	pcb_dad_preview_zoomto(&ctx->dlg[ctx->wprev], &ctx->bb_prv);
}


static void netlist_row_selected(pcb_hid_attribute_t *attrib, void *hid_ctx, pcb_hid_row_t *row)
{
	pcb_hid_tree_t *tree = attrib->wdata;
	netlist_ctx_t *ctx= tree->user_ctx;
	const char *netname = NULL;
	pcb_net_t *net = NULL;

	if (row != NULL)
		netname = row->cell[0];
	if (netname != NULL)
		net = pcb_net_get(ctx->pcb, &ctx->pcb->netlist[PCB_NETLIST_EDITED], netname, 0);
	netlist_data2dlg_connlist(ctx, net);
	pcb_event(&PCB->hidlib, PCB_EVENT_GUI_LEAD_USER, "cci", 0, 0, 0);
	netlist_force_redraw(ctx);
}

static void termlist_row_selected(pcb_hid_attribute_t *attrib, void *hid_ctx, pcb_hid_row_t *row)
{
	pcb_hid_tree_t *tree = attrib->wdata;
	netlist_ctx_t *ctx= tree->user_ctx;
	char *refdes, *term;
	pcb_any_obj_t *obj;

	pcb_event(&PCB->hidlib, PCB_EVENT_GUI_LEAD_USER, "cci", 0, 0, 0);
	if (row == NULL)
		return;
	refdes = pcb_strdup(row->cell[0]);
	term = strchr(refdes, '-');
	if (term != NULL) {
		*term = '\0';
		term++;
		obj = pcb_term_find_name(ctx->pcb, ctx->pcb->Data, PCB_LYT_COPPER, refdes, term, NULL, NULL);
		if (obj != NULL) {
			pcb_coord_t x, y;
			pcb_obj_center(obj, &x, &y);
			pcb_event(&PCB->hidlib, PCB_EVENT_GUI_LEAD_USER, "cci", x, y, 1);
		}
	}
	free(refdes);
}

static void netlist_button_cb(void *hid_ctx, void *caller_data, pcb_hid_attribute_t *attr)
{
	netlist_ctx_t *ctx = caller_data;
	pcb_hid_attribute_t *atree;
	int w = attr - ctx->dlg;
	pcb_hid_row_t *r;
	const char *name;

	atree = &ctx->dlg[ctx->wnetlist];
	r = pcb_dad_tree_get_selected(atree);
	if (r == NULL)
		return;
	name = pcb_strdup(r->cell[0]);

	if (w == ctx->wsel)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "select", name, NULL);
	else if (w == ctx->wunsel)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "unselect", name, NULL);
	else if (w == ctx->wfind) {
		pcb_actionva(&ctx->pcb->hidlib, "connection", "reset", NULL);
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "find", name, NULL);
	}
	else if (w == ctx->wunfind)
		pcb_actionva(&ctx->pcb->hidlib, "connection", "reset", NULL);
	else if (w == ctx->wrats)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "rats", name, NULL);
	else if (w == ctx->wnorats)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "norats", name, NULL);
	else if (w == ctx->wripup)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "ripup", name, NULL);
	else if (w == ctx->waddrats)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "AddRats", name, NULL);
	else if (w == ctx->wrename)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "rename", name, NULL);
	else if (w == ctx->wmerge)
		pcb_actionva(&ctx->pcb->hidlib, "netlist", "merge", name, NULL);
	else if (w == ctx->wattr) {
		char *tmp = pcb_concat("net:", name, NULL);
		pcb_actionva(&ctx->pcb->hidlib, "propedit", tmp, NULL);
		free(tmp);
	}
	else {
		pcb_message(PCB_MSG_ERROR, "Internal error: netlist_button_cb() called from an invalid widget\n");
		return;
	}
	pcb_gui->invalidate_all(pcb_gui);
}

static void netlist_claim_obj_cb(void *hid_ctx, void *caller_data, pcb_hid_attribute_t *attr)
{
	netlist_ctx_t *ctx = caller_data;
	pcb_actionva(&ctx->pcb->hidlib, "ClaimNet", "object", NULL);
}

static void netlist_claim_sel_cb(void *hid_ctx, void *caller_data, pcb_hid_attribute_t *attr)
{
	netlist_ctx_t *ctx = caller_data;
	pcb_actionva(&ctx->pcb->hidlib, "ClaimNet", "selected", NULL);
}

static void netlist_claim_fnd_cb(void *hid_ctx, void *caller_data, pcb_hid_attribute_t *attr)
{
	netlist_ctx_t *ctx = caller_data;
	pcb_actionva(&ctx->pcb->hidlib, "ClaimNet", "found", NULL);
}

static vtp0_t netlist_color_save;

static void netlist_expose(pcb_hid_attribute_t *attrib, pcb_hid_preview_t *prv, pcb_hid_gc_t gc, const pcb_hid_expose_ctx_t *e)
{
	netlist_ctx_t *ctx = prv->user_ctx;
	pcb_xform_t xform;
	size_t n;
	void **p;
	pcb_hid_attribute_t *attr;
	pcb_hid_row_t *r;
	pcb_net_t *net = NULL;

	attr = &ctx->dlg[ctx->wnetlist];
	r = pcb_dad_tree_get_selected(attr);
	if (r != NULL)
		net = pcb_net_get(ctx->pcb, &ctx->pcb->netlist[PCB_NETLIST_EDITED], r->cell[0], 0);

	if (net != NULL) { /* save term object colors */
		pcb_net_term_t *t;
		vtp0_truncate(&netlist_color_save, 0);
		for(t = pcb_termlist_first(&net->conns); t != NULL; t = pcb_termlist_next(t)) {
			pcb_any_obj_t *obj = pcb_term_find_name(ctx->pcb, ctx->pcb->Data, PCB_LYT_COPPER, t->refdes, t->term, NULL, NULL);
			if (obj == NULL)
				continue;

			vtp0_append(&netlist_color_save, obj);
			if (obj->override_color != NULL)
				vtp0_append(&netlist_color_save, (char *)obj->override_color);
			else
				vtp0_append(&netlist_color_save, NULL);
			obj->override_color = pcb_color_magenta;
		}
	}

	/* draw the board */
	memset(&xform, 0, sizeof(xform));
	xform.layer_faded = 1;
	pcbhl_expose_main(pcb_gui, e, &xform);

	if (net != NULL) {/* restore object color */
		for(n = 0, p = netlist_color_save.array; n < netlist_color_save.used; n+=2,p+=2) {
			pcb_any_obj_t *obj = p[0];
			pcb_color_t *s = p[1];
			obj->override_color = s;
		}
		vtp0_truncate(&netlist_color_save, 0);
	}
}

static pcb_bool netlist_mouse(pcb_hid_attribute_t *attrib, pcb_hid_preview_t *prv, pcb_hid_mouse_ev_t kind, pcb_coord_t x, pcb_coord_t y)
{
	return pcb_false;
}

static void pcb_dlg_netlist(pcb_board_t *pcb)
{
	static const char *hdr[] = {"network", "FR", NULL};
	static const char *hdr2[] = {"terminals", NULL};
	pcb_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};

	int wvpan;

	if (netlist_ctx.active)
		return; /* do not open another */

	netlist_ctx.bb_prv.X1 = 0;
	netlist_ctx.bb_prv.Y1 = 0;
	netlist_ctx.bb_prv.X2 = pcb->hidlib.size_x;
	netlist_ctx.bb_prv.Y2 = pcb->hidlib.size_y;
	netlist_ctx.pcb = pcb;

	PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg); /* layout */
		PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);

		PCB_DAD_BEGIN_HPANE(netlist_ctx.dlg);
			PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);

			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg); /* left */
				PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);
				PCB_DAD_TREE(netlist_ctx.dlg, 2, 0, hdr);
					PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL | PCB_HATF_SCROLL);
					netlist_ctx.wnetlist = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_TREE_SET_CB(netlist_ctx.dlg, selected_cb, netlist_row_selected);
					PCB_DAD_TREE_SET_CB(netlist_ctx.dlg, ctx, &netlist_ctx);
			PCB_DAD_END(netlist_ctx.dlg);

			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg); /* right */
				PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);
				PCB_DAD_BEGIN_VPANE(netlist_ctx.dlg);
					PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);
					wvpan = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg); /* right-top */
						PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);
						PCB_DAD_PREVIEW(netlist_ctx.dlg, netlist_expose, netlist_mouse, NULL, &netlist_ctx.bb_prv, 100, 100, &netlist_ctx);
							PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL | PCB_HATF_PRV_BOARD);
							netlist_ctx.wprev = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_END(netlist_ctx.dlg);
					PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg); /* right-bottom */
						PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);
						PCB_DAD_TREE(netlist_ctx.dlg, 1, 0, hdr2);
							PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL | PCB_HATF_SCROLL);
							netlist_ctx.wtermlist = PCB_DAD_CURRENT(netlist_ctx.dlg);
							PCB_DAD_TREE_SET_CB(netlist_ctx.dlg, selected_cb, termlist_row_selected);
							PCB_DAD_TREE_SET_CB(netlist_ctx.dlg, ctx, &netlist_ctx);
					PCB_DAD_END(netlist_ctx.dlg);
				PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_END(netlist_ctx.dlg);
		PCB_DAD_END(netlist_ctx.dlg);

		PCB_DAD_BEGIN_HBOX(netlist_ctx.dlg); /* bottom button row */
			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "select");
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
					netlist_ctx.wsel = PCB_DAD_CURRENT(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "unsel.");
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
					netlist_ctx.wunsel = PCB_DAD_CURRENT(netlist_ctx.dlg);
			PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "find ");
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
					netlist_ctx.wfind = PCB_DAD_CURRENT(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "clear");
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
					netlist_ctx.wunfind = PCB_DAD_CURRENT(netlist_ctx.dlg);
			PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "rat disable");
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
					netlist_ctx.wnorats = PCB_DAD_CURRENT(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "rat enable");
					netlist_ctx.wrats = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
			PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "add rats");
					netlist_ctx.waddrats = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "rip up  ");
					netlist_ctx.wripup = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
			PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg);
				PCB_DAD_BUTTON(netlist_ctx.dlg, "rename");
					netlist_ctx.wrename = PCB_DAD_CURRENT(netlist_ctx.dlg);
					PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);

				PCB_DAD_BEGIN_HBOX(netlist_ctx.dlg);
					PCB_DAD_BUTTON(netlist_ctx.dlg, "merge");
						netlist_ctx.wmerge = PCB_DAD_CURRENT(netlist_ctx.dlg);
						PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
					PCB_DAD_BUTTON(netlist_ctx.dlg, "attr");
						netlist_ctx.wattr = PCB_DAD_CURRENT(netlist_ctx.dlg);
						PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_button_cb);
				PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_END(netlist_ctx.dlg);
		PCB_DAD_END(netlist_ctx.dlg);

		PCB_DAD_BEGIN_HBOX(netlist_ctx.dlg); /* bottom button row */
			PCB_DAD_LABEL(netlist_ctx.dlg, "Claim net:");
			PCB_DAD_BUTTON(netlist_ctx.dlg, "click");
				PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_claim_obj_cb);
			PCB_DAD_BUTTON(netlist_ctx.dlg, "select");
				PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_claim_sel_cb);
			PCB_DAD_BUTTON(netlist_ctx.dlg, "found");
				PCB_DAD_CHANGE_CB(netlist_ctx.dlg, netlist_claim_fnd_cb);
			PCB_DAD_BEGIN_VBOX(netlist_ctx.dlg); /* fill between buttons and close */
				PCB_DAD_COMPFLAG(netlist_ctx.dlg, PCB_HATF_EXPFILL);
			PCB_DAD_END(netlist_ctx.dlg);
			PCB_DAD_BUTTON_CLOSES(netlist_ctx.dlg, clbtn);
		PCB_DAD_END(netlist_ctx.dlg);
	PCB_DAD_END(netlist_ctx.dlg);

	/* set up the context */
	netlist_ctx.active = 1;

	PCB_DAD_DEFSIZE(netlist_ctx.dlg, 300, 350);
	PCB_DAD_NEW("netlist", netlist_ctx.dlg, "pcb-rnd netlist", &netlist_ctx, pcb_false, netlist_close_cb);

	{
		pcb_hid_attr_val_t hv;
		hv.dbl = 0.33;
		pcb_gui->attr_dlg_set_value(netlist_ctx.dlg_hid_ctx, wvpan, &hv);
	}

	netlist_data2dlg(&netlist_ctx);
}

static const char pcb_acts_NetlistDialog[] = "NetlistDialog()\n";
static const char pcb_acth_NetlistDialog[] = "Open the netlist dialog.";
static fgw_error_t pcb_act_NetlistDialog(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	if (strcmp(pcb_gui->name, "lesstif") == 0)
		pcb_actionva(PCB_ACT_HIDLIB, "DoWindows", "netlist");
	else
		pcb_dlg_netlist(PCB);
	PCB_ACT_IRES(0);
	return 0;
}

/* update the dialog after a netlist change */
static void pcb_dlg_netlist_ev(pcb_hidlib_t *hidlib, void *user_data, int argc, pcb_event_arg_t argv[])
{
	netlist_ctx_t *ctx = user_data;
	if (!ctx->active)
		return;
	netlist_data2dlg(ctx);
}
static void pcb_dlg_netlist_init(void)
{
	pcb_event_bind(PCB_EVENT_NETLIST_CHANGED, pcb_dlg_netlist_ev, &netlist_ctx, dlg_netlist_cookie);
}

static void pcb_dlg_netlist_uninit(void)
{
	pcb_event_unbind_allcookie(dlg_netlist_cookie);
	vtp0_uninit(&netlist_color_save);
}
