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

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

static void systemc_print_ident(FILE* const f, const char* const str)
{
	if(str)
	{
		if(strpbrk(str, "\" *()[]{}:."))
		{
			rnd_message(RND_MSG_ERROR, "systemc: Broken output! Name contains illegal character, but it cannot be escaped: %s\n", str);
		}

		fputs(str, f);
	}
	else
	{
		rnd_message(RND_MSG_ERROR, "systemc: Broken output! Name is empty!\n");
	}
}

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

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

		fputs(str, f);
	}

	fputc('\"', f);
}

static void systemc_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);

	if(dev)
	{
		systemc_print_ident(f, dev);
	}
	else
	{
		rnd_message(RND_MSG_ERROR, "systemc: Broken output! No device is specified for \"%s\" component, generating a placeholder name\n", refdes);

		fputs("COMP__", f);
		fputs(refdes, f);

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

/* Export abstract components; exports the component-kind list as #includes */
static void systemc_export_compincludes(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);

		if (refdes == NULL)
			continue;

		if (comp->hdr.omit)
			continue;

		fputs("#include \"", f);
		systemc_print_compname(f, comp);
		fputs(".h\"\n", f);
	}
}

/* Export abstract components; exports component instantiations as class */
/* members */
static void systemc_export_compinsts(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);

		if (refdes == NULL)
			continue;

		if (comp->hdr.omit)
			continue;

		systemc_print_compname(f, comp);
		fputc(' ', f);
		systemc_print_ident(f, refdes);
		fputs(";\n", f);
	}
}

/* Export abstract components; exports components as constructors */
static void systemc_export_compctors(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e;
	int comma = 0;

	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;

		if(comma)
		{
			fputs(",\n", f);
		}
		else
		{
			comma = 1;
		}

		fputs("    ", f);
		systemc_print_ident(f, refdes);
		fputs("(\"", f);
		systemc_print_ident(f, refdes);
		fputs("\")", f);
	}

	fputc('\n', f);
}

/* Export abstract components; exports component pins with connected net */
static void systemc_export_comppins(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);

		if (refdes == NULL)
			continue;

		if (comp->hdr.omit)
			continue;

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

			netname = sch_nle_get_netname(net);

			/* export pins */
			SCH_NLE_FOREACH_PINNUM(pinnums, my_num,
				{
					fputs("    ", f);
					systemc_print_ident(f, refdes);
					fputc('.', f);
					systemc_print_ident(f, my_num);
					fputc('(', f);
					systemc_print_ident(f, netname);
					fputs(");\n", f);
				}
			);
		}

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

/* Export abstract nets; exports all known netname as sc_signal */
static void systemc_export_netdefs(FILE *f, csch_abstract_t *abs)
{
	htsp_entry_t *e;

	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;

		fputs("sc_signal<0> ", f);
		systemc_print_ident(f, netname);
		fputs(";\n", f);
	}
}

/* Export netlist from the abstract model */
static int systemc_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, "/* generated by sch-rnd */\n");
	fprintf(f, "\n");
	fprintf(f, "#include \"systemc.h\"\n");
	systemc_export_compincludes(f, abs);
	fprintf(f, "\n");
	fprintf(f, "SC_MODULE(sch)\n");
	fprintf(f, "{\n");
	systemc_export_netdefs(f, abs);
	fprintf(f, "\n");
	systemc_export_compinsts(f, abs);
	fprintf(f, "\n");
	fprintf(f, "SC_CTOR(sch):\n");
	systemc_export_compctors(f, abs);
	fprintf(f, "  {\n");
	systemc_export_comppins(f, abs);
	fprintf(f, "  }\n");
	fprintf(f, "};\n");
	fclose(f);
	return 0;
}

#include "hid_impl.c"

int pplg_check_ver_export_systemc(int ver_needed) { return 0; }

void pplg_uninit_export_systemc(void)
{
	csch_plug_io_unregister(&esystemc_net);
	rnd_export_remove_opts_by_cookie(systemc_cookie);
	rnd_hid_remove_hid(&systemc_hid);
}

int pplg_init_export_systemc(void)
{
	RND_API_CHK_VER;

	esystemc_net.name = "export to Systemc";
	esystemc_net.export_prio = systemc_export_prio;
	esystemc_net.export_project_abst = systemc_export_project_abst;
	esystemc_net.ext_export_project = ".net";
	csch_plug_io_register(&esystemc_net);


	rnd_hid_nogui_init(&systemc_hid);

	systemc_hid.struct_size = sizeof(rnd_hid_t);
	systemc_hid.name = "systemc";
	systemc_hid.description = "Exports project's Systemc netlist";
	systemc_hid.exporter = 1;

	systemc_hid.get_export_options = systemc_get_export_options;
	systemc_hid.do_export = systemc_do_export;
	systemc_hid.parse_arguments = systemc_parse_arguments;
	systemc_hid.argument_array = systemc_values;

	systemc_hid.usage = systemc_usage;

	rnd_hid_register_hid(&systemc_hid);
	rnd_hid_load_defaults(&systemc_hid, systemc_options, NUM_OPTIONS);


	return 0;
}

