#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <time.h>
#include "p88common.h"
#include "p88simulator.h"
#include "p88symboltable.h"

#define CF_CLEAR	0
#define CF_NotBELOW	0
#define CF_BELOW	1

#define IS_STORE(x)	((x) & 01)
#define IS_MEMONLY(x)	((x) >= op_PUSH && (x) <= op_OUT)
	/* opcode == op_PUSH, op_POP, op_IN, op_OUT */
#define IS_BRANCH(x)	((x) >= op_JMP && (x) <= op_CALL)
	/* opcode == op_JMP, op_JNB, op_JB, op_CALL */

static unsigned char *mem;
static unsigned long pc;
static unsigned long max_addr;	/* MAX address kept by source program */
static unsigned int cf;
static short regs[REGISTERS];
static jmp_buf jmpenv;	/* long jump; in order to return from signal */


enum {
	st_running = 0,
	st_halt, st_outOfMem, st_illegalOpcode, st_illegalOperand,
	st_illegalMemory, st_zeroDiv, st_EOF
};

static const char *reason_str[] = {
	"running",
	"Halt",
	"out of memory",
	"illegal code",
	"illegal operand",
	"illegal memory access",
	"div by zero",
	"unexpected EOF"
};

static void ins_regs(char *p, int pos)
{
	int len = strlen(p);
	if (len < pos)
		while (len < pos)
			p[len++] = ' ';
	else
		p[len++] = ' ';
	sprintf(&p[len], "%4d %4d %4d %4d   %2s",
		regs[0], regs[1], regs[2], regs[3],
		(cf == CF_BELOW) ? "B" : "NB");
}

static void print_trace(int opcode, operand *infop)
{
	const int position = 18;
	char buf[80], *bp;
	short n;
	int cpflag = (opcode == op_COPY && IS_STORE(infop->flags));

	printf(". . . . . . . %4ld:  %-4s  ", pc, oprations[opcode]);
	if (infop == NULL) {
		buf[0] = 0;
		ins_regs(buf, position);
		printf("%s\n", buf);
		return;
	}
	if (IS_MEMONLY(opcode)) {
		sprintf(buf, "%s", registers[infop->reg2]);
		ins_regs(buf, position);
		printf("%s\n", buf);
		return;
	}
	if (!cpflag && !IS_BRANCH(opcode)) {
		sprintf(buf, "%s,", registers[infop->reg2]);
		bp = buf + strlen(buf);
	}else
		bp = buf;
	switch (infop->typ) {
	case t_reg_only: /* register direct */
		sprintf(bp, "%s", registers[infop->reg]);
		break;
	case t_mem_only: /* address */
		sprintf(bp, "[%ld]", infop->val);
		break;
	case t_reg_indirect: /* register indirect */
		sprintf(bp, "c(%s)", registers[infop->reg]);
		break;
	case t_mem_register: /* address + register indirect */
		sprintf(bp, "[%ld]+c(%s)", infop->val, registers[infop->reg]);
		break;
	case t_value: /* literal value */
		n = infop->val;	/* convert into signed short */
		sprintf(bp, "#%d", n);
		break;
	default:
		break;
	}
	if (cpflag) {
		bp += strlen(bp);
		sprintf(bp, ",%s", registers[infop->reg2]);
	}
	ins_regs(buf, position);
	printf("%s\n", buf);
}

static operand inspect_code(unsigned char *p, int *opleng)
{
	unsigned int ibits;
	static operand info;

	info.reg2 = p[0] & 0x03;
	info.reg = (p[1] >> 4) & 0x03;
	info.flags = (p[0] & 0x0c) | (p[1] >> 6);
	ibits = (p[0] >> 2) & 0x03;

	switch (ibits) {
	case 0: /* immediate */
		info.typ = t_value;
		info.val = ((signed char *)p)[1];
		*opleng = 2;
		break;
	case 1:
		info.typ = (p[1] & 0x80) ? t_reg_indirect : t_reg_only;
		*opleng = 2;
		break;
	case 2:
	case 3:
		info.typ = (ibits == 2) ? t_mem_only : t_mem_register;
		info.val = ((p[1] << 8) | p[2]) & 0x0fff;
		*opleng = 3;
		break;
	}
	return info;
}

