#define _POSIX_C_SOURCE 199309
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <string.h>
#include "intel_8080_emulator.h"
#include "dis8080.h"
#include "globals.h"

uint8_t ram[0x10000];

struct watchpoint
{
	uint16_t address;
	uint8_t value;
	bool enabled;
};

static uint16_t breakpoints[16];
static struct watchpoint watchpoints[16];
static bool tracing = false;
static bool singlestepping = true;
static bool bdosbreak = false;

static const char* delimiters = " \t\n\r";

uint8_t i8080_read(uint16_t addr)
{
	return ram[addr];
}

void i8080_write(uint16_t addr, uint8_t value)
{
	ram[addr] = value;
}

uint8_t i8080_inport(uint8_t addr)
{
	return 0;
}

void i8080_outport(uint8_t addr, uint8_t value)
{
	biosbdos_entry(addr & 0xff);
	if (bdosbreak)
		singlestepping = true;
}

void showregs(void)
{
	uint16_t af = i8080_read_reg16(AF);
	printf("%c%c.%c.%c%c%c sp=%04x af=%04x bc=%04x de=%04x hl=%04x\n",
		(af & 0x80) ? 'S' : 's',
		(af & 0x40) ? 'Z' : 'z',
		(af & 0x10) ? 'H' : 'h',
		(af & 0x04) ? 'P' : 'p',
		(af & 0x02) ? 'N' : 'n',
		(af & 0x01) ? 'C' : 'c',
		i8080_read_reg16(SP),
		af,
		i8080_read_reg16(BC),
		i8080_read_reg16(DE),
		i8080_read_reg16(HL));

	char buffer[80];
	int tstates;
	uint16_t pc = i8080_read_reg16(PC);
	i8080_disassemble(buffer, sizeof(buffer), pc);
	puts(buffer);
}

static void cmd_register(void)
{
	char* w1 = strtok(NULL, delimiters);
	char* w2 = strtok(NULL, delimiters);

	if (w1 && w2)
	{
		int reg = -1;
		if (strcmp(w1, "sp") == 0)
			reg = SP;
		else if (strcmp(w1, "pc") == 0)
			reg = PC;
		else if (strcmp(w1, "af") == 0)
			reg = AF;
		else if (strcmp(w1, "bc") == 0)
			reg = BC;
		else if (strcmp(w1, "de") == 0)
			reg = DE;
		else if (strcmp(w1, "hl") == 0)
			reg = HL;
		else
		{
			printf("Bad register\n");
			return;
		}

		i8080_write_reg16(reg, strtoul(w2, NULL, 16));
	}

	showregs();
}

static void cmd_break(void)
{
	int i;
	char* w1 = strtok(NULL, delimiters);
	if (w1)
	{
		uint16_t breakpc = strtoul(w1, NULL, 16);
		for (i=0; i<sizeof(breakpoints)/sizeof(*breakpoints); i++)
		{
			if (breakpoints[i] == 0xffff)
			{
				breakpoints[i] = breakpc;
				return;
			}
		}
		printf("Too many breakpoints\n");
	}
	else
	{
		for (i=0; i<sizeof(breakpoints)/sizeof(*breakpoints); i++)
		{
			if (breakpoints[i] != 0xffff)
				printf("%04x\n", breakpoints[i]);
		}
	}
}

static void cmd_watch(void)
{
	int i;
	char* w1 = strtok(NULL, delimiters);
	if (w1)
	{
		uint16_t watchaddr = strtoul(w1, NULL, 16);
		for (i=0; i<sizeof(watchpoints)/sizeof(*watchpoints); i++)
		{
			struct watchpoint* w = &watchpoints[i];
			if (!w->enabled)
			{
				w->address = watchaddr;
				w->enabled = true;
				w->value = ram[watchaddr];
				return;
			}
		}
		printf("Too many breakpoints\n");
	}
	else
	{
		for (i=0; i<sizeof(watchpoints)/sizeof(*watchpoints); i++)
		{
			struct watchpoint* w = &watchpoints[i];
			if (w->enabled)
				printf("%04x (current value: %02x)\n", w->address, w->value);
		}
	}
}

static void cmd_delete_breakpoint(void)
{
	int i;
	char* w1 = strtok(NULL, delimiters);
	if (w1)
	{
		uint16_t breakpc = strtoul(w1, NULL, 16);
		for (i=0; i<sizeof(breakpoints)/sizeof(*breakpoints); i++)
		{
			if (breakpoints[i] == breakpc)
			{
				breakpoints[i] = 0xffff;
				return;
			}
		}
		printf("No such breakpoint\n");
	}
}

static void cmd_delete_watchpoint(void)
{
	int i;
	char* w1 = strtok(NULL, delimiters);
	if (w1)
	{
		uint16_t address = strtoul(w1, NULL, 16);
		for (i=0; i<sizeof(breakpoints)/sizeof(*breakpoints); i++)
		{
			struct watchpoint* w = &watchpoints[i];
			if (w->enabled && (w->address == address))
			{
				w->enabled = false;
				return;
			}
		}
		printf("No such watchpoint\n");
	}
}

