/* 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 <r_types.h>
#include <r_util/r_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);
}

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[128];
	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, ")");
	}

#if 0
	/* XXX quirks */
	if (!rel && in->in_mode == DATA_MODE_IMMEDIATE
	    && ((d->d_operand & 0xff00) == 0x7F00) && d->d_operand & 0x80)
		s->s_ff_quirk = 1;

	if (rel && !s->s_prefix && d->d_operand == 0x7F) {
		if (s->s_nopd) {
			printf("w00t\n");
			strcpy(s->s_nopd->d_asm, "nop");
		}
		printf("Warning: fucking up a branch %x\n", d->d_off);
		decode_unknown(s, d);
	}
#endif
	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) {
			R_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);
		R_RETURN_IF_FAIL (!l->l_next);
	}

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