/* sorbo '07 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include "dis.h"

static struct state _state;

#include <rz_types.h>
#include <rz_util/rz_assert.h>

static inline struct state *get_state(void) {
	memset(&_state, 0, sizeof(struct state));
	return &_state;
}

static uint16_t i2u16(struct instruction *in) {
	return *((uint16_t *)in);
}

#if 0
static void output(struct state *s, char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	cons_printf(fmt, ap);
	//vfprintf(s->s_out, fmt, ap);
	va_end(ap);
}
#endif

static void decode_unknown(struct state *s, struct directive *d) {
#if 0
	printf("Opcode 0x%x reg %d mode %d operand 0x%x",
	       in->in_opcode, in->in_reg, in->in_mode, in->in_operand);
#endif
	sprintf(d->d_asm, "DC 0x%4x", i2u16(&d->d_inst));
}

static int decode_fixed(struct state *s, struct directive *d) {
	*d->d_asm = '\0';
	switch (i2u16(&d->d_inst)) {
	case INST_NOP:
		if (s->s_prefix) {
			return 0;
		}
		s->s_nop++;
		strcpy(d->d_asm, "nop");
		break;
	case INST_BRK: strcpy(d->d_asm, "brk"); break;
	case INST_SLEEP: strcpy(d->d_asm, "sleep"); break;
	case INST_SIF: strcpy(d->d_asm, "sif"); break;
	case INST_BC: strcpy(d->d_asm, "bc"); break;
	case INST_BRXL: strcpy(d->d_asm, "brxl"); break;
	case INST_U:
		strcpy(d->d_asm, "");
		s->s_u = 1;
		break;
	case INST_RTS: strcpy(d->d_asm, "rts"); break;
	}
	return d->d_asm[0] != 0;
}

static char *regname(int reg) {
	switch (reg) {
	case REG_AH: return "AH";
	case REG_AL: return "AL";
	case REG_X: return "X";
	case REG_Y: return "Y";
	}
	return NULL;
}

static int get_num(int num, int shift) {
	char x = (char)((num >> shift) & 0xff);
	return (int)(x << shift);
}

static int get_operand(struct state *s, struct directive *d) {
	int total = get_num(d->d_inst.in_operand, 0);
	if (s->s_prefix)
		total += get_num(s->s_prefix_val, 8);
	if (s->s_prefix == 2)
		total += get_num(s->s_prefix_val, 16);
	return total;
}

static int decode_known(struct state *s, struct directive *d) {
	char *op = NULL;
	char *regn = NULL;
	int reg = 0;
	int ptr = 1;
	int idx = 1;
	int imm = 0;
	int rel = 0;
	char fmt[16];
	char tmp[128];
	int fmtsz;
	int branch = 0;
	struct instruction *in = &d->d_inst;
	//	int operand;
	char *sign = "";
	int rti = 0;

	switch (in->in_opcode) {
	case 0:
		if (in->in_reg == 0 && in->in_mode == 0) {
			if (s->s_prefix == 0)
				s->s_prefix_val = 0;
			s->s_prefix++;

			if (s->s_prefix == 2)
				s->s_prefix_val <<= 8;
#if 0
			/* XXX we need to look ahead more to see if we're
			 * getting a branch instruction */
			if (s->s_nopd && in->in_operand == 0x80)
				strcpy(s->s_nopd->d_asm, "");
