/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - boxsym export
 *  Copyright (C) 2025 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/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
 */


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_fs.h>
#include <librnd/core/paths.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/plugins.h>
#include <librnd/core/error.h>
#include <librnd/hid/hid.h>
#include <librnd/hid/hid_dad.h>
#include <librnd/hid/tool.h>
#include <libcschem/config.h>
#include <libcschem/plug_io.h>
#include <libcschem/util_export.h>
#include <sch-rnd/conf_core.h>
#include <sch-rnd/buffer.h>

#if 0
static csch_plug_io_t eboxsym;

static int boxsym_export_prio(const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	if (type != CSCH_IOTYP_NETLIST)
		return 0;
	if (rnd_strcasecmp(fmt, "boxsym") == 0)
		return 100;
	return 0;
}
#endif

typedef struct bsterm_s {
	const char *name;
	const char *grp;
	const char *num;
	int prio;
	csch_cgrp_t *obj;
} bsterm_t;

static int btsort(const void *b1, const void *b2)
{
	const bsterm_t *bt1 = *((const bsterm_t **)b1);
	const bsterm_t *bt2 = *((const bsterm_t **)b2);
	int c;
	const char *grp1 = bt1->grp, *grp2 = bt2->grp;

	if (grp1 == NULL) grp1 = "";
	if (grp2 == NULL) grp2 = "";

	c = strcmp(grp1, grp2);
	if (c == 0) {
		if (bt1->prio == bt2->prio) {
			c = strcmp(bt1->name, bt2->name);
			if (c == 0)
				c = 1;
		}
		else
			c = (bt1->prio < bt2->prio) ? -1 : +1;
	}

	return c;
}


static int sch_rnd_export_boxsym_(csch_sheet_t *sheet, FILE *f, int viewid, rnd_hid_attr_val_t *options)
{
	htip_entry_t *e;
	const char *side_names[4] = {"left", "right", "top", "bottom"};
	vtp0_t side_objs[4] = {0};
	double xo, yo, xs, ys;
	int n, loci, prio;
	bsterm_t *bt;

	xo = sheet->direct.hdr.bbox.x1;
	yo = sheet->direct.hdr.bbox.y1;
	xs = sheet->direct.hdr.bbox.x2 - sheet->direct.hdr.bbox.x1;
	ys = sheet->direct.hdr.bbox.y2 - sheet->direct.hdr.bbox.y1;


	/* collect all terminals into side_objs wrapped in bsterm_t allocations */
	for(e = htip_first(&sheet->direct.id2obj); e != NULL; e = htip_next(&sheet->direct.id2obj, e)) {
		csch_cgrp_t *term = e->value;
		const char *name, *bsloc, *tmp, *bsgrp, *bsnum;

		if (term->role != CSCH_ROLE_TERMINAL) continue;

		name = csch_attrib_get_str(&term->attr, "name");
		if (name == NULL) {
			rnd_message(RND_MSG_ERROR, "Terminal without name at %rc %rc (removed from output)\n", term->x, term->y);
			continue;
		}

		/* figure where to put it */
		prio = 500;
		loci = -1;
		bsgrp = csch_attrib_get_str(&term->attr, "boxsym/grp");
		bsloc = csch_attrib_get_str(&term->attr, "boxsym/loc");
		bsnum = csch_attrib_get_str(&term->attr, "boxsym/num");
		if (bsloc != NULL) {
			for(n = 0; n < 4; n++) {
				if (strcmp(side_names[n], bsloc) == 0) {
					loci = n;
					break;
				}
			}
			if (loci < 0) {
				rnd_message(RND_MSG_ERROR, "Wrong loc attribute of terminal %s: '%s' (should be: left, right, top or bottom); removed from output\n", name, bsloc);
				continue;
			}
		}
		else {
			double xl1, yl1, yl2;
			xl1 = (term->hdr.bbox.x1 - xo) / xs;
			yl1 = (term->hdr.bbox.y1 - yo) / ys;
			yl2 = (term->hdr.bbox.y2 - yo) / ys;

			if (yl1 < 0.1) {
				loci = 3;
				prio = xl1*1000.0;
			}
			else if (yl2 > 0.9) {
				loci = 2;
				prio = xl1*1000.0;
			}
			else if (xl1 < 0.5) {
				loci = 0;
				prio = 1000-yl1*1000.0;
			}
			else {
				loci = 1;
				prio = 1000-yl1*1000.0;
			}
		}

		tmp = csch_attrib_get_str(&term->attr, "boxsym/prio");
		if (tmp != NULL)
			prio = atoi(tmp);

		rnd_trace("term %s: %s prio=%d\n", name, side_names[loci], prio);
		bt = malloc(sizeof(bsterm_t));
		bt->name = name;
		bt->num = bsnum;
		bt->grp = bsgrp;
		bt->obj = term;
		bt->prio = prio;

		vtp0_append(&side_objs[loci], bt);
	}

	/* write output */
	fprintf(f, "refdes U??\n");
	fprintf(f, "attr cschem/child/name %s\n", sheet->hidlib.loadname);
	fprintf(f, "shape box\n");
	fprintf(f, "pinalign top center\npinalign bottom center\npinalign left center\npinalign right center\n");

	for(loci = 0; loci < 4; loci++) {
		qsort(side_objs[loci].array, side_objs[loci].used, sizeof(void *), btsort);

		fprintf(f, "\n");
		for(n = 0; n < side_objs[loci].used; n++) {
			bt = side_objs[loci].array[n];
			fprintf(f, "\n");
			fprintf(f, "begin pin %s\n", bt->name);
			fprintf(f, "  loc %s\n", side_names[loci]);
			if (bt->num != NULL)
				fprintf(f, "  num %s\n", bt->num);
			else
				fprintf(f, "  num %d\n", (100+loci*100) + n);
			fprintf(f, "end pin\n");
		}
		fprintf(f, "\n");
	}

	return 0;
}

