/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - Ewnet netlist export
 *  Copyright (C) 2025 Tibor 'Igor2' Palinkas
 *  Copyright (C) 2025 Aron Barath
 *
 *  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 eewnet_net;

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

/* print a quoted name with quote symbols */
static void ewnet_print_quoted(FILE* const f, const char* const str)
{
	fputc('\"', f);

	if(str)
	{
		if(strchr(str, '\"'))
		{
			rnd_message(RND_MSG_ERROR, "ewnet: Broken output! String contains double-qute (\"), but it cannot be escaped: %s\n", str);
		}

		fputs(str, f);
	}

	fputc('\"', f);
}

struct strlist
{
	struct strlist* next;
	char str[1];
};

static struct strlist* ewnet_strlist_find(struct strlist* list, const char* const str)
{
	for(;list;list=list->next)
	{
		if(strcmp(list->str, str)==0)
		{
			return list;
		}
	}

	return NULL;
}

static struct strlist* ewnet_strlist_add(struct strlist* list, const char* const str)
{
	struct strlist* n = (struct strlist*)malloc(sizeof(struct strlist)+strlen(str));

	strcpy(n->str, str);
	n->next = list;

	return n;
}

/* Export abstract components; exports  refdes, footprint, value, device */
/* and all pins with pin names and connected net */
static void ewnet_export_comps(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e, *p;
	struct strlist* pinlist;

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

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

		if (refdes == NULL)
			continue;

		if (comp->hdr.omit)
			continue;

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

		if(fp==NULL)
		{
			/* just warn the user */
			rnd_message(RND_MSG_ERROR, "ewnet: no footprint is set for \"%s\"\n", refdes);

			/* if footprint is unknown, set to empty */
			fp = "";
		}

		fprintf(f, "  (instance ");
		ewnet_print_quoted(f, fp);
		fputc(' ', f);
		ewnet_print_quoted(f, refdes);
		fputc('\n', f);

		fprintf(f, "    (device ");
		ewnet_print_quoted(f, dev);
		fputs(")\n", f);

		fprintf(f, "    (value ");
		ewnet_print_quoted(f, val);
		fputs(")\n", f);

		/* some bloat... */
		fprintf(f, "    (gateswap \"0\")\n");
		fprintf(f, "    (pinswap \"0\")\n");
		fprintf(f, "    (component_space \"0.00000000e+000\")\n");
		fprintf(f, "    (component_group \"\")\n");
		fprintf(f, "    (settings_locked \"0\")\n");
		fprintf(f, "    (comp_variants \"Default1;\")\n");
		fprintf(f, "    (comp_variant_independent \"0\")\n");

		/* this linked list will be used to keep track which pins were */
		/* exported -- typically there is not too much pins, so no worries */
		pinlist = NULL;

		/* 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 = htsp_next(&comp->ports, p)) {
			const csch_aport_t *port = p->value;
			csch_anet_t* net = port->conn.net;
			const char* netname;
			const char *pinnums = sch_nle_get_pinnum(port);
			const char *pinname = sch_nle_get_alt_attr(&port->hdr.attr, SCH_NLE_ALTATTR_PINNAME);

			if (pinname == NULL)
				continue;

			if(net && !net->hdr.omit)
			{
				netname = sch_nle_get_netname(net);
			}
			else
			{
				netname = NULL;
			}

			/* export pins */
			SCH_NLE_FOREACH_PINNUM(pinnums, my_num,
				{
					if(!ewnet_strlist_find(pinlist, my_num))
					{
						pinlist = ewnet_strlist_add(pinlist, my_num);

						fprintf(f, "    (pin ");
						ewnet_print_quoted(f, my_num);
						fputc('\n', f);

						/* TODO: I do not know how this should be handled. */
						/* For now, we omit the (net). */
						if(netname)
						{
							fprintf(f, "      (net ");
							ewnet_print_quoted(f, netname);
							fputs(")\n", f);
						}

						fprintf(f, "      (pintype \"BIDIR\")\n");
						fprintf(f, "      (gategroup \"\")\n");
						fprintf(f, "      (pingroup \"\")\n");

						fprintf(f, "      (label ");
						ewnet_print_quoted(f, pinname);
						fputs(")\n", f);

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

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

		while(pinlist)
		{
			struct strlist* n = pinlist;
			pinlist = pinlist->next;

			free(n);
		}

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

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

/* Export abstract nets; exports net's name and a lot of hardwired junk */
/* the format wants */
static void ewnet_export_nets(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e;

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

	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);

		if (net->hdr.omit) continue;

		fprintf(f, "  (net ");
		ewnet_print_quoted(f, netname);
		fprintf(f, "\n");

		fprintf(f, "    (trackwidth \"-1.00000000e+000\")\n");
		fprintf(f, "    (trackwidth_max \"-1.00000000e+000\")\n");
		fprintf(f, "    (trackwidth_min \"-1.00000000e+000\")\n");
		fprintf(f, "    (tracklength_max \"-1.00000000e+000\")\n");
		fprintf(f, "    (tracklength_min \"-1.00000000e+000\")\n");
		fprintf(f, "    (clearance_to_trace \"-1.00000000e+000\")\n");
		fprintf(f, "    (clearance_to_pad \"-1.00000000e+000\")\n");
		fprintf(f, "    (clearance_to_via \"-1.00000000e+000\")\n");
		fprintf(f, "    (clearance_to_copper \"-1.00000000e+000\")\n");
		fprintf(f, "    (routing_layer \"\")\n");
		fprintf(f, "    (settings_locked \"0\")\n");
		fprintf(f, "    (net_group \"\")\n");

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

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


/* Export netlist from the abstract model */
static int ewnet_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, "(ToolInfo\n");
	fprintf(f, "  (netlist \"ULTIboard\" 7 0 0)\n");
	fprintf(f, "  (tool \"sch-rnd\" 0 0 0)\n");
	fprintf(f, "  (timestamp \"16:6:6\" \"18-9-2025\")\n");
	fprintf(f, "  (version 3 0 0)\n");
	fprintf(f, "  (gateswap 2)\n");
	fprintf(f, "  (pinswap 1)\n");
	fprintf(f, "  (crossprobe {D6EE9C01-C93E-4246-9BC1-214A35C4954C})\n");
	fprintf(f, "  (units Mil)\n");
	fprintf(f, ")\n");

	ewnet_export_nets(f, abs);
	ewnet_export_comps(f, abs);

	fprintf(f, "(layers\n");
	fprintf(f, "  (layer \"Copper Bottom\"\n");
	fprintf(f, "    (routable \"1\")\n");
	fprintf(f, "    (type \"Signal\")\n");
	fprintf(f, "  )\n");
	fprintf(f, "  (layer \"Copper Top\"\n");
	fprintf(f, "    (routable \"1\")\n");
	fprintf(f, "    (type \"Signal\")\n");
	fprintf(f, "  )\n");
	fprintf(f, ")\n");

	fclose(f);
	return 0;
}

#include "hid_impl.c"

int pplg_check_ver_export_ewnet(int ver_needed) { return 0; }

void pplg_uninit_export_ewnet(void)
{
	csch_plug_io_unregister(&eewnet_net);
	rnd_export_remove_opts_by_cookie(ewnet_cookie);
	rnd_hid_remove_hid(&ewnet_hid);
}

int pplg_init_export_ewnet(void)
{
	RND_API_CHK_VER;

	eewnet_net.name = "export to Ewnet";
	eewnet_net.export_prio = ewnet_export_prio;
	eewnet_net.export_project_abst = ewnet_export_project_abst;
	eewnet_net.ext_export_project = ".net";
	csch_plug_io_register(&eewnet_net);


	rnd_hid_nogui_init(&ewnet_hid);

	ewnet_hid.struct_size = sizeof(rnd_hid_t);
	ewnet_hid.name = "ewnet";
	ewnet_hid.description = "Exports project's Ewnet netlist";
	ewnet_hid.exporter = 1;

	ewnet_hid.get_export_options = ewnet_get_export_options;
	ewnet_hid.do_export = ewnet_do_export;
	ewnet_hid.parse_arguments = ewnet_parse_arguments;
	ewnet_hid.argument_array = ewnet_values;

	ewnet_hid.usage = ewnet_usage;

	rnd_hid_register_hid(&ewnet_hid);
	rnd_hid_load_defaults(&ewnet_hid, ewnet_options, NUM_OPTIONS);


	return 0;
}