#endif
			s->s_prefix_val |= in->in_operand << 8;

			strcpy(d->d_asm, "");
			return 1;
		}

		switch (i2u16(in) & 0xf) {
		case 1:
			op = "st";
			regn = "FLAGS";
			break;
		case 2:
			op = "st";
			regn = "UX";
			break;
		case 3:
			op = "st";
			regn = "UY";
			break;
		case 5:
			op = "ld";
			regn = "FLAGS";
			break;
		case 6:
			op = "ld";
			regn = "UX";
			break;
		case 7:
			op = "ld";
			regn = "UY";
			break;
		case 0xa:
			op = "st";
			regn = "XH";
			break;
		case 0xd:
			op = "rti";
			regn = "";
			rti = 1;
			break;
		case 0xe:
			op = "ld";
			regn = "XH";
			break;
		}
		break;

	case 1:
		op = "ld";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 2:
		if (in->in_mode == DATA_MODE_IMMEDIATE) {
			op = "print";
			imm = 1;
			reg = 1;
		} else {
			op = "st";
			reg = 1;
			ptr = 1;
			idx = 1;
		}
		break;
	case 3:
		op = "add";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 4:
		op = "addc";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 5:
		op = "sub";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 6:
		op = "subc";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 7:
		op = "nadd";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 8:
		op = "cmp";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 0x9:
		switch (in->in_reg) {
		case 0:
			if (s->s_u)
				op = "umult";
			else
				op = "smult";
			imm = 1;
			s->s_u = 0;
			idx = 1;
			ptr = 1;
			break;
		case 1:
			if (s->s_u)
				op = "udiv";
			else
				op = "sdiv";
			s->s_u = 0;
			imm = 1;
			break;
		case 2:
			op = "tst";
			imm = 1;
			ptr = 1;
			idx = 1;
			break;
		case 3:
			branch = 1;
			op = "bsr";
			ptr = 1;
			idx = 1;
			if (in->in_mode == ADDR_MODE_RELATIVE)
				rel = 1;
			break;
		}
		break;
	case 0xa:
		switch (in->in_reg) {
		case 0:
			op = "asl";
			imm = 1;
			break;
		case 1:
			if (s->s_u)
				op = "lsr";
			else
				op = "asr";
			s->s_u = 0;
			imm = 1;
			idx = 1;
			ptr = 1;
			break;
		case 2:
			op = "rol";
			imm = 1;
			break;
		case 3:
			op = "ror";
			imm = 1;
			break;
		}
		break;

	case 0xb:
		op = "or";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 0xc:
		op = "and";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 0xd:
		op = "xor";
		reg = 1;
		ptr = 1;
		idx = 1;
		imm = 1;
		break;
	case 0xe:
		branch = 1;
		if (in->in_mode == ADDR_MODE_RELATIVE)
			rel = 1;
		switch (in->in_reg) {
		case 0:
			op = "bra";
			ptr = 1;
			idx = 1;
#if 0
			if (s->s_nopd) {
				op = "bra2"; /* XXX need bra3 support */
				strcpy(s->s_nopd->d_asm, "");
			}
#endif
			break;

		case 1:
			op = "blt"; /* yummy */
			break;
		case 2:
			op = "bpl";
			break;
		case 3:
			op = "bmi";
			break;
		}
		break;
	case 0xf:
		branch = 1;
		if (in->in_mode == ADDR_MODE_RELATIVE)
			rel = 1;
		switch (in->in_reg) {
		case 0: op = "bne"; break;
		case 1: op = "beq"; break;
		case 2: op = "bcc"; break;
		case 3: op = "bcs"; break;
		}
		break;
	}

	if (!op)
		return 0;

	if (ptr && in->in_mode == DATA_MODE_IMMEDIATE)
		ptr = 0;

	if (branch && in->in_mode == ADDR_MODE_X_RELATIVE)
		ptr = 0;

	if (idx && (!(in->in_mode & 2)))
		idx = 0;

	if (regn) {
		ptr = 1;
		idx = 1;
		reg = 1;
	}

	sprintf(d->d_asm, "%s ", op);
	if (reg) {
		char *r = regn;
		if (!r) {
			r = regname(in->in_reg);
		}
		if (r && !rti) {
			if ((strlen(r) + 4 + strlen(d->d_asm)) < sizeof(d->d_asm)) {
				strcat(d->d_asm, r);
				strcat(d->d_asm, ", ");
			}
		}
	}
	if (ptr) {
		strcat(d->d_asm, "@");
		rel = 0;
	} else if (imm) {
		strcat(d->d_asm, "#");
	}
	if (idx && ptr) {
		strcat(d->d_asm, "(");
	}

	d->d_prefix = s->s_prefix;
