/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - Edif 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 eedif_net;

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

static int edif_is_legal_character(const char ch)
{
	return '_'==ch ||
		('0'<=ch && ch<='9') ||
		('a'<=ch && ch<='z') || ('A'<=ch && ch<='Z');
}

static int edif_name_requires_rename(const char* name)
{
	char ch = *name;

	if('0'<=ch && ch<='9')
	{
		return 1;
	}

	while(*name)
	{
		if(!edif_is_legal_character(*name++))
		{
			return 1;
		}
	}

	return 0;
}

/* set 'first' to non-zero to handle first character differently */
static void edif_print_renamed(FILE* const f, const char* str, int first)
{
	if(first && '0'<=(*str) && (*str)<='9')
	{
		fputc('&', f);
		fputc(*str++, f);
	}

	while(*str)
	{
		char ch = *str++;

		if(!edif_is_legal_character(ch))
		{
			fputc('_', f);
		}
		else
		{
			fputc(ch, f);
		}
	}
}

static void edif_print_quoted(FILE* const f, const char* str)
{
	fputc('\"', f);

	while(*str)
	{
		char ch = *str++;

		if(strchr("\"\r\n", ch))
		{
			fputc('_', f);
		}
		else
		{
			fputc(ch, f);
		}
	}

	fputc('\"', f);
}

static void edif_print_filename(FILE* const f, const char* str)
{
	edif_print_renamed(f, str, 0);
}

static void edif_print_name_or_renamed(FILE* const f, const char* name)
{
	if(edif_name_requires_rename(name))
	{
		fprintf(f, "(rename ");
		edif_print_renamed(f, name, 1);
		fputc(' ', f);
		edif_print_quoted(f, name);
		fputc(')', f);
	}
	else
	{
		fputs(name, f);
	}
}

static void edif_print_portref(FILE* const f, const char* ref)
{
	edif_print_renamed(f, ref, 1);
}

static void edif_print_ref_of_renamed(FILE* const f, const char* name)
{
	if(edif_name_requires_rename(name))
	{
		edif_print_renamed(f, name, 1);
	}
	else
	{
		fputs(name, f);
	}
}

static void edif_print_compname(FILE* const f, csch_acomp_t* const comp)
{
	const char* refdes = sch_nle_get_refdes(comp);
	const char* val = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_VALUE);
	const char* dev = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_DEVICE);

	edif_print_renamed(f, refdes, 1);

	if(dev)
	{
		fprintf(f, "___d_");
		edif_print_renamed(f, dev, 0);
	}

	if(val)
	{
		fprintf(f, "___v_");
		edif_print_renamed(f, val, 0);
	}
}

/* Write a single attribute to f */
static void export_attr(FILE* f, const char* key, const char* val, const char* indent)
{
	if(key && val)
	{
		fputs(indent, f);
		fprintf(f, "(Property ");
		edif_print_name_or_renamed(f, key);
		fprintf(f, " (String ");
		edif_print_quoted(f, val);
		fprintf(f, "))\r\n");
	}
}


