/*
 * Copyright (C) 2015 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#define DEBUG	0

#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "xml-schem.h"

char *progname;
int opt_M;
char *inname;

static struct entry {
	int seq;
	const char *name;
	const char *inout;
	const char *type;
	const char *sig;
} entry[1000];
static unsigned int nentries;

static void
table_init(void)
{
	nentries = 0;
}

static void
table_add(
	const char *pinseq,
	const char *name,
	const char *inout,
	const char *type,
	const char *sig
)
{
	entry[nentries].seq = pinseq ? atoi(pinseq) : -1;
	entry[nentries].name = name;
	entry[nentries].inout = inout;
	entry[nentries].type = type;
	entry[nentries].sig = sig;
	nentries++;
}

static void
table_sort(void)
{
	unsigned int i;
	int done;

	if (nentries <= 1) {
		/* Nothing to sort... */
		return;
	}

	do {
		done = 1;
		for (i = 0; i < nentries - 1; i++) {
		       if (entry[i + 1].seq < entry[i].seq
			 || (entry[i + 1].seq == entry[i].seq
			  && strcmp(entry[i + 1].name, entry[i].name) < 0)) {
				int tint;
				const char *tstr;
#define swap_int(x) \
	{ tint = entry[i].x; entry[i].x = entry[i + 1].x; entry[i + 1].x = tint; }
#define swap_str(x) \
	{ tstr = entry[i].x; entry[i].x = entry[i + 1].x; entry[i + 1].x = tstr; }
				swap_int(seq);
				swap_str(name);
				swap_str(inout);
				swap_str(type);
				swap_str(sig);
				done = 0;
			}
		}
	} while (! done);
}

static void
mangle(char *buf, const char *real)
{
	char c;

	*buf++ = 'v';
	*buf++ = 'h';
	*buf++ = 'd';
	*buf++ = 'l';
	*buf++ = '_';

	while ((c = *real++) != '\0') {
		switch (c) {
		case '0' ... '9':
		case 'a' ... 'z':
			*buf++ = c;
			break;
		case 'A' ... 'Z':
			*buf ++ = c - 'A' + 'a';
			break;
		default:
			sprintf(buf, "_%02x_", c);
			buf += strlen(buf);
			break;
		}
	}
	*buf = '\0';
}

static const char *
vhdl_sig_name(const char *real)
{
	static char buf[10][100];
	static unsigned int bufno = 0;

	bufno %= 10;

	mangle(&buf[bufno][0], real);

	return buf[bufno++];
}

static const char *
vhdl_sig_type(const char *s)
{
	return s;
}

static const char *
sig_type(struct xml_schem *schem, const char *id)
{
	struct xml_schem_signal *sig;

	for (sig = schem->sig_first; ; sig = sig->next) {
		if (! sig) {
			fprintf(stderr, "ERROR: signal \"%s\" not found.\n", id);
			return "UNKNOWN";
		}
		if (strcmp(sig->id, id) == 0) {
			return sig->type;
		}
	}
}

static void
gen_vhdl_entity(FILE *fp, const char *type, struct xml_schem *schem)
{
	struct xml_schem_generic *gen;
	struct xml_schem_port *port;

	/*
	 * Generate Header
	 */
	fprintf(fp, "--\n");
	fprintf(fp, "-- WARNING:\n");
	fprintf(fp, "--\n");
	fprintf(fp, "-- Automatically generated from %s.\n", inname);
	fprintf(fp, "--\n");

	fprintf(fp, "\n");

	/*
	 * Generate Entry
	 */
	/* Name */
	fprintf(fp, "USE types.all;\n");
	fprintf(fp, "LIBRARY ieee;\n");
	fprintf(fp, "USE ieee.std_logic_1164.all;\n");
	fprintf(fp, "ENTITY %s IS\n", type);

	/* Skip Simuation Setups */

	/* Generics */
	if (schem->gen_first) {
		fprintf(fp, "\tGENERIC (\n");

		for (gen = schem->gen_first; gen; gen = gen->next) {
			fprintf(fp, "\t\t");
			fprintf(fp, "%s", gen->name);
			fprintf(fp, " : ");
			fprintf(fp, "%s", gen->type);
			fprintf(fp, " := ");
			if (strcmp(gen->type, "string") == 0) {
				fprintf(fp, "\"%s\"", gen->value);
			} else {
				fprintf(fp, "%s", gen->value);
			}
			if (gen->next) {
				fprintf(fp, ";");
			}
			fprintf(fp, "\n");
		}

		fprintf(fp, "\t);\n");
	}

	/* Ports */
	if (schem->port_first) {
		fprintf(fp, "\tPORT (\n");
		
		for (port = schem->port_first; port; port = port->next) {
			fprintf(fp, "\t\t");
			fprintf(fp, "%s", port->name);
			fprintf(fp, " : ");
			if (strcmp(port->inout, "in") == 0
			 || strcmp(port->inout, "incall") == 0) {
				fprintf(fp, "IN");
			} else if (strcmp(port->inout, "out") == 0
				|| strcmp(port->inout, "outcall") == 0) {
				fprintf(fp, "OUT");
			} else if (strcmp(port->inout, "io") == 0
				|| strcmp(port->inout, "iocall") == 0
				|| strcmp(port->inout, "call") == 0) {
				fprintf(fp, "INOUT");
			}
			fprintf(fp, " ");
			fprintf(fp, "%s", sig_type(schem, port->signal));
			if (port->next) {
				fprintf(fp, ";");
			}
			fprintf(fp, "\n");
		}

		fprintf(fp, "\t);\n");
	}

	fprintf(fp, "END %s;\n", type);
}

