/*
 * Copyright (C) 2008-2016 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.
 */

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct block {
	struct block *prev;
	struct block *next;

	uint64_t addr;
	char *label[1024*1024];
	uint8_t start[1024*1024];
	uint8_t mem[1024*1024];
};

struct block *block_first;
struct block *block_last;

static int
dis_read(FILE *fp, uint64_t *addrp, uint8_t *bytep)
{
	char key[1024];
	int keylen;
	int c;

again:	;
	c = fgetc(fp);
	if (c == EOF) {
		return -1;
	}

	/*
	 * Skip CPU ID (if present).
	 */
	while ('0' <= c && c <= '9') {
		c = fgetc(fp);
	}
	if (c == ':') {
		c = fgetc(fp);
	}
	if (c == ' ') {
		c = fgetc(fp);
	}

	/*
	 * Get keyword.
	 */
	keylen = 0;
	while (('A' <= c && c <= 'Z')
	    || ('a' <= c && c <= 'z')) {
		key[keylen++] = c;
		c = fgetc(fp);
	}
	key[keylen] = '\0';

	/*
	 * Skip space (if any).
	 */
	while (c == ' '
	    || c == '\t') {
		c = fgetc(fp);
	}

	if (strcmp(key, "DIS") == 0) {
		/*
		 * DIS ...
		 */
		unsigned int count;

		count = 0;

		/*
		 * Read address.
		 */
		*addrp = 0;
		while (('0' <= c && c <= '9')
		    || ('A' <= c && c <= 'F')
		    || ('a' <= c && c <= 'f')) {
			*addrp *= 16;
			if ('0' <= c && c <= '9') {
				*addrp += c - '0';
			} else if ('A' <= c && c <= 'F') {
				*addrp += c - 'A' + 10;
			} else if ('a' <= c && c <= 'f') {
				*addrp += c - 'a' + 10;
			} else {
				assert(0);
			}
			c = fgetc(fp);
		}

		while (c != '\n' && c != EOF) {
			uint32_t val;
			unsigned int n;

			/*
			 * Skip space.
			 */
			if (c != ' ') goto skip;
			assert(c == ' ');
			c = fgetc(fp);

			/*
			 * Read byte/word/long.
			 */
			n = 0;
			val = 0;
			while (('0' <= c && c <= '9')
			    || ('A' <= c && c <= 'F')
			    || ('a' <= c && c <= 'f')) {
				val *= 16;
				if ('0' <= c && c <= '9') {
					val += c - '0';
				} else if ('A' <= c && c <= 'F') {
					val += c - 'A' + 10;
				} else if ('a' <= c && c <= 'f') {
					val += c - 'a' + 10;
				} else {
					assert(0);
				}
				n++;
				c = fgetc(fp);
			}
			if (n % 2 != 0) goto skip;
			assert(n % 2 == 0);
			n /= 2;
			switch (n) {
			case 1:
				*bytep++ = val;
				break;
			case 2:
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				break;
			case 4:
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				break;
			case 8:
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				val >>= 8;
				*bytep++ = val & 0xff;
				break;
			default:
				assert(0);
			}

			count += n;
		}

		assert(0 < count);
		return count;

	} else if (strcmp(key, "Executing") == 0) {
		/*
		 * Executing at ...
		 */

		/*
		 * Skip "at" and " ".
		 */
		if (c != 'a') goto skip;
		assert(c == 'a');
		c = fgetc(fp);
		if (c != 't') goto skip;
		assert(c == 't');
		c = fgetc(fp);

		if (c != ' ') goto skip;
		assert(c == ' ');
		c = fgetc(fp);

		/*
		 * Read address.
		 */
		*addrp = 0;
		while (('0' <= c && c <= '9')
		    || ('A' <= c && c <= 'F')
		    || ('a' <= c && c <= 'f')) {
			*addrp *= 16;
			if ('0' <= c && c <= '9') {
				*addrp += c - '0';
			} else if ('A' <= c && c <= 'F') {
				*addrp += c - 'A' + 10;
			} else if ('a' <= c && c <= 'f') {
				*addrp += c - 'a' + 10;
			} else {
				assert(0);
			}
			c = fgetc(fp);
		}

		/*
		 * Skip rest of line.
		 */
		while (c != '\n' && c != EOF) {
			c = fgetc(fp);
		}

		return 0;

	} else {
		/*
		 * Skip unknown line.
		 */
	skip:	;
		while (c != '\n' && c != EOF) {
			c = fgetc(fp);
		}
		goto again;
	}
}

