// SPDX-FileCopyrightText: 2012 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

/* copypasted from http://pastie.org/3732465 */
/* adapted by pancake // public license */
/* known bugs: some (%s, NULL) happen */

#include <stdio.h>
#include <stdlib.h>
#include "dcpu16.h"

struct op_code {
	ut8 opcode;
};

struct op_basic {
	ut8 opcode;
	ut8 a_type;
	ut8 b_type;

	ut16 a;
	ut16 b;
};

struct op_nbasic {
	ut8 __empty;
	ut8 a_type;
	ut8 opcode;
	ut16 a;
};

typedef union {
	struct op_code code;
	struct op_basic b;
	struct op_nbasic n;
} op;

static const int opCycle[] = {
	0, 1, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 2, 2, 2, 2
};
static const int opCycleB[] = { 0, 2 };

static const char *opName[] = {
	"", "set",
	"add", "sub", "mul", "div", "mod",
	"shl", "shr", "and", "bor", "xor",
	"ife", "ifn", "ifg", "ifb"
};

static const char *opNameB[] = { "reserved", "jsr" };

static const char *regs[] = {
	"a", "b", "c", "x", "y", "z", "i", "j",
	"pop", "peek", "push", "sp", "pc", "o"
};

static inline int needWord(ut8 type) {
	return ((type <= 0x17) && (type > 0x0f)) || (type == 0x1e) || (type == 0x1f);
}

static int valPrint(char *out, size_t size_out, ut8 type, ut16 value) {
	if (type <= 0x07)
		return snprintf(out, size_out, "%s", regs[type]);
	if (type <= 0x0f)
		return snprintf(out, size_out, "[%s]", regs[type - 0x08]);
	if (type <= 0x17)
		return snprintf(out, size_out, "[%s + %#hx]", regs[type - 0x10], value);
	if (type <= 0x1d)
		return snprintf(out, size_out, "%s", regs[type - 0x18 + 0x08]);
	if (type == 0x1e)
		return snprintf(out, size_out, "[%#hx]", value);
	if (type == 0x1f)
		return snprintf(out, size_out, "%#hx", value);
	return snprintf(out, size_out, "%#hx", (short)(type - 0x20));
}

static int instrPrint(char *out, size_t size_out, const op *o) {
	char arg[32], arg2[32];
	if (o->code.opcode == 0) {
		valPrint(arg, sizeof(arg), o->n.a_type, o->n.a);
		if (o->n.opcode > 1) {
			strncpy(out, "invalid", size_out);
			out[size_out - 1] = 0;
			return strlen(out);
		}
		return snprintf(out, size_out, "%s %s",
			opNameB[o->n.opcode], arg);
	}
	valPrint(arg, sizeof(arg), o->b.a_type, o->b.a);
	valPrint(arg2, sizeof(arg2), o->b.b_type, o->b.b);
	return snprintf(out, size_out, "%s %s, %s", opName[o->b.opcode], arg, arg2);
}

static int instrGet(ut16 in, op *o, ut16 a, ut16 b) {
	int ret = 1;
	o->code.opcode = in & 0xF;
	if (!(o->code.opcode = in & 0xF)) {
		/* Non basic op code */
		o->n.opcode = (in >> 4) & 0x3F;
		o->n.a_type = (in >> 10) & 0x3F;

		if (needWord(o->n.a_type)) {
			o->n.a = a;
			ret++;
		}
	} else {
		o->b.a_type = (in >> 4) & 0x3F;
		o->b.b_type = (in >> 10) & 0x3F;
		if (needWord(o->b.a_type)) {
			o->b.a = a;
			ret++;
			if (needWord(o->b.b_type)) {
				o->b.b = b;
				ret++;
			}
		} else if (needWord(o->b.b_type)) {
			o->b.b = a;
			ret++;
		}
	}
	return ret;
}

static int instrGetCycles(const op *o) {
	if (o->code.opcode == 0)
		return opCycleB[o->n.opcode] + needWord(o->n.a_type);
	return opCycle[o->b.opcode] + needWord(o->b.a_type) + needWord(o->b.b_type);
}

int dcpu16_disasm(char *out, size_t size_out, const ut16 *inp, int len, int *cost) {
	op o = { { 0 } };
	int delta = instrGet(inp[0], &o, inp[1], inp[2]);
	if (cost)
		*cost = instrGetCycles(&o) + ((o.b.opcode >= 0xc) ? 1 : 0);
	instrPrint(out, size_out, &o);
	// ind = (o.b.opcode >= 0xC);
	return delta << 1;
}
