/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - Calay 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 <librnd/hid/hid_attrib.h>
#include <libcschem/config.h>
#include <libcschem/plug_io.h>

#include <plugins/lib_netlist_exp/lib_netlist_exp.h>

static csch_plug_io_t ecalay_net, ecalay_net_old;
static const char* calay_get_outcmp_option(rnd_hid_attr_val_t* options);

static int new_calay_export_prio(const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	if (type != CSCH_IOTYP_NETLIST)
		return 0;
	if ((strstr(fmt, "calay") != NULL) && (strstr(fmt, "old") == NULL))
		return 100;
	return 0;
}

static int old_calay_export_prio(const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	if (type != CSCH_IOTYP_NETLIST)
		return 0;
	if ((strstr(fmt, "calay") != NULL) && (strstr(fmt, "old") != NULL))
		return 100;
	return 0;
}

static void calay_print_value(FILE* const f, const char* str)
{
	size_t len = strlen(str);

	if(len>19)
	{
		rnd_message(RND_MSG_ERROR, "calay: Broken output! Value is longer than 19 characters: \"%s\"\n", str);
	}

	if(strchr(str, ' '))
	{
		rnd_message(RND_MSG_ERROR, "calay: Broken output! Value contains space: \"%s\"\n", str);
	}

	fprintf(f, "%-19s", str);
}

static void calay_print_nodename(FILE* const f, const char* str)
{
	size_t len = strlen(str);
	const char* p;

	/* shorten anonymous nets */
	if(len>9 && memcmp(str, "anon_net_", 9)==0)
	{
		str += 9;
		len -= 9;

		if(len>6)
		{
			rnd_message(RND_MSG_ERROR, "calay: Broken output! Node name is longer than 8 characters: \"N$%s\"\n", str);
		}

		fprintf(f, "N$%-6s", str);
		return;
	}

	if(len>8)
	{
		rnd_message(RND_MSG_ERROR, "calay: Broken output! Node name is longer than 8 characters: \"%s\"\n", str);
	}

	if(strchr(str, ' '))
	{
		rnd_message(RND_MSG_ERROR, "calay: Broken output! Node name contains space: \"%s\"\n", str);
	}

	p = strchr(str, '_');

	if(p)
	{
		/* we must replace all '_' with '-' */
		rnd_message(RND_MSG_WARNING, "calay: Underscores in node name replaced with dashes: \"%s\"\n", str);

		while(p)
		{
			fwrite(str, p-str, 1, f);

			while((*p)=='_')
			{
				fputc('-', f);
				++p;
			}

			str = p;

			p = strchr(str, '_');
		}

		fprintf(f, "%s", str);
		while(len<8) { fputc(' ', f); ++len; }
	}
	else
	{
		fprintf(f, "%-8s", str);
	}
}

/* Export abstract components; exports refdes, footprint, value/device */
static void calay_export_comps(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e;
	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;

		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);
		if(fp==NULL)
		{
			fp = "unknown_fp";
			rnd_message(RND_MSG_ERROR, "calay: no footprint is set for \"%s\"\n", refdes);
		}

		dev = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_DEVICE);
		if(dev==NULL) dev = sch_nle_get_alt_attr(&comp->hdr.attr, SCH_NLE_ALTATTR_VALUE);
		if(dev==NULL) dev = "X";

		calay_print_value(f, dev);
		fputc(' ', f);
		calay_print_value(f, refdes);
		fputc(' ', f);
		calay_print_value(f, fp);
		fprintf(f, " 000     000     0\n");
	}
}

/* Export abstract nets; exports net's name and a list of refdes-pinnum */
/* pairs connected */
static void calay_export_nets(FILE *f, csch_abstract_t *abs, int new_fmt)
{
	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, "calay: invalid connection (object type)\n");
				continue;
			}

			pinnums = sch_nle_get_pinnum(port);
			if (pinnums == NULL) {
				rnd_message(RND_MSG_ERROR, "calay: 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, "calay: 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;

						if(!new_fmt)
						{
							fputc('/', f);
						}

						calay_print_nodename(f, netname);
					}

					fprintf(f, " %s(", refdes);
					if(new_fmt)
					{
						fputc('\'', f);
					}
					fprintf(f, "%s)", my_num);
				}
			);
		}

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

#define CALAY_NET_EXT ".net"
#define CALAY_CMP_EXT ".cmp"