static void cmd_memory(void)
{
	int i;
	char* w1 = strtok(NULL, delimiters);
	char* w2 = strtok(NULL, delimiters);

	if (!w2)
		w2 = "100";

	if (w1 && w2)
	{
		uint16_t startaddr = strtoul(w1, NULL, 16);
		uint16_t endaddr = startaddr + strtoul(w2, NULL, 16);
		uint16_t startrounded = startaddr & ~0xf;
		uint16_t endrounded = (endaddr + 0xf) & ~0xf;

		uint16_t p = startrounded;

		while (p < endrounded)
		{
			printf("%04x : ", p);
			for (i = 0; i < 16; i++)
			{
				uint16_t pp = p + i;
				if ((pp >= startaddr) && (pp < endaddr))
					printf("%02x ", ram[pp]);
				else
					printf("   ");
			}
			printf(": ");
			for (i = 0; i < 16; i++)
			{
				uint16_t pp = p + i;
				if ((pp >= startaddr) && (pp < endaddr))
				{
					uint8_t c = ram[pp];
					if ((c < 32) || (c > 127))
						c = '.';
					putchar(c);
				}
				else
					putchar(' ');
			}
			p += 16;
			putchar('\n');
		}
	}
}

static void cmd_unassemble(void)
{
	char* w1 = strtok(NULL, delimiters);
	char* w2 = strtok(NULL, delimiters);
	uint16_t startaddr = i8080_read_reg16(PC);
	uint16_t endaddr;

	if (w1)
		startaddr = strtoul(w1, NULL, 16);
	endaddr = startaddr + 0x20;
	if (w2)
		endaddr = startaddr + strtoul(w2, NULL, 16);

	while (startaddr < endaddr)
	{
		char buffer[80];
		startaddr = i8080_disassemble(buffer, sizeof(buffer), startaddr);
		puts(buffer);
	}
}

static void cmd_bdos(void)
{
	char* w1 = strtok(NULL, delimiters);
	if (w1)
		bdosbreak = !!strtoul(w1, NULL, 16);
	else
		printf("break on bdos entry: %s\n", bdosbreak ? "on" : "off");
}

static void cmd_tracing(void)
{
	char* w1 = strtok(NULL, delimiters);
	if (w1)
		tracing = !!strtoul(w1, NULL, 16);
	else
		printf("tracing: %s\n", tracing ? "on" : "off");
}

static void cmd_help(void)
{
	printf("Sleazy debugger\n"
	       "  r               show registers\n"
		   "  r <reg> <value> set register\n"
		   "  b               show breakpoints\n"
		   "  b <addr>        set breakpoint\n"
		   "  db <addr>       delete breakpoint\n"
		   "  w <addr>        set watchpoint\n"
		   "  dw <addr>       delete watchpoint\n"
		   "  m <addr> <len>  show memory\n"
		   "  u <addr> <len>  unassemble memory\n"
		   "  s               single step\n"
		   "  g               continue\n"
		   "  bdos 0|1        enable break on bdos entry\n"
		   "  tracing 0|1     enable tracing\n"
	);
}

static void debug(void)
{
	bool go = false;
	showregs();
	while (!go)
	{
		char cmdline[80];
		printf("debug> ");
		fflush(stdout);
		if (!fgets(cmdline, sizeof(cmdline), stdin))
			exit(0);

		char* token = strtok(cmdline, delimiters);
		if (token != NULL)
		{
			if (strcmp(token, "?") == 0)
				cmd_help();
			else if (strcmp(token, "r") == 0)
				cmd_register();
			else if (strcmp(token, "b") == 0)
				cmd_break();
			else if (strcmp(token, "w") == 0)
				cmd_watch();
			else if (strcmp(token, "db") == 0)
				cmd_delete_breakpoint();
			else if (strcmp(token, "dw") == 0)
				cmd_delete_watchpoint();
			else if (strcmp(token, "m") == 0)
				cmd_memory();
			else if (strcmp(token, "u") == 0)
				cmd_unassemble();
			else if (strcmp(token, "s") == 0)
			{
				singlestepping = true;
				go = true;
			}
			else if (strcmp(token, "g") == 0)
			{
				singlestepping = false;
				go = true;
			}
			else if (strcmp(token, "bdos") == 0)
				cmd_bdos();
			else if (strcmp(token, "tracing") == 0)
				cmd_tracing();
			else
				printf("Bad command\n");
		}
	}
}

static void sigusr1_cb(int number)
{
	singlestepping = true;
}

void emulator_init(void)
{
	int i;
	for (i=0; i<sizeof(breakpoints)/sizeof(*breakpoints); i++)
		breakpoints[i] = 0xffff;

	singlestepping = flag_enter_debugger;

	struct sigaction action = {
		.sa_handler = sigusr1_cb
	};
	sigaction(SIGUSR1, &action, NULL);
}

void emulator_run(void)
{
	int i;
	for (;;)
	{
		uint16_t pc = i8080_read_reg16(PC);
		if (!singlestepping)
		{
			for (i=0; i<sizeof(breakpoints)/sizeof(*breakpoints); i++)
				if (pc == breakpoints[i])
					singlestepping = true;
		}
		for (i=0; i<sizeof(watchpoints)/sizeof(*watchpoints); i++)
		{
			struct watchpoint* w = &watchpoints[i];
			if (w->enabled && (ram[w->address] != w->value))
			{
				printf("\nWatchpoint hit: %04x has changed from %02x to %02x\n",
					w->address, w->value, ram[w->address]);
				w->value = ram[w->address];
				singlestepping = true;
			}
		}

		if (singlestepping)
			debug();
		else if (tracing)
			showregs();

		i8080_exec(1);
	}
}