/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - Jsonnet netlist 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., 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
 */


#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/plugins.h>
#include <librnd/core/error.h>
#include <libcschem/config.h>
#include <libcschem/plug_io.h>

#include <plugins/lib_netlist_exp/lib_netlist_exp.h>

static csch_plug_io_t ejsonnet_net;

static int jsonnet_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, "jsonnet") == 0)
		return 100;
	return 0;
}

/* print text, valid for xml */
static void export_qstr(FILE *f, const char *txt)
{
	const char *s;

	fputc('"', f);
	if (txt != NULL) {
		for(s = txt; *s != '\0'; s++) {
			if (*s == '"')
				fprintf(f, "\"");
			else if (*s == '\\')
				fprintf(f, "\\\\");
			else
				fputc(*s, f);
		}
	}
	fputc('"', f);
}


static void export_key_val(FILE *f, const char *ind, const char *key, const char *val, const char *suffix)
{
	fprintf(f, "%s\"%s\": ", ind, key);
	export_qstr(f, val);
	fprintf(f, "%s", suffix);
}

/* Export comptag and nettag for attributes that have export_name; the user
   wanted these to end up on the netlist as custom tags/attributes/properties */
static void export_suffix_attrs(FILE *f, const char *ind, csch_ahdr_t *obj)
{
	htsp_entry_t *e;
	int found = 0, need_comma = 0;

	/* figure if there's at least one attrib to export */
	for(e = htsp_first(&obj->attr); e != NULL; e = htsp_next(&obj->attr, e)) {
		csch_attrib_t *a = e->value;
		if ((a->export_name != NULL) && (a->val != NULL)) {
			found = 1;
			break;
		}
	}
	if (!found) {
		fprintf(f, "\n");
		return;
	}

	fprintf(f, ",\n%s\"Attribute\": [", ind);
	for(e = htsp_first(&obj->attr); e != NULL; e = htsp_next(&obj->attr, e)) {
		csch_attrib_t *a = e->value;

		if ((a->export_name != NULL) && (a->val != NULL)) {
			if (need_comma)
				fprintf(f, ",\n");
			else
				fprintf(f, "\n");

			fprintf(f, "%s { \"Key\": ", ind);
			export_qstr(f, a->export_name);
			fprintf(f, ", \"Value\": ");
			export_qstr(f, a->val);
			fprintf(f, " }");
			need_comma = 1;
		}
	}
	fprintf(f, "\n%s]\n", ind);
}

/* Export abstract components: important fields and attributes */
static void jsonnet_export_comps(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e, *enext, *p, *pnext;

	fprintf(f, "  \"Components\": [\n");

	for(e = htsp_first(&abs->comps); e != NULL; e = enext) {
		csch_acomp_t *comp = e->value;
		const char *refdes = sch_nle_get_refdes(comp);
		const char *fp, *dev, *val;
		int need_comma = 0;

		enext = htsp_next(&abs->comps, e);

		if (refdes == NULL)
			continue;

		if (comp->hdr.omit)
			continue;

		fprintf(f, "   {\n");

		/* Get main symbol attributes the safe way, considering alternate names;
		   these should be always exported and are usually hardwiared in the
		   netlist format. */
		fp = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_FOOTPRINT);
		val = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_VALUE);
		dev = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_DEVICE);

		export_key_val(f, "    ", "Refdes", refdes, ",\n");
		export_key_val(f, "    ", "Footprint", fp, ",\n");
		export_key_val(f, "    ", "Value", val, ",\n");
		export_key_val(f, "    ", "Device", dev, ",\n");

		fprintf(f, "    \"Pins\": [");

		/* Print terminals so that terminal symbolic names can be preserved.
		   (The netlist uses pin number to reference the terminal, symbolic name
		   is just an useful hint) */
		for(p = htsp_first(&comp->ports); p != NULL; p = pnext) {
			const csch_aport_t *port = p->value;
			const char *pinnums = sch_nle_get_pinnum(port);
			const char *pinname = sch_nle_get_alt_attr(&port->hdr.attr, SCH_NLE_ALTATTR_PINNAME);

			pnext = htsp_next(&comp->ports, p);

			if (pinname == NULL)
				continue;

			/* call export_attr on each pin in pinnums (which can be a list of
			   pin numbers) so that each pinnum -> pinname is written */
			SCH_NLE_FOREACH_PINNUM(pinnums, my_num,
				if (need_comma)
					fprintf(f, ",\n");
				else
					fprintf(f, "\n");

				fprintf(f, "     { \"Number\": ");
				export_qstr(f, my_num);
				fprintf(f, ", \"PinName\": ");
				export_qstr(f, pinname);
				fprintf(f, " }");
				need_comma = 1;
			);

/*			if (pnext == NULL)
				fprintf(f, "\n     }\n");
			else
				fprintf(f, "\n     },\n");*/
		}
		fprintf(f, "\n    ]");
		export_suffix_attrs(f, "    ", &comp->hdr);

		if (enext == NULL)
			fprintf(f, "   }\n");
		else
			fprintf(f, "   },\n");
	}

	fprintf(f, "  ],\n");
}