static short shramdom(void)
{
	static int initialized = 0;

#ifdef _USE_RANDOM_
	if (initialized == 0) {
		unsigned long seed = time(NULL);
		srandom(seed);
		initialized = 1;
	}
	return (random() & 0x7fff);
#else
	if (initialized == 0) {
		unsigned int seed = (unsigned int)time(NULL);
		srand(seed);
		initialized = 1;
	}
	return (rand() & 0x7fff);
#endif
}

static long fetch_data(operand info, int *errcode)
{
	long addr;
	short n;

	*errcode = st_running;
	switch (info.typ) {
	case t_reg_only: /* register direct */
		return regs[info.reg];
	case t_mem_only: /* address */
		addr = info.val;
		break;
	case t_reg_indirect: /* register indirect */
		addr = regs[info.reg];
		break;
	case t_mem_register: /* address + register indirect */
		addr = info.val + regs[info.reg];
		break;
	case t_value: /* literal value */
	default:
		n = info.val;	/* convert into signed short value */
		return n;
	}

	if (addr > MAX_ADDRESS) {
		if (addr == RANDOM_ADDR)
			return shramdom();
		*errcode = st_illegalMemory;
		return 0; /* Error */
	}
	n = ((mem[addr] << 8) | mem[addr + 1]);	/* convert into signed short */
	return n;
}

#define INBUF_SZ	64