/* 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_custom_attrs(FILE* f, csch_ahdr_t* obj, const char* indent)
{
	htsp_entry_t *e;
	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)) {
			export_attr(f, a->export_name, a->val, indent);
		}
	}
}

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

static struct strlist* edif_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* edif_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 component library with port renames */
static void edif_export_complib(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e, *p;
	struct strlist* pinlist;

	fprintf(f, "  (library COMPONENT_LIB\r\n");
	fprintf(f, "    (edifLevel 0)\r\n");
	fprintf(f, "    (technology\r\n");
	fprintf(f, "      (numberDefinition\r\n");
	fprintf(f, "        (scale 1 1 (unit distance))\r\n");
	fprintf(f, "      )\r\n");
	fprintf(f, "    )\r\n");

	/* NOTE: Maybe this is not the most efficient way to build the component */
	/* lib, because in theory it will have a lot more entries than it should */
	/* be. Unfortunately, it is really hard to group resistors and */
	/* capacitors, and the majority of the dupes come from res/caps. The */
	/* result is still correct, but more verbose than is should/could be. */

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

		if (refdes == NULL)
			continue;

		if (comp->hdr.omit)
			continue;

		fprintf(f, "    (cell ");
		edif_print_compname(f, comp);
		fprintf(f, "\r\n");

		fprintf(f, "      (cellType GENERIC)\r\n");
		fprintf(f, "      (view netListView\r\n");
		fprintf(f, "        (viewType NETLIST)\r\n");
		fprintf(f, "        (interface\r\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 terminal renames */
		for(p = htsp_first(&comp->ports); p != NULL; p = htsp_next(&comp->ports, p)) {
			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);

			if (pinname == NULL)
				continue;

			SCH_NLE_FOREACH_PINNUM(pinnums, my_num,
				{
					if(!edif_strlist_find(pinlist, my_num))
					{
						pinlist = edif_strlist_add(pinlist, my_num);

						fprintf(f, "          (port (rename ");
						edif_print_portref(f, my_num);
						fputc(' ', f);
						edif_print_quoted(f, my_num);
						fprintf(f, ") (direction INOUT))\r\n");
					}
				}
			);
		}

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

			free(n);
		}

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

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

static const char instance_property_indent[] = "            ";

/* Export abstract components; exports refdes, footprint, value, device, */
/* and all other attributes */
static void edif_export_comps(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e, *p;

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

		fprintf(f, "          (Instance ");
		edif_print_name_or_renamed(f, refdes);
		fprintf(f, "\r\n");

		fprintf(f, "            (viewRef NetlistView\r\n");

		fprintf(f, "              (cellRef ");
		edif_print_compname(f, comp);
		fprintf(f, "\r\n");

		fprintf(f, "                (LibraryRef COMPONENT_LIB)\r\n");
		fprintf(f, "              )\r\n");
		fprintf(f, "            )\r\n");

		/* Warning: Essential information (like fp ande dev/val) are just */
		/* regular properties. Not much care taken here to remove possible */
		/* duplicates, because samples shown dupes are not an issue. See: */
		/* svn://svn.repo.hu/sch-rnd/work/alien/netlist/samples/edif/alt.edf */

		if(fp)
		{
			export_attr(f, "Footprint", fp, instance_property_indent);
		}

		if(val)
		{
			export_attr(f, "Value", val, instance_property_indent);
		}

		if(dev)
		{
			export_attr(f, "Description", dev, instance_property_indent);
			export_attr(f, "Device", dev, instance_property_indent);

			/* altium tends to export device as "comment" */
			export_attr(f, "Comment", dev, instance_property_indent);
		}

		export_custom_attrs(f, &comp->hdr, instance_property_indent);

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

			/* 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,
				{
					static const char pin_prop_prefix[] = "Pin///";

					size_t len = strlen(my_num);
					char* cpy = (char*)malloc(sizeof(pin_prop_prefix)+len);

					memcpy(cpy, pin_prop_prefix, sizeof(pin_prop_prefix)-1);
					strcpy(cpy+sizeof(pin_prop_prefix)-1, my_num);

					export_attr(f, cpy, pinname, instance_property_indent);

					free(cpy);
				}
			);
		}

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

/* Export abstract nets; exports net's name and a list of refdes-pinnum */
/* pairs connected */
static void edif_export_nets(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e;
	long 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);
		int net_exported = 0; /* net had at least one connection so it was written */

		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, "edif: invalid connection (object type)\n");
				continue;
			}

			pinnums = sch_nle_get_pinnum(port);
			if (pinnums == NULL) {
				rnd_message(RND_MSG_ERROR, "edif: 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, "edif: can't determine port's parent component refdes\n");*/
				continue;
			}

			/* 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(!net_exported)
					{
						net_exported = 1;

						fprintf(f, "          (Net ");
						edif_print_name_or_renamed(f, netname);
						fprintf(f, "\r\n");
						fprintf(f, "            (Joined\r\n");
					}

					fprintf(f, "                (PortRef ");
					edif_print_portref(f, my_num);
					fprintf(f, " (InstanceRef ");
					edif_print_ref_of_renamed(f, refdes);
					fprintf(f, "))\r\n");
				}
			);

		}

		/* If the net got exported, close the block */
		if(net_exported)
		{
			fprintf(f, "            )\r\n");
			fprintf(f, "          )\r\n");
		}
	}
}