static int sch_rnd_export_boxsym(csch_sheet_t *sheet, const char *fn, int viewid, rnd_hid_attr_val_t *options)
{
	int ovr = 0, rv;
	FILE *f;

	f = rnd_fopen_askovr(&sheet->hidlib, fn, "w", &ovr);
	if (f == NULL)
		return -1;

	rv = sch_rnd_export_boxsym_(sheet, f, viewid, options);
	fclose(f);
	return rv;
}

static void gensym_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	rnd_design_t *dsg = rnd_gui->get_dad_design(hid_ctx);
	rnd_hid_export_opt_func_dad_t *dad = caller_data;
	int wcmd = attr->max_val, res, n;
	const char *templ = dad->dlg[wcmd].val.str;
	char *cmd, *tmpfn, *tmpfnn, *symfn, *end;
	rnd_build_argfn_t args = {0};
	const rnd_export_opt_t *opt = attr->user_data;
	const char *ftype = opt->default_val.str;
	rnd_hid_attribute_t *ebtn = NULL, *aoutfile = NULL;

	/* find the "outfile" field, before wcmd */
	for(n = wcmd; n >= 0; n--) {
		rnd_hid_attribute_t *a = dad->dlg+n;
		if ((a->type == RND_HATT_STRING) && (a->name != NULL) && (strcmp(a->name, "outfile") == 0)) {
			aoutfile = a;
			break;
		}
	}

	/* find the "Export!" button, after wcmd */
	for(n = wcmd; n < dad->dlg_len; n++) {
		rnd_hid_attribute_t *a = dad->dlg+n;
		if ((a->type == RND_HATT_BUTTON) && (a->val.str != NULL) && (strcmp(a->val.str, "Export!") == 0)) {
			ebtn = a;
			break;
		}
	}

	if (ebtn == NULL) {
		rnd_message(RND_MSG_ERROR, "Internal error: can't find the 'Export!' button\n");
		return;
	}

	if (aoutfile == NULL) {
		rnd_message(RND_MSG_ERROR, "Internal error: can't find the 'outfule' entry\n");
		return;
	}

	/* render temp file name - extension is ftype */
	tmpfnn = rnd_concat("boxsym.", ftype, NULL);
	tmpfn = rnd_tempfile_name_new(tmpfnn);
	free(tmpfnn);

	/* render command line from template */
	args.design = dsg;
	args.params['f' - 'a'] = tmpfn;
	cmd = rnd_build_argfn(templ, &args);

	rnd_trace("gensym! %d '%s' '%s' dsg=%p ftype='%s' tmpfn='%s'\n", wcmd, templ, cmd, dsg, ftype, tmpfn);

	/* export: replace outfile temporarily and "click" the export button */
	tmpfnn = (char *)aoutfile->val.str;
	aoutfile->val.str = tmpfn;
	ebtn->change_cb(hid_ctx, caller_data, ebtn);
	aoutfile->val.str = tmpfnn;

	/* figure symbol file name (assuming default) */
	symfn = rnd_strdup(tmpfn);
	end = symfn + strlen(symfn) - 7;
	strcpy(end, ".ry"); /* safe: .ry is shorter than .gensym */

	/* run external command and import result */
	res = rnd_system(dsg, cmd);
	if (res == 0) {
		sch_rnd_buffer_clear(SCH_RND_PASTEBUFFER);
		if (csch_load_grp(SCH_RND_PASTEBUFFER, symfn, "lihata") != NULL)
			rnd_tool_select_by_name(dsg, "buffer");
		else
			rnd_message(RND_MSG_ERROR, "Failed to load resulting symbol file\n");
	}
	else
		rnd_message(RND_MSG_ERROR, "boxsym-rnd returned error, not loading the symbol\n");

	/* clean up */
	free(cmd);
	rnd_unlink(dsg, symfn);
	free(symfn);
	rnd_tempfile_unlink(tmpfn);
}