static int exec_a_step(int trace)
{
	int opcode;
	int length = 1;
	int status = st_running; /* default */
	int rx, jmpflag, top = 0;
	operand info;
	long data, addr;

	if (pc >= max_addr)
		return st_outOfMem;
	opcode = mem[pc] >> 4;
	switch (opcode) {
	case op_HALT:
		length = 0;
		if (trace)
			print_trace(opcode, NULL);
		status = st_halt;
		break;
	case op_COPY:
		info = inspect_code(&mem[pc], &length);
		if (trace)
			print_trace(opcode, &info);
		if (! IS_STORE(info.flags)) { /* Load-type */
			data = fetch_data(info, &status);
			if (status == st_running)
				regs[info.reg2] = data;
			break;
		}
		/* Store-type */
		addr = 0;
		switch (info.typ) {
		case t_mem_only: /* address */
			addr = info.val;
			break;
		case t_reg_indirect: /* register indirect */
			addr = regs[info.reg];
			break;
		case t_mem_register: /* address + register indirect */
			addr = info.val + regs[info.reg];
			break;
		default: /* t_reg_only || t_value */
			status = st_illegalOperand;
			break;
		}
		if (addr > MAX_ADDRESS)
			status = st_illegalMemory;
		if (status == st_running) {
			mem[addr] = regs[info.reg2] >> 8;
			mem[addr + 1] = regs[info.reg2];
		}
		break;
	case op_ADD:
	case op_SUB:
	case op_MUL:
	case op_DIV:
	case op_CMP:
		info = inspect_code(&mem[pc], &length);
		if (trace)
			print_trace(opcode, &info);
		if (info.typ == t_reg_indirect
				|| info.typ == t_mem_register) {
			status = st_illegalOperand;
			break;
		}
		data = fetch_data(info, &status);
		if (status != st_running)
			break;
		if (opcode == op_CMP) {
			cf = (regs[info.reg2] < data) ? CF_BELOW : CF_NotBELOW;
			break;
		}
		if (opcode == op_DIV && data == 0) {
			status = st_zeroDiv;
			break;
		}
		switch (opcode) {
		case op_ADD: regs[info.reg2] += data;  break;
		case op_SUB: regs[info.reg2] -= data;  break;
		case op_MUL: regs[info.reg2] *= data;  break;
		case op_DIV: regs[info.reg2] /= data;  break;
		}
		break;
	case op_JMP:
	case op_JNB:
	case op_JB:
		length = 2;
		jmpflag = YES;
		switch (opcode) {
		case op_JNB: jmpflag = (cf == CF_NotBELOW); break;
		case op_JB:  jmpflag = (cf == CF_BELOW);    break;
		case op_JMP:
		default: break;
		}
		addr = ((mem[pc] << 8) | mem[pc+1]) & 0x0fff;
		if (trace) {
			info.typ = t_mem_only;
			info.val = addr;
			print_trace(opcode, &info);
		}
		if (jmpflag) {
			pc = addr;
			return status; /* JUMP!! */
		}
		break;
	case op_CALL:
		addr = ((mem[pc] << 8) | mem[pc+1]) & 0x0fff;
		if (trace) {
			info.typ = t_mem_only;
			info.val = addr;
			print_trace(opcode, &info);
		}
		top = (regs[SP] & ~01) - 2;
		if (top <= max_addr) {
			status = st_illegalMemory;
			break;
		}
		pc += 2;	/* length == 2 */
		mem[top] = pc >> 8;
		mem[top+1] = pc & 0xff;
		regs[SP] = top;
		pc = addr;
		return status; /* JUMP!! */
	case op_RTN:
		if (trace)
			print_trace(opcode, NULL);
		top = regs[SP] & ~01;
		if (top >= STACK_BASE) {
			status = st_illegalMemory;
			break;
		}
		pc = (mem[top] << 8) | mem[top+1];
		regs[SP] = top + 2;
		return status; /* JUMP!! */
	case op_PUSH:
	case op_POP:
		rx = mem[pc] & 0x03;
		if (trace) {
			info.reg2 = rx;
			print_trace(opcode, &info);
		}
		top = regs[SP] & ~01;
		if (opcode == op_PUSH) {
			top -= 2;
			if (top <= max_addr) {
				status = st_illegalMemory;
				break;
			}
			mem[top] = regs[rx] >> 8;
			mem[top+1] = regs[rx] & 0xff;
			regs[SP] = top;
		}else { /* POP */
			if (top >= STACK_BASE) {
				status = st_illegalMemory;
				break;
			}
			regs[rx] = (mem[top] << 8) | mem[top+1];
			regs[SP] = top + 2;
		}
		break;
	case op_IN:
		rx = mem[pc] & 0x03;
		if (trace) {
			info.reg2 = rx;
			print_trace(opcode, &info);
		}
		fprintf(stderr, "in ? ");
		for ( ; ; ) {
			char inbuf[INBUF_SZ];
			int x = 0;
			if (fgets(inbuf, INBUF_SZ, stdin) == NULL) {
				status = st_EOF;
				break;
			}
			while (inbuf[x] == ' ' || inbuf[x] == '\t')
				x++;
			if (isdigit(inbuf[x]) || inbuf[x] == '-') {
				regs[rx] = atoi(&inbuf[x]);
				break;
			}
			fprintf(stderr, "input number ? ");
		}
		break;
	case op_OUT:
		rx = mem[pc] & 0x03;
		if (trace) {
			info.reg2 = rx;
			print_trace(opcode, &info);
		}
		printf("out: %d\n", regs[rx]);
		break;
	default:
		if (trace)
			print_trace(opcode, NULL);
		status = st_illegalOpcode;
		break;
	}
	if (status == st_running)
		pc += length;
	return status;
}

static void interrupt(int sig)
{
	signal(SIGINT, SIG_DFL);
	longjmp(jmpenv, 1);
}

/* extern */
void simulate(unsigned char *core, long maxaddress, int trace)
{
	int i, st;

	mem = core;
	max_addr = maxaddress;
	cf = CF_CLEAR;
	pc = 0x00;
	for (i = 0; i < REGISTERS; i++)
		regs[i] = 0;
	regs[SP] = STACK_BASE;
	signal(SIGINT, interrupt);
	if (setjmp(jmpenv) == 0) {
		do {
			st = exec_a_step(trace);
		}while (st == st_running);
		printf(">>> Program terminated. (%s)\n", reason_str[st]);
		signal(SIGINT, SIG_DFL);
	}else {
		printf("!!>>> Program is interrupted. IP=%ld\n", pc);
	}
}
