

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "defc.h"


enum modes {
	none = 0,
	reg = 1 << 1,
	indir_reg = 1 << 2,
	reg_imm = 1 << 3,
	relative = 1 << 4
};


static word32 to_hex(const char *iter, const char *end) {
	word32 rv = 0;
	while(iter != end) {
		char c = *iter++;
		rv <<= 4;
		if (isdigit(c)) rv |= c - '0';
		else rv |= (c | 0x20) - 'a' + 10;
	}
	return rv;
}

const char *ltrim(const char *cp) {
	while (isspace(*cp)) ++cp;
	return cp;
}

/*!re2c
	re2c:define:YYCTYPE = char;
	re2c:yyfill:enable = 0;
	eol = "\x00";
	ws = [ \t];
	x = [A-Fa-f0-9];
*/


const char *parse_pc(const char *cp, uint32_t *pc) {
	
	const char *YYCURSOR = cp;
	const char *YYMARKER = NULL;
	const char *YYCTXMARKER = NULL;

	if (isspace(*cp)) return ltrim(cp);

	/*!re2c
	* { return NULL; }
	x{4} ':' {
		*pc &= 0xff0000;
		*pc |= to_hex(cp, YYCURSOR - 1);
		goto next;
	}
	x{6} ':' {
		*pc = to_hex(cp, YYCURSOR - 1);
		goto next;
	}

	x{2} '/' x{4} ':' {
		uint32_t tmp = to_hex(cp, cp + 2) << 16;
		tmp |= to_hex(cp + 3, YYCURSOR - 1);
		*pc = tmp;
		goto next;
	}

	*/

next:
	return ltrim(YYCURSOR);
}

const char *parse_opcode(const char *cp, int *opcode, int *mode) {
	int op = -1;
	int m = 0;
	unsigned c;

	const char *YYCURSOR = cp;
	const char *YYMARKER = NULL;
	const char *YYCTXMARKER = NULL;

	/* TODO - db, dw ? */

	/*!re2c
	* { return NULL; }
	'set'   { op = 0x10; m = reg_imm; goto next; }
	'ld'    { op = 0x20; m = reg | indir_reg; goto next; }
	'st'    { op = 0x30; m = reg | indir_reg; goto next; }
	'ldd'   { op = 0x60; m = indir_reg; goto next; }
	'std'   { op = 0x70; m = indir_reg; goto next; }
	'pop'   { op = 0x80; m = indir_reg; goto next; }
	'stp'   { op = 0x90; m = indir_reg; goto next; }
	'add'   { op = 0xa0; m = reg; goto next; }
	'sub'   { op = 0xb0; m = reg; goto next; }
	'popd'  { op = 0xc0; m = indir_reg; goto next; }
	'cpr'   { op = 0xd0; m = reg; goto next; }
	'inr'   { op = 0xe0; m = reg; goto next; }
	'dcr'   { op = 0xf0; m = reg; goto next; }

	'rtn'   { op = 0x00; goto next; }
	'br'    { op = 0x01; m = relative; goto next; }
	'bnc'   { op = 0x02; m = relative; goto next; }
	'bc'    { op = 0x03; m = relative; goto next; }
	'bp'    { op = 0x04; m = relative; goto next; }
	'bm'    { op = 0x05; m = relative; goto next; }
	'bz'    { op = 0x06; m = relative; goto next; }
	'bnz'   { op = 0x07; m = relative; goto next; }
	'bm1'   { op = 0x08; m = relative; goto next; }
	'bnm1'  { op = 0x09; m = relative; goto next; }
	'bk'    { op = 0x0a; goto next; }
	'rs'    { op = 0x0b; goto next; }
	'bs'    { op = 0x0c; m = relative; goto next; }
*/

next:
	c = *YYCURSOR;
	if (c && !isspace(c)) return NULL;

	*opcode = op;
	*mode = m;
	return ltrim(YYCURSOR);
}

