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

#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <xml-schem.h>

#define MAX_NAME    128
#define MAX_PORTS   10000

static char ports[MAX_PORTS][MAX_NAME];
static int use[MAX_PORTS];

static char *
mangle_vhdl(const char *name)
{
   static char buf[16][1024];
    static int n = 0;
    char *to;
    int x;

    to = buf[n];
    n = (n + 1) % 16;

    x = 0;

    while (*name != '\0') {
        if (('0' <= *name && *name <= '9')
         || ('A' <= *name && *name <= 'Z')
         || ('a' <= *name && *name <= 'z')) {
                to[x++] = *name++;
        } else {
                unsigned char c;

                c = *name++;
                to[x++] = '_';
                to[x++] = (c / 16 < 10) ? (c / 16 + '0') : (c / 16 - 10 + 'A');
                to[x++] = (c % 16 < 10) ? (c % 16 + '0') : (c % 16 - 10 + 'A');
        }
    }

    to[x] = '\0';

    return to;
}

static int
create_comp(FILE *f, char* fullname, int count)
{
    char name[MAX_NAME];
    FILE *fp;
    struct xml_schem *schem;

    strcpy(name, strrchr(fullname, '/') + 1);
    *strrchr(name, '.') = '\0';

    fprintf(f, "\t\t%s : %s\n", name, name);
    fprintf(f, "\t\t\tport map(\n");

    /*
     * Read xml file.
     */
    fp = fopen(fullname, "r");
    if (! fp) {
	perror("fopen");
	exit(1);
    }
    assert(fp);

    schem = xml_schem_read(fp);
    fclose(fp);
    struct xml_schem_port *port;
    //struct xml_schem_signal *sig;
    int i, twin = 0;

    for(port = schem->port_first; port; port = port->next){
	fprintf(f, "\t\t\t\tport_%s => port_%s",
			mangle_vhdl(port->name), mangle_vhdl(port->name));
	if (port->next) {
	    fprintf(f, ",");
	}// else if (schem->sig_first && !strstr(fullname, "zuse_trommel")) {
	//    fprintf(f, ",");
	//}
	fprintf(f, "\n");

	if (count >= MAX_PORTS) {
	    fprintf(stderr, "More than %i ports, increase MAX_PORTS", MAX_PORTS);
	}
	use[count]++;

	for(i = 0; i < count; i++) {
	    if(! strcmp(port->name, ports[i])) {
		use[i]++;
		twin = 1;
		break;
	    }
	}
	if(twin){
	    /* avoid double init */
	    twin = 0;
	} else {
	    strncpy(ports[count++], port->name, MAX_NAME);
	}

    }
    /* TODO haesslicher special case fuer die trommel. stattdessen
     * trommel.src erstellen lassen? */
//
//    if(!strstr(fullname, "zuse_trommel")) {
//
//	for(sig = schem->sig_first; sig; sig = sig->next) {
//
//	    fprintf(f, "\t\t\t\tport_%s => port_%s", sig->id, sig->id);
//	    if(sig->next) {
//		fprintf(f, ",");
//	    }
//	    fprintf(f, "\n");
//
//	    if (count >= MAX_PORTS) {
//		fprintf(stderr, "More than %i ports, increase MAX_PORTS", MAX_PORTS);
//	    }
//	    use[count]++;
//
//	    for(i = 0; i < count; i++) {
//		if(! strcmp(sig->id, ports[i])) {
//		    use[i]++;
//		    twin = 1;
//		    break;
//		}
//	    }
//	    if(twin){
//		/* avoid double init */
//		twin = 0;
//	    } else {
//		strncpy(ports[count++], sig->id, MAX_NAME);
//	    }
//	}
//    }
    fprintf(f, "\t\t\t);\n");
    return count;
}

static void
create_cable(FILE *f, int count)
{
	int i;
	for (i = 0; i < count; i++) {
		fprintf(f, "\t\tsignal port_%s : std_logic;\n", mangle_vhdl(ports[i]));
	}
}