static struct block *
block_get(uint64_t addr)
{
	struct block *b;

	addr &= ~(1024*1024-1);
	for (b = block_first; ; b = b->next) {
		if (! b) {
			/* Block not found. */
			b = (struct block *) malloc(sizeof(struct block));
			assert(b);

			b->addr = addr;
			memset(b->start, 0, sizeof(b->start));
			memset(b->label, 0, sizeof(b->label));
			memset(b->mem, 0, sizeof(b->mem));

			b->prev = NULL;
			b->next = block_first;
			block_first = b;
			if (b->next) {
				b->next->prev = b;
			} else {
				block_last = b;
			}
			break;
		}
		if (b->addr == addr) {
			/* Block found. */
			break;
		}
	}
	return b;
}

static void
block_start(uint64_t addr)
{
	struct block *b;

	b = block_get(addr);
	assert(b);
	b->start[addr & (1024*1024-1)] = 1;
}

static void
block_label(uint64_t addr, char *label)
{
	struct block *b;

	b = block_get(addr);
	assert(b);
	b->label[addr & (1024*1024-1)] = strdup(label);
	assert(b->label[addr & (1024*1024-1)]);
}

static void
block_mem(uint64_t addr, uint8_t val)
{
	struct block *b;

	b = block_get(addr);
	assert(b);
	b->mem[addr & (1024*1024-1)] = val;
}

int
main(int argc, char **argv)
{
	uint64_t addr;
	uint8_t byte[16];
	unsigned int count;
	unsigned int i;
	struct block *b;

	if (argc == 2) {
		/* Read labels into blocks. */
		FILE *fp;
		uint64_t addr;
		char label[1024];
		int labellen;
		int c;
		int ret;

		fp = fopen(argv[1], "r");
		assert(fp);

		for (;;) {
			/* Read address. */
			addr = 0;
			c = fgetc(fp);
			if (c == EOF) {
				break;
			}
			while (('0' <= c && c <= '9')
			    || ('A' <= c && c <= 'F')
			    || ('a' <= c && c <= 'f')) {
				addr *= 16;
				if ('0' <= c && c <= '9') {
					addr += c - '0';
				} else if ('A' <= c && c <= 'F') {
					addr += c - 'A' + 10;
				} else if ('a' <= c && c <= 'f') {
					addr += c - 'a' + 10;
				} else {
					assert(0);
				}
				c = fgetc(fp);
			}

			/* Skip space. */
			assert(c == '\t'
			    || c == ' ');
			while (c == '\t'
			    || c == ' ') {
				c = fgetc(fp);
			}

			/* Read label. */
			labellen = 0;
			while (('0' <= c && c <= '9')
			    || ('A' <= c && c <= 'Z')
			    || ('a' <= c && c <= 'z')
			    || c == '_') {
				label[labellen++] = c;
				c = fgetc(fp);
			}
			label[labellen] = '\0';

			/* Skip rest of line. */
			while (c != '\n') {
				c = fgetc(fp);
			}
			assert(c == '\n');

			block_label(addr, label);
		}

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

	/* Read data into blocks. */
	while ((count = dis_read(stdin, &addr, byte)) != -1) {
		if (count == 0) {
			block_start(addr);

		} else {
			for (i = 0; i < count; i++) {
				block_mem(addr + i, byte[i]);
			}
		}
	}

	/* Sort blocks. */
again:	;
	for (b = block_first; b && b->next; b = b->next) {
		if (b->next->addr < b->addr) {
			struct block *prev;
			struct block *act0;
			struct block *act1;
			struct block *next;

			act0 = b;
			act1 = b->next;
			prev = act0->prev;
			next = act1->next;

			act0->prev = act1;
			act0->next = next;
			act1->prev = prev;
			act1->next = act0;

			if (prev) {
				prev->next = act1;
			} else {
				block_first = act1;
			}
			if (next) {
				next->prev = act0;
			} else {
				block_last = act0;
			}
			goto again;
		}
	}

	printf("\t.text\n");
	for (b = block_first; b; b = b->next) {
		for (addr = 0; addr < 1024*1024; addr++) {
			if (b->start[addr]) {
				printf("org_%llx:\n", (unsigned long long) addr + b->addr);
			}
			if (b->label[addr]) {
				printf("%s:\n", b->label[addr]);
			}
			printf("\t.byte 0x%02x\n", b->mem[addr]);
		}
	}

	return 0;
}