static void gensym_dad(rnd_hid_export_opt_func_action_t act, void *call_ctx, const rnd_export_opt_t *opt, rnd_hid_attr_val_t *val)
{
	rnd_hid_export_opt_func_dad_t *dad = call_ctx;
	int wcmd;

	switch(act) {
		case RND_HIDEOF_USAGE:
			/* not available - GUI-only feature */
			break;
		case RND_HIDEOF_DAD:

			RND_DAD_BEGIN_VBOX(dad->dlg);
				RND_DAD_LABEL(dad->dlg, "");
				RND_DAD_BEGIN_VBOX(dad->dlg);
					RND_DAD_COMPFLAG(dad->dlg, RND_HATF_EXPFILL | RND_HATF_FRAME);
					RND_DAD_LABEL(dad->dlg, "Execute boxsym-rnd:");
					RND_DAD_BEGIN_HBOX(dad->dlg);
						RND_DAD_STRING(dad->dlg);
							RND_DAD_DEFAULT_PTR(dad->dlg, rnd_strdup("boxsym-rnd %f"));
							wcmd = RND_DAD_CURRENT(dad->dlg);
						RND_DAD_BUTTON(dad->dlg, "Generate symbol!");
							RND_DAD_CHANGE_CB(dad->dlg, gensym_cb);
							dad->dlg[dad->dlg_len - 1].user_data = (void *)opt;
							dad->dlg[dad->dlg_len - 1].max_val = wcmd;
					RND_DAD_END(dad->dlg);
				RND_DAD_END(dad->dlg);
				RND_DAD_LABEL(dad->dlg, "");
			RND_DAD_END(dad->dlg);
			break;
	}
}


#if 0
static int boxsym_export_sheet(const char *fn, const char *fmt, csch_sheet_t *dst)
{
	return sch_rnd_export_boxsym(dst, fn, viewid, options)
}
#endif

#include "hid_impl.c"

int pplg_check_ver_export_boxsym(int ver_needed) { return 0; }

void pplg_uninit_export_boxsym(void)
{
#if 0
	csch_plug_io_unregister(&eboxsym);
#endif
	rnd_export_remove_opts_by_cookie(boxsym_cookie);
	rnd_hid_remove_hid(&boxsym_hid);
}

int pplg_init_export_boxsym(void)
{
	RND_API_CHK_VER;

#if 0
	eboxsym.name = "export boxsym for a hierarchic child sheet extracting its sheet terminals";
	eboxsym.export_prio = boxsym_export_prio;
	eboxsym.export_sheet = boxsym_export_sheet;
	eboxsym.ext_export_project = ".boxsym";
	csch_plug_io_register(&eboxsym);
#endif

	rnd_hid_nogui_init(&boxsym_hid);

	boxsym_hid.struct_size = sizeof(rnd_hid_t);
	boxsym_hid.name = "boxsym";
	boxsym_hid.description = "export boxsym for a hierarchic child sheet extracting its sheet terminals";
	boxsym_hid.exporter = 1;

	boxsym_hid.get_export_options = boxsym_get_export_options;
	boxsym_hid.do_export = boxsym_do_export;
	boxsym_hid.parse_arguments = boxsym_parse_arguments;
	boxsym_hid.argument_array = boxsym_values;

	boxsym_hid.usage = boxsym_usage;

	rnd_hid_register_hid(&boxsym_hid);
	rnd_hid_load_defaults(&boxsym_hid, boxsym_options, NUM_OPTIONS);

	return 0;
}