static FILE* calay_open_cmp(rnd_design_t* hidlib, const char* outcmp,
	const char* netfn)
{
	if(outcmp)
	{
		return rnd_fopen(hidlib, outcmp, "w");
	}
	else
	{
		const size_t len = strlen(netfn);
		char* const buf = (char*)malloc(len+sizeof(CALAY_CMP_EXT));

		if(buf)
		{
			FILE* f;
			char* p = buf + len - (sizeof(CALAY_NET_EXT)-1);

			strcpy(buf, netfn);

			if(len>(sizeof(CALAY_NET_EXT)-1) && strcmp(p, CALAY_NET_EXT)==0)
			{
				strcpy(p, CALAY_CMP_EXT);
			}
			else
			{
				strcat(p, CALAY_CMP_EXT);
			}

			f = rnd_fopen(hidlib, buf, "w");

			free(buf);

			return f;
		}
	}

	return NULL;
}

/* Export netlist from the abstract model */
static int calay_export_project_abst(const char *fn, const char *fmt, csch_abstract_t *abs, rnd_hid_attr_val_t *options, int new_fmt)
{
	TODO("get hidlib as an arg")
	rnd_design_t *hidlib = NULL;
	FILE* f_cmp;
	FILE* f_net = rnd_fopen(hidlib, fn, "w");

	if(f_net == NULL)
		return -1;

	f_cmp = calay_open_cmp(hidlib, calay_get_outcmp_option(options), fn);
	if(f_cmp == NULL)
	{
		fclose(f_net);
		return -1;
	}

	calay_export_comps(f_cmp, abs);
	calay_export_nets(f_net, abs, new_fmt);

	fclose(f_net);
	fclose(f_cmp);
	return 0;
}

static int calay_old_export_project_abst(const char *fn, const char *fmt, csch_abstract_t *abs, rnd_hid_attr_val_t *options)
{
	return calay_export_project_abst(fn, fmt, abs, options, 0);
}

static int calay_new_export_project_abst(const char *fn, const char *fmt, csch_abstract_t *abs, rnd_hid_attr_val_t *options)
{
	return calay_export_project_abst(fn, fmt, abs, options, 1);
}

#include "hid_impl.c"

static const char* calay_get_outcmp_option(rnd_hid_attr_val_t* options)
{
	return options[HA_outcmp].str;
}

int pplg_check_ver_export_calay(int ver_needed) { return 0; }

void pplg_uninit_export_calay(void)
{
	csch_plug_io_unregister(&ecalay_net);
	rnd_export_remove_opts_by_cookie(calay_cookie);
	rnd_hid_remove_hid(&calay_hid);
}

int pplg_init_export_calay(void)
{
	RND_API_CHK_VER;

	ecalay_net_old.name = "export to old Calay";
	ecalay_net_old.export_prio = old_calay_export_prio;
	ecalay_net_old.export_project_abst = calay_old_export_project_abst;
	ecalay_net_old.ext_export_project = CALAY_NET_EXT;
	csch_plug_io_register(&ecalay_net_old);

	ecalay_net.name = "export to new Calay";
	ecalay_net.export_prio = new_calay_export_prio;
	ecalay_net.export_project_abst = calay_new_export_project_abst;
	ecalay_net.ext_export_project = CALAY_NET_EXT;
	csch_plug_io_register(&ecalay_net);


	rnd_hid_nogui_init(&calay_hid_old);
	calay_hid_old.struct_size = sizeof(rnd_hid_t);

	calay_hid_old.struct_size = sizeof(rnd_hid_t);
	calay_hid_old.name = "calay_old";
	calay_hid_old.description = "Exports project's Calay netlist (old Calay format)";
	calay_hid_old.exporter = 1;

	calay_hid_old.get_export_options = calay_get_export_options;
	calay_hid_old.do_export = calay_do_export_old;
	calay_hid_old.parse_arguments = calay_parse_arguments;
	calay_hid_old.argument_array = calay_values;

	calay_hid_old.usage = calay_usage;

	rnd_hid_register_hid(&calay_hid_old);
	rnd_hid_load_defaults(&calay_hid_old, calay_options, NUM_OPTIONS);

	rnd_hid_nogui_init(&calay_hid);
	calay_hid.struct_size = sizeof(rnd_hid_t);
	calay_hid.name = "calay";
	calay_hid.description = "Exports project's Calay netlist (new Calay format)";
	calay_hid.exporter = 1;

	calay_hid.get_export_options = calay_get_export_options;
	calay_hid.do_export = calay_do_export_new;
	calay_hid.parse_arguments = calay_parse_arguments;
	calay_hid.argument_array = calay_values;

	calay_hid.usage = calay_usage;

	rnd_hid_register_hid(&calay_hid);
	rnd_hid_load_defaults(&calay_hid, calay_options, NUM_OPTIONS);


	return 0;
}