const char *parse_register(const char *cp, int *opcode) {
	const char *YYCURSOR = cp;
	const char *YYMARKER = NULL;
	const char *YYCTXMARKER = NULL;

	/*!re2c
	* { return NULL; }

	'R' ([0-9]{1,2}) {
		int r = 0;
		for(cp = cp + 1; cp < YYCURSOR; ++cp) {
			r = r * 10 + *cp - '0';		
		}
		if (r > 15) return NULL;
		*opcode |= r;
		goto next;
	}

	/*
	'R' x {
		int r = to_hex(cp + 1, YYCURSOR);
		*opcode |= r;
		goto next;
	}
	*/
	'ACC' { *opcode |= 0x00; goto next; }
	'PC' { *opcode |= 0x0f; goto next; }
	'SR' { *opcode |= 0x0e; goto next; }
	*/

next:
	return ltrim(YYCURSOR);
}

const char *parse_address(const char *cp, int *address, int pc) {

	const char *YYCURSOR = cp;
	const char *YYMARKER = NULL;
	const char *YYCTXMARKER = NULL;

	int ea = 0;

	/*!re2c

	* { return NULL; }
	'*' { ea = pc; goto next; }
	'*' [+-] x{1,4} {
		ea = to_hex(cp + 2, YYCURSOR);
		if (cp[1] == '+') ea += pc;
		else ea -= pc;
		goto next;
	}
	x{1,4} {
		ea = to_hex(cp, YYCURSOR);
		goto next;
	}
	*/	

next:
	*address = ea & 0xffff;
	return ltrim(YYCURSOR);
}


uint32_t sweet16_disasm(uint32_t addr, int lines) {
	
	unsigned op;
	const char *opcode;
	unsigned mode = none;
	unsigned operand;
	unsigned x;
	int i;

	uint8_t bytes[4];

	while (lines--) {
		unsigned size = 0;
		uint32_t pc = addr;
		op = get_memory_c(addr++, 0);
		bytes[size++] = op;

		switch (op >> 4) {
		case 0x00:
			switch(op) {
			case 0x00: opcode = "RTN"; break;
			case 0x01: opcode = "BR"; mode = relative; break;
			case 0x02: opcode = "BNC"; mode = relative; break;
			case 0x03: opcode = "BC"; mode = relative; break;
			case 0x04: opcode = "BP"; mode = relative; break;
			case 0x05: opcode = "BM"; mode = relative; break;
			case 0x06: opcode = "BZ"; mode = relative; break;
			case 0x07: opcode = "BNZ"; mode = relative; break;
			case 0x08: opcode = "BM1"; mode = relative; break;
			case 0x09: opcode = "BNM1"; mode = relative; break;
			case 0x0a: opcode = "BK"; break;
			case 0x0b: opcode = "RS"; break;
			case 0x0c: opcode = "BS"; mode = relative; break;
			case 0x0d: opcode = "???"; break;
			case 0x0e: opcode = "???"; break;
			case 0x0f: opcode = "???"; break;
			}
			break;
		case 0x01: opcode = "SET"; mode = reg_imm; break;
		case 0x02: opcode = "LD"; mode = reg; break;
		case 0x03: opcode = "ST"; mode = reg; break;
		case 0x04: opcode = "LD"; mode = indir_reg; break;
		case 0x05: opcode = "ST"; mode = indir_reg; break;
		case 0x06: opcode = "LDD"; mode = indir_reg; break;
		case 0x07: opcode = "STD"; mode = indir_reg; break;
		case 0x08: opcode = "POP"; mode = indir_reg; break;
		case 0x09: opcode = "STP"; mode = indir_reg; break;
		case 0x0a: opcode = "ADD"; mode = reg; break;
		case 0x0b: opcode = "SUB"; mode = reg; break;
		case 0x0c: opcode = "POPD"; mode = indir_reg; break;
		case 0x0d: opcode = "CPR"; mode = reg; break;
		case 0x0e: opcode = "INR"; mode = reg; break;
		case 0x0f: opcode = "DCR"; mode = reg; break;
		}


		switch(mode) {
			case none:
			case reg:
			case indir_reg:
				break;
			case relative:
				operand = (int8_t)get_memory_c(addr++, 0);
				bytes[size++] = operand;
				operand += addr;
				operand &= 0xffff;
				break;
			case reg_imm:
				operand = get_memory16_c(addr, 0);
				addr += 2;
				bytes[size++] = operand & 0xff;
				bytes[size++] = operand >> 8;
				break;
		}

		x = printf("%02x/%04x:", pc >> 16, pc & 0xffff);
		for (i = 0; i < size; ++i) {
			x += printf(" %02x", bytes[i]);
		}

		for( ; x < 20; ++x) fputc(' ', stdout);
		printf("%-5s", opcode);
		switch(mode) {
			case none: break;
			case reg: x += printf("R%d", op & 0x0f); break;
			case indir_reg: x += printf("@R%d", op & 0x0f); break;
			case relative:
				x += printf("%04x", operand);
				break;
			case reg_imm:
				x += printf("R%d, %04x", op & 0x0f, operand);
				break;
		}

		fputc('\n', stdout);
	}
	return addr;
}