static void
gen_vhdl_architecture(FILE *fp, const char *type, struct xml_schem *schem)
{
	struct xml_schem_generic *gen;
	struct xml_schem_port *port;
	struct xml_schem_signal *sig;
	struct xml_schem_component *comp;
	struct xml_schem_name *name;

	/*
	 * Generate Header
	 */
	fprintf(fp, "--\n");
	fprintf(fp, "-- WARNING:\n");
	fprintf(fp, "--\n");
	fprintf(fp, "-- Automatically generated from %s.\n", inname);
	fprintf(fp, "--\n");

	fprintf(fp, "\n");

	/* ARCHITECTURE */
	fprintf(fp, "ARCHITECTURE structural OF %s IS\n", type);

	/*
	 * Signals
	 */
	for (sig = schem->sig_first; sig; sig = sig->next) {
		fprintf(fp, "\tSIGNAL %s : %s;",
				vhdl_sig_name(sig->id),
				vhdl_sig_type(sig->type));
		for (name = sig->name_first; name; name = name->next) {
			fprintf(fp, " -- %s", name->name);
		}
		fprintf(fp, "\n");
	}

	/* BEGIN */
	fprintf(fp, "BEGIN\n");

	/*
	 * Components
	 */
	for (comp = schem->comp_first; comp; comp = comp->next) {
		unsigned int i;

		fprintf(fp, "\t%s : %s\n", comp->name, comp->type);
	
		/* Generics */
		table_init();
		for (gen = comp->gen_first; gen; gen = gen->next) {
			table_add(gen->seq, gen->name, NULL, NULL, gen->value);
		}
		table_sort();
		if (nentries) {
			fprintf(fp, "\t\tGENERIC MAP (\n");
			for (i = 0; i < nentries; i++) {
				fprintf(fp, "\t\t\t%s => %s",
					vhdl_sig_name(entry[i].name),
					entry[i].sig);
				if (i != nentries - 1) {
					fprintf(fp, ",");
				}
				fprintf(fp, "\n");
			}
			fprintf(fp, "\t\t)\n");
		}

		/* Ports */
		table_init();
		for (port = comp->port_first; port; port = port->next) {
			table_add(port->seq, port->name, NULL, NULL, port->signal);
		}
		table_sort();
		if (nentries) {
			fprintf(fp, "\t\tPORT MAP (\n");

			for (i = 0; i < nentries; i++) {
				fprintf(fp, "\t\t\t%s => %s",
					vhdl_sig_name(entry[i].name),
					vhdl_sig_name(entry[i].sig));
				if (i != nentries - 1) {
					fprintf(fp, ",");
				}
				fprintf(fp, "\n");
			}

			fprintf(fp, "\t\t);\n");
		}
	}

	/* END */
	fprintf(fp, "END structural;\n");
}

static void __attribute__((noreturn))
usage(int retval)
{
	fprintf(stderr, "Usage: %s <*.xml>\n", progname);
	exit(retval);
}

int
main(int argc, char **argv)
{
	int c;
	FILE *fp;
	struct xml_schem *schem;
	char type[1024];
	char outname[1024];
	int ret;

	/*
	 * Get program name.
	 */
	progname = *argv;

	/*
	 * Get options.
	 */
	while ((c = getopt(argc, argv, "M")) != -1) {
		switch (c) {
		case 'M':
			opt_M = 1;
			break;
		default:
			usage(1);
		}
	}
	argc -= optind;
	argv += optind;

	/*
	 * Get parameter.
	 */
	if (0 < argc) {
		inname = *argv;
		argc--;
		argv++;
	} else {
		usage(1);
	}
	if (argc != 0) {
		usage(1);
	}

	/*
	 * Check parameter.
	 */
	if (! strchr(inname, '.')) {
		usage(1);
	}
	
	if (strchr(inname, '/')) {
		strcpy(type, strrchr(inname, '/') + 1);
	} else {
		strcpy(type, inname);
	}
	if (strchr(type, '.')) {
		*strchr(type, '.') = '\0';
	}

	/*
	 * Read xml file.
	 */
	fp = fopen(inname, "r");
	if (! fp) {
		fprintf(stderr, "ERROR: %s: %s: %s.\n", progname,
				inname, strerror(errno));
		exit(1);
	}
	assert(fp);

	schem = xml_schem_read(fp);

	fclose(fp);

	/*
	 * Generate <type>.vhdl File
	 */
	strcpy(outname, inname);
	strcpy(strrchr(outname, '.'), ".vhdl");
	fp = fopen(outname, "w");
	if (! fp) {
		fprintf(stderr, "%s: ERROR: Can't create %s: %s.\n", progname,
				outname, strerror(errno));
		exit(1);
	}

	gen_vhdl_entity(fp, type, schem);

	ret = fclose(fp);
	assert(ret == 0);

	/*
	 * Generate <type>.avhdl File
	 */
	strcpy(outname, inname);
	strcpy(strrchr(outname, '.'), ".avhdl");
	fp = fopen(outname, "w");
	if (! fp) {
		fprintf(stderr, "%s: ERROR: Can't create %s: %s.\n", progname,
				outname, strerror(errno));
		exit(1);
	}

	gen_vhdl_architecture(fp, type, schem);

	ret = fclose(fp);
	assert(ret == 0);

	return 0;
}