static void create_vhdl
(const char* output, const char* path)
{

    FILE *f, *swap;
    char filename[MAX_NAME];
    strcpy(filename, output);
    strcat(filename, "test_bench.vhdl");

    f = fopen(filename, "w");
    if (f == NULL) {
	fprintf(stderr, "Can't open output file %s!\n",
		filename);
	exit(EXIT_FAILURE);
    }
    strcat(filename, ".tmp");
    swap = fopen(filename, "w");

    fprintf(f, "--\n");
    fprintf(f, "-- Copyright (C) 2015 FAUmachine Team <info@faumachine.org>.\n");
    fprintf(f, "--\n");
    fprintf(f, "-- This program is free software. You can redistribute it and/or modify it\n");
    fprintf(f, "-- under the terms of the GNU General Public License, either version 2 of\n");
    fprintf(f, "-- the License, or (at your option) any later version. See COPYING.\n");
    fprintf(f, "--\n");
    fprintf(f, "\n");
    fprintf(f, "library expect;\n");
    fprintf(f, "use expect.types.all;\n");
    fprintf(f, "use expect.procedures.all;\n");
    fprintf(f, "library ieee;\n");
    fprintf(f, "use ieee.std_logic_1164.ALL;\n");
    fprintf(f, "\n");
    fprintf(f, "entity test_bench is\n");
    fprintf(f, "end test_bench;\n");
    fprintf(f, "\n");
    fprintf(f, "architecture structural of test_bench is\n");

    struct stat status;
    if (lstat(path, &status) == -1) {
	perror(path);
	return;
    }
    if (! S_ISDIR(status.st_mode)) {
	fprintf(stderr, "%s is not a directory", path);
	exit(1);
    }

    DIR *dir = opendir(path);
    if (dir == NULL) {
	perror(path);
	exit(1);
    }
    char fullname[512];
    int len;
    struct dirent *entry;
    int count = 0;
    for (;;) {
	errno = 0;
	entry = readdir(dir);
	if (entry == NULL) {
	    if (errno != 0) {
		perror("readdir()");
		exit(1);
	    }
	    break;
	}

	sprintf(fullname, "%s/%s", path, entry->d_name);

	struct stat buf;
	if (lstat(fullname, &buf) ) {
	    perror("lstat");
	    continue;
	}
	if ( ! S_ISREG(buf.st_mode) ) {
	    continue;
	}
	len = strlen(entry->d_name);
	if(entry->d_name[len-4] == '.' &&
	   entry->d_name[len-3] == 'x' &&
	   entry->d_name[len-2] == 'm' &&
	   entry->d_name[len-1] == 'l'
	  ) {
	    count = create_comp(swap, fullname, count);
	}
    }

    fclose(swap);

    create_cable(f, count);

    fprintf(f, "begin\n");
    swap = fopen(filename, "r");
    int ch;
    while (1) {
	ch = fgetc(swap);

	if (ch == EOF)
	    break;
	else
	    putc(ch, f);
    }

    fprintf(f, "end structural;\n");
    fclose(f);
    fclose(swap);
    remove(filename);
}

static void __attribute__((noreturn))
usage(int retval, char *progname)
{
    fprintf(stderr, "Usage: %s <folder> [-o output]\n", progname);
    exit(retval);
}

int
main(int argc, char **argv)
{
    char* progname = *argv;
    int opt_o = 0;
    char output[MAX_NAME];
    char* folder;
    argv++;
    argc--;

    /*
     * Get options.
     */
    int c = getopt(argc, argv, "o:");
    if (c != -1) {
	if (c == 'o'){
	    strncpy(output, optarg, MAX_NAME);
	    opt_o = 1;
	} else {
	    usage(1, progname);
	}
    } else {
	strcpy(output, ".");
    }
    strcat(output, "/");

    if (argc == 1 ||
	(argc == 3 && opt_o) ) {
	folder = argv[0];
    } else {
	usage(1, progname);
    }

    create_vhdl(output, folder);

    return 0;
}