/* Export abstract nets; should exports net's name and a list of
   refdes-pinnum pairs connected and attributes */
static void jsonnet_export_nets(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e;
	int first_net = 1;
	long n;

	fprintf(f, "  \"Nets\": [");

	for(e = htsp_first(&abs->nets); e != NULL; e = htsp_next(&abs->nets, e)) {
		csch_anet_t *net = e->value;
		const char *netname = sch_nle_get_netname(net);
		int net_exported = 0; /* net had at least one connection so it was written */
		int need_comma = 0;

		if (net->hdr.omit) continue;


		for(n = 0; n < net->conns.used; n++) {
			csch_aport_t *port = net->conns.array[n];
			const char *refdes = NULL, *pinnums;

			if (port->hdr.type != CSCH_ATYPE_PORT) {
				rnd_message(RND_MSG_ERROR, "jsonnet: invalid connection (object type)\n");
				continue;
			}

			pinnums = sch_nle_get_pinnum(port);
			if (pinnums == NULL) {
				rnd_message(RND_MSG_ERROR, "jsonnet: can't determine port's pin number\n");
				continue;
			}

			if (port->parent != NULL) {
				refdes = sch_nle_get_refdes(port->parent);
				if (port->parent->hdr.omit)
					continue; /* omit component */
			}
			if (refdes == NULL) {
				/* This is not an error: no refdes means: do not export (e.g. gnd) */
/*				rnd_message(RND_MSG_ERROR, "jsonnet: can't determine port's parent component refdes\n");*/
				continue;
			}

			if (!net_exported) {
				SCH_NLE_FOREACH_PINNUM(pinnums, my_num,
					{
						(void)my_num;
						net_exported = 1;
						break;
					}
				);
				if (!net_exported)
					continue;
				if (first_net)
					fprintf(f, "\n   {\n");
				else
					fprintf(f, ",\n   {\n");

				export_key_val(f, "    ", "Name", netname, ",\n");
				fprintf(f, "    \"Connections\": [");
				first_net = 0;
			}

			/* split up pinnum at space and create one or more conn lines connecting
			   each pin to the given net */
			SCH_NLE_FOREACH_PINNUM(pinnums, my_num,
				{
					if (need_comma)
						fprintf(f, ",\n");
					else
						fprintf(f, "\n");

					fprintf(f, "     { \"Refdes\": ");
					export_qstr(f, refdes);
					fprintf(f, ", \"PinNum\": ");
					export_qstr(f, my_num);
					fprintf(f, " }");
					need_comma = 1;
				}
			);

		}

		if (net_exported) {
			fprintf(f, "\n    ]");
			export_suffix_attrs(f, "    ", &net->hdr);
			fprintf(f, "   }");
		}
	}

	fprintf(f, "\n  ]\n");
}


/* Export netlist from the abstract model */
static int jsonnet_export_project_abst(const char *fn, const char *fmt, csch_abstract_t *abs, rnd_hid_attr_val_t *options)
{
	TODO("get hidlib as an arg")
	rnd_design_t *hidlib = NULL;
	FILE *f = rnd_fopen(hidlib, fn, "w");
	if (f == NULL)
		return -1;


	fprintf(f, "{\n");
	fprintf(f, " \"Netlist\": {\n");
	fprintf(f, "  \"BySoftware\": \"sch-rnd\",\n");
	fprintf(f, "  \"ByModule\": \"export_jsonnet\",\n");


	jsonnet_export_comps(f, abs);
	jsonnet_export_nets(f, abs);

	fprintf(f, " }\n");
	fprintf(f, "}\n");

	fclose(f);
	return 0;
}

#include "hid_impl.c"

int pplg_check_ver_export_jsonnet(int ver_needed) { return 0; }

void pplg_uninit_export_jsonnet(void)
{
	csch_plug_io_unregister(&ejsonnet_net);
	rnd_export_remove_opts_by_cookie(jsonnet_cookie);
	rnd_hid_remove_hid(&jsonnet_hid);
}

int pplg_init_export_jsonnet(void)
{
	RND_API_CHK_VER;

	ejsonnet_net.name = "export netlist to generic json";
	ejsonnet_net.export_prio = jsonnet_export_prio;
	ejsonnet_net.export_project_abst = jsonnet_export_project_abst;
	ejsonnet_net.ext_export_project = ".net";
	csch_plug_io_register(&ejsonnet_net);


	rnd_hid_nogui_init(&jsonnet_hid);

	jsonnet_hid.struct_size = sizeof(rnd_hid_t);
	jsonnet_hid.name = "jsonnet";
	jsonnet_hid.description = "Exports project's generic json netlist";
	jsonnet_hid.exporter = 1;

	jsonnet_hid.get_export_options = jsonnet_get_export_options;
	jsonnet_hid.do_export = jsonnet_do_export;
	jsonnet_hid.parse_arguments = jsonnet_parse_arguments;
	jsonnet_hid.argument_array = jsonnet_values;

	jsonnet_hid.usage = jsonnet_usage;

	rnd_hid_register_hid(&jsonnet_hid);
	rnd_hid_load_defaults(&jsonnet_hid, jsonnet_options, NUM_OPTIONS);


	return 0;
}

