#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <unistd.h>		/* getopt */

const char *program;
const char *package;
const char *filename;
long width;
int bigendian;
int verbose;

void help()
{
	fprintf(stderr, "Usage: %s [OPTION] <filename>\n", program);
	fprintf(stderr, "\n");
	fprintf(stderr,
			"	-w <width>		 width of values in bytes [1/2/4/8/16]				(4)\n");
	fprintf(stderr,
			"	-p <package>	 name of the output package						(filename)\n");
	fprintf(stderr,
			"	-s <size>			pad the output up to size bytes			 (filesize)\n");
	fprintf(stderr,
			"	-b						 big-endian operation												 (*)\n");
	fprintf(stderr,
			"	-l						 little-endian operation												 \n");
	fprintf(stderr, "	-v						 verbose operation\n");
	fprintf(stderr, "	-h						 display this help and exit\n");
	fprintf(stderr, "\n");
	fprintf(stderr,
			"Report Etherbone bugs to <white-rabbit-dev@ohwr.org>\n");
}

/* We don't want localized versions from ctype.h */
inline int my_isalpha(char c)
{
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}

inline int my_isok(char c)
{
	return c == '_' || my_isalpha(c) || (c >= '0' && c <= '9');
}

int main(int argc, char **argv)
{
	int j, opt, error, i_width;
	long i, elements, size, columns, entry_width;
	char *value_end;
	unsigned char x[16];	/* Up to 128 bit */
	char buf[100];
	FILE *f;

	/* Default values */
	program = argv[0];
	package = 0;		/* auto-detect */
	width = 4;
	bigendian = 1;
	verbose = 0;
	size = -1;		/* file size */

	/* Process the command-line */
	while ((opt = getopt(argc, argv, "w:p:s:blvh")) != -1) {
		switch (opt) {
			case 'w':
				width = strtol(optarg, &value_end, 0);
				if (*value_end ||	/* bad integer */
						((width - 1) & width) != 0 ||	/* not a power of 2 */
						width == 0 || width > 16) {
					fprintf(stderr,
							"%s: invalid value width -- '%s'\n",
							program, optarg);
					error = 1;
				}
				break;
			case 'p':
				package = optarg;
				break;
			case 's':
				size = strtol(optarg, &value_end, 0);
				if (*value_end) {
					fprintf(stderr,
							"%s: invalid value size -- '%s'\n",
							program, optarg);
					error = 1;
				}
				break;
			case 'b':
				bigendian = 1;
				break;
			case 'l':
				bigendian = 0;
				break;
			case 'v':
				verbose = 1;
				break;
			case 'h':
				help();
				return 1;
			case ':':
			case '?':
				error = 1;
				break;
			default:
				fprintf(stderr, "%s: bad getopt result\n", program);
				return 1;
		}
	}

	if (optind + 1 != argc) {
		fprintf(stderr,
				"%s: expecting one non-optional argument: <filename>\n",
				program);
		return 1;
	}

	filename = argv[optind];

	/* Confirm the filename exists */
	if ((f = fopen(filename, "r")) == 0) {
		fprintf(stderr, "%s: %s while opening '%s'\n", program,
				strerror(errno), filename);
		return 1;
	}

	/* Deduce if it's aligned */
	fseek(f, 0, SEEK_END);
	elements = ftell(f);
	rewind(f);

	if (size == -1) {
		size = elements;
	}

	if (size < elements) {
		fprintf(stderr,
				"%s: length of initialization file '%s' (%ld) exceeds specified size (%ld)\n",
				program, filename, elements, size);
		return 1;
	}

	if (elements % width != 0) {
		fprintf(stderr,
				"%s: initialization file '%s' is not a multiple of %ld bytes\n",
				program, filename, width);
		return 1;
	}
	elements /= width;

	if (size % width != 0) {
		fprintf(stderr,
				"%s: specified size '%ld' is not a multiple of %ld bytes\n",
				program, size, width);
		return 1;
	}
	size /= width;

	/* Find a suitable package name */
	if (package == 0) {
		if (strlen(filename) >= sizeof(buf) - 5) {
			fprintf(stderr,
					"%s: filename too long to deduce package name -- '%s'\n",
					program, filename);
			return 1;
		}

		/* Find the first alpha character */
		while (*filename && !my_isalpha(*filename))
			++filename;

		/* Start copying the filename to the package */
		for (i = 0; filename[i]; ++i) {
			if (my_isok(filename[i]))
				buf[i] = filename[i];
			else
				buf[i] = '_';
		}
		buf[i] = 0;

		if (i == 0) {
			fprintf(stderr,
					"%s: no appropriate characters in filename to use for package name -- '%s'\n",
					program, filename);
			return 1;
		}

		package = &buf[0];
	} else {
		/* Check for valid VHDL identifier */
		if (!my_isalpha(package[0])) {
			fprintf(stderr, "%s: invalid package name -- '%s'\n",
					program, package);
			return 1;
		}
		for (i = 1; package[i]; ++i) {
			if (!my_isok(package[i])) {
				fprintf(stderr,
						"%s: invalid package name -- '%s'\n",
						program, package);
				return 1;
			}
		}
	}

	/* Find how many digits it takes to fit 'size' */
	i_width = 1;
	for (i = 10; i <= size; i *= 10)
		++i_width;

	/* How wide is an entry of the table? */
	entry_width = i_width + 6 + width * 2 + 3;
	columns = 76 / entry_width;

	printf("-- AUTOGENERATED FILE (from genramvhd.c run on %s) --\n",
			argv[1]);
	printf("library IEEE;\n");
	printf("use IEEE.std_logic_1164.all;\n");
	printf("use IEEE.numeric_std.all;\n");
	printf("\n");
	printf("library work;\n");
	printf("use work.memory_loader_pkg.all;\n");
	printf("\n");

	printf("package %s_pkg is\n", package);
	printf
		("	constant %s_init : t_meminit_array(%ld downto 0, %ld downto 0) := (\n",
		 package, size - 1, (width * 8) - 1);

	for (i = 0; i < size; ++i) {
		if (i % columns == 0)
			printf("		");

		if (i < elements) {
			if (fread(x, 1, width, f) != width) {
				perror("fread");
				return 1;
			}
		} else {
			memset(x, 0, sizeof(x));
		}

		printf("%*ld => x\"", i_width, i);
		if (bigendian) {
			for (j = 0; j < width; ++j)
				printf("%02x", x[j]);
		} else {
			for (j = width - 1; j >= 0; --j)
				printf("%02x", x[j]);
		}
		printf("\"");

		if ((i + 1) == size)
			printf(");\n");
		else if ((i + 1) % columns == 0)
			printf(",\n");
		else
			printf(", ");
	}
	fclose(f);

	printf("end %s_pkg;\n", package);

	return 0;
}