//	d->d_operand = get_operand(s, d);
#if 1
	if ((branch && idx) || rti) {
		d->d_operand = get_operand(s, d);
		if (d->d_operand < 0) {
			d->d_operand *= -1;
			sign = "-";
		}
	} else {
		d->d_operand = s->s_prefix_val | in->in_operand;
		if (d->d_operand & 0x80) {
			if (d->d_prefix) {
				if (!rel)
					d->d_operand -= 0x100;
			} else
				d->d_operand |= 0xff00;
		}
	}
#endif
#if 0
	operand = d->d_operand;
	if (operand < 0)
		operand *= -1;
#endif
	fmtsz = 4;
	if (d->d_operand & 0xff0000)
		fmtsz += 2;

	// can be cleaned, no need to fmtsz
	snprintf(fmt, sizeof(fmt), "%s0x%%.%dX", sign, fmtsz);
	snprintf(tmp, sizeof(tmp), fmt, d->d_operand);
	strcat(d->d_asm, tmp);

	if (idx) {
		char *r = in->in_mode == DATA_MODE_INDEXED_X ? "X" : "Y";
		if (regn)
			r = "Y";
		snprintf(tmp, sizeof(tmp), ", %s", r);
		strcat(d->d_asm, tmp);
		if (ptr)
			strcat(d->d_asm, ")");
	}

	return 1;
}

static void xap_decode(struct state *s, struct directive *d) {
	int prefix = s->s_prefix;
	if (!decode_fixed(s, d))
		if (!decode_known(s, d))
			decode_unknown(s, d);
	if (s->s_prefix == prefix)
		s->s_prefix_val = s->s_prefix = 0;
}

static int read_bin(struct state *s, struct directive *d) {
	memcpy(&d->d_inst, s->s_buf, sizeof(d->d_inst));
	d->d_off = s->s_off++;
	return 1;
}

static inline struct directive *next_inst(struct state *s) {
	int rd;
	struct directive *d = malloc(sizeof(*d));
	if (!d) {
		perror("malloc()");
		return NULL;
	}
	memset(d, 0, sizeof(*d));
#if 0
	if (s->s_format)
		rd = read_text(s, d);
	else
#endif
	rd = read_bin(s, d);
	if (!rd) {
		free(d);
		return NULL;
	}

	return d;
}

#if 0
static void own(struct state *s)
{
	struct directive *d;
	struct directive *last = &s->s_dirs;
	struct label *l;
	int flush = 0;
	char fname[128];
	char *fnamep;

	snprintf(fname, sizeof(fname), "%s", s->s_fname);
	fnamep = strchr(fname, '.');
	if (fnamep)
		*fnamep = 0;
	output(s, "\tMODULE %s\n"
	          "\t.CODE\n"
		  "\t.LARGE\n"
	          "\n", fname);

	/* decode instructions */
	s->s_off = 0;
	while ((d = next_inst(s))) {
		xap_decode(s, d);

		if (s->s_ff_quirk) {
			strcpy(last->d_asm, "DC\t0x8000");

			sprintf(d->d_asm, "DC\t0x%.4x", i2u16(&d->d_inst));
			s->s_ff_quirk = 0;
		}

		if (s->s_nopd) {
			last->d_next = s->s_nopd;
			last = s->s_nopd;
			s->s_nopd = NULL;
			s->s_nop = 0;
		}

		if (s->s_nop) {
			rz_return_if_fail (!s->s_nopd);
			s->s_nopd = d;
		} else {
			last->d_next = d;
			last = d;
		}

#if 1
		if (flush++ > 10000) {
			printf("@0x%.6x\r", d->d_off);
			fflush(stdout);
			flush = 0;
		}
#endif
	}
	if (s->s_nopd)
		last->d_next = s->s_nopd;
	printf("\n");

	/* print them */
	d = s->s_dirs.d_next;
	l = s->s_labels.l_next;
	while (d) {

		/* print any labels first */
		while (l) {
			if (l->l_off > d->d_off)
				break;

			print_label(s, l);
			l = l->l_next;
		}

		add_comment(s, d);
		output(s, "\t%s\n", d->d_asm);

		d = d->d_next;
	}
	if (l) {
		print_label(s, l);
		rz_return_if_fail (!l->l_next);
	}

	output(s, "\n\tENDMOD\n");
}
#endif