static int error(int offset, const char *msg) {
	while (offset > 0) { fputc(' ', stderr); --offset; }
	fputs("  ^", stderr);
	fputs(msg, stderr);
	fputc('\n', stderr);
	return -1;
}
int parse_line(const char *cp, uint32_t *pc) {
	
	uint32_t addr = *pc;
	int opcode;
	int operand;
	int indir = 0;
	int mode = 0;
	int i;
	unsigned offset = 0;
	const char *start = cp;

	uint8_t bytes[3];
	int size = 0;

	cp = parse_pc(cp, &addr);
	if (!cp) return error(0, "error");
	offset = cp - start;

	/* label only? */
	if (!*cp) {
		*pc = addr;
		return 0;
	}

	cp = parse_opcode(cp, &opcode, &mode);
	if (!cp) return error(offset, "bad opcode");
	offset = cp - start;

	if (mode & (reg | indir_reg | reg_imm)) {
		if (*cp == '@') {
			indir = 1;
			++cp;
		}
		cp = parse_register(cp, &opcode);
		if (!cp) return error(offset, "bad register");
		offset = cp - start;

		/* cleanup indir */
		/* LD / ST */
		if (indir && mode == (reg|indir_reg)) {
			opcode += 0x20;
			mode = indir_reg;
		}
		if ((mode == indir_reg) != indir)
			return error(offset, "bad operand");
	}


	bytes[size++] = opcode;
	if (mode == reg_imm) {
		if (*cp++ != ',')
			return error(offset, "expected ,");

		cp = ltrim(cp);
		offset = cp - start;
	}

	if (mode & (relative | reg_imm)) {
		cp = parse_address(cp, &operand, addr);
		if (!cp) return error(offset, "bad operand");
		offset = cp - start;

		if (mode == relative) {
			int tmp = (addr + 2) & 0xffff;
			operand -= tmp;
			if (operand > 127 || operand < -128)
				return error(offset, "out of range");
			bytes[size++] = operand;
		} else {
			bytes[size++] = operand & 0xff;
			bytes[size++] = operand >> 8;
		}
	}
	if (!cp) return error(offset, "bad operand");


	for (i = 0; i < size; ++i) {
		set_memory_c(addr + i, bytes[i], 0);
	}
	*pc = addr + size;

	fputs("\r\x1b[A\x1b[K", stdout);
	sweet16_disasm(addr, 1);
	return 1;
}




extern char *x_readline(const char *prompt);


uint32_t sweet16_asm_shell(uint32_t addr) {
	
	for(;;) {
		const char *cp = x_readline("!!");
		if (!cp || !*cp) return addr;

		parse_line(cp, &addr);

	}
}