/* Export netlist from the abstract model */
static int edif_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, "(edif file__");
	edif_print_filename(f, fn);
	fprintf(f, "\r\n");

	fprintf(f, "  (edifVersion 2 0 0)\r\n");
	fprintf(f, "  (edifLevel 0)\r\n");
	fprintf(f, "  (keywordMap\r\n");
	fprintf(f, "    (keywordLevel 0)\r\n");
	fprintf(f, "  )\r\n");
	fprintf(f, "  (status\r\n");
	fprintf(f, "    (written\r\n");
	fprintf(f, "      (timeStamp 2025 9 17 8 33 0)\r\n");
	fprintf(f, "      (program \"sch-rnd\"\r\n");
	fprintf(f, "        (version \"\")\r\n");
	fprintf(f, "      )\r\n");
	fprintf(f, "      (author \"sch-rnd\")\r\n");
	fprintf(f, "    )\r\n");
	fprintf(f, "  )\r\n");
	fprintf(f, "\r\n");

	edif_export_complib(f, abs);

	fprintf(f, "  (library SHEET_LIB\r\n");
	fprintf(f, "    (edifLevel 0)\r\n");
	fprintf(f, "    (technology\r\n");
	fprintf(f, "      (numberDefinition\r\n");
	fprintf(f, "        (scale 1 1 (unit distance))\r\n");
	fprintf(f, "      )\r\n");
	fprintf(f, "    )\r\n");
	fprintf(f, "    (cell my_schematic\r\n");
	fprintf(f, "      (cellType generic)\r\n");
	fprintf(f, "      (view netListView\r\n");
	fprintf(f, "        (viewType netlist)\r\n");
	fprintf(f, "        (interface\r\n");
	fprintf(f, "        )\r\n");
	fprintf(f, "        (contents\r\n");

	edif_export_comps(f, abs);
	edif_export_nets(f, abs);

	fprintf(f, "        )\r\n");
	fprintf(f, "      )\r\n");
	fprintf(f, "    )\r\n");
	fprintf(f, "  )\r\n");
	fprintf(f, "  (design my_design\r\n");
	fprintf(f, "    (cellRef my_schematic\r\n");
	fprintf(f, "      (libraryRef SHEET_LIB)\r\n");
	fprintf(f, "    )\r\n");
	fprintf(f, "  )\r\n");
	fprintf(f, ")\r\n");

	fclose(f);
	return 0;
}

#include "hid_impl.c"

int pplg_check_ver_export_edif(int ver_needed) { return 0; }

void pplg_uninit_export_edif(void)
{
	csch_plug_io_unregister(&eedif_net);
	rnd_export_remove_opts_by_cookie(edif_cookie);
	rnd_hid_remove_hid(&edif_hid);
}

int pplg_init_export_edif(void)
{
	RND_API_CHK_VER;

	eedif_net.name = "export to Edif";
	eedif_net.export_prio = edif_export_prio;
	eedif_net.export_project_abst = edif_export_project_abst;
	eedif_net.ext_export_project = ".net";
	csch_plug_io_register(&eedif_net);


	rnd_hid_nogui_init(&edif_hid);

	edif_hid.struct_size = sizeof(rnd_hid_t);
	edif_hid.name = "edif";
	edif_hid.description = "Exports project's Edif netlist";
	edif_hid.exporter = 1;

	edif_hid.get_export_options = edif_get_export_options;
	edif_hid.do_export = edif_do_export;
	edif_hid.parse_arguments = edif_parse_arguments;
	edif_hid.argument_array = edif_values;

	edif_hid.usage = edif_usage;

	rnd_hid_register_hid(&edif_hid);
	rnd_hid_load_defaults(&edif_hid, edif_options, NUM_OPTIONS);


	return 0;
}

