/*
 * TMS320 disassembly engine
 *
 * Written by Ilya V. Matveychikov <i.matveychikov@milabs.ru>
 *
 * Distributed under LGPL
 */

#include <stdio.h>
#include <string.h>

/* public headers */
#include <r_util.h>
#include <r_types.h>
#include <sdb.h>


/* private headers */
#include "tms320_p.h"
#include "tms320_dasm.h"

#include "c55x_plus/c55plus.h"

/*
 * TMS320 disassembly engine implementation
 */

int run_f_list(tms320_dasm_t * dasm) {
	ut32 temp;
	insn_flag_t * flag;

	if (!dasm->insn->f_list)
		return 1;

	for (flag = dasm->insn->f_list; !f_list_last(flag); flag++) {
		switch (flag->v) {
		case TMS320_FLAG_E:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, E, temp);
			break;
		case TMS320_FLAG_R:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, R, temp);
			break;
		case TMS320_FLAG_U:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, U, temp);
			break;
		case TMS320_FLAG_u:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, u, temp);
			break;
		case TMS320_FLAG_g:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, g, temp);
			break;
		case TMS320_FLAG_r:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, r, temp);
			break;
		case TMS320_FLAG_t:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, t, temp);
			break;

		case TMS320_FLAG_k3:
			temp = get_bits(dasm->opcode64, flag->f, 3);
			set_field_value(dasm, k3, temp);
			break;
		case TMS320_FLAG_k4:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, k4, temp);
			break;
		case TMS320_FLAG_k5:
			temp = get_bits(dasm->opcode64, flag->f, 5);
			set_field_value(dasm, k5, temp);
			break;
		case TMS320_FLAG_k6:
			temp = get_bits(dasm->opcode64, flag->f, 6);
			set_field_value(dasm, k6, temp);
			break;
		case TMS320_FLAG_k8:
			temp = get_bits(dasm->opcode64, flag->f, 8);
			set_field_value(dasm, k8, temp);
			break;
		case TMS320_FLAG_k12:
			temp = get_bits(dasm->opcode64, flag->f, 12);
			set_field_value(dasm, k12, temp);
			break;
		case TMS320_FLAG_k16:
			temp = get_bits(dasm->opcode64, flag->f, 16);
			set_field_value(dasm, k16, temp);
			break;

		case TMS320_FLAG_l1:
			temp = get_bits(dasm->opcode64, flag->f, 1);
			set_field_value(dasm, l1, temp);
			break;
		case TMS320_FLAG_l3:
			temp = get_bits(dasm->opcode64, flag->f, 3);
			set_field_value(dasm, l3, temp);
			break;
		case TMS320_FLAG_l7:
			temp = get_bits(dasm->opcode64, flag->f, 7);
			set_field_value(dasm, l7, temp);
			break;
		case TMS320_FLAG_l16:
			temp = get_bits(dasm->opcode64, flag->f, 16);
			set_field_value(dasm, l16, temp);
			break;

		case TMS320_FLAG_K8:
			temp = get_bits(dasm->opcode64, flag->f, 8);
			set_field_value(dasm, K8, temp);
			break;
		case TMS320_FLAG_K16:
			temp = get_bits(dasm->opcode64, flag->f, 16);
			set_field_value(dasm, K16, temp);
			break;

		case TMS320_FLAG_L7:
			temp = get_bits(dasm->opcode64, flag->f, 7);
			set_field_value(dasm, L7, temp);
			break;
		case TMS320_FLAG_L8:
			temp = get_bits(dasm->opcode64, flag->f, 8);
			set_field_value(dasm, L8, temp);
			break;
		case TMS320_FLAG_L16:
			temp = get_bits(dasm->opcode64, flag->f, 16);
			set_field_value(dasm, L16, temp);
			break;

		case TMS320_FLAG_P8:
			temp = get_bits(dasm->opcode64, flag->f, 8);
			set_field_value(dasm, P8, temp);
			break;
		case TMS320_FLAG_P24:
			temp = get_bits(dasm->opcode64, flag->f, 24);
			set_field_value(dasm, P24, temp);
			break;

		case TMS320_FLAG_D16:
			temp = get_bits(dasm->opcode64, flag->f, 16);
			set_field_value(dasm, D16, temp);
			break;

		case TMS320_FLAG_SHFT:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, SHFT, temp);
			break;
		case TMS320_FLAG_SHIFTW:
			temp = get_bits(dasm->opcode64, flag->f, 6);
			set_field_value(dasm, SHIFTW, temp);
			break;

		case TMS320_FLAG_CCCCCCC:
			temp = get_bits(dasm->opcode64, flag->f, 7);
			set_field_value(dasm, CCCCCCC, temp);
			break;
		case TMS320_FLAG_AAAAAAAI:
			temp = get_bits(dasm->opcode64, flag->f, 8);
			set_field_value(dasm, AAAAAAAI, temp);
			break;

		case TMS320_FLAG_uu:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, uu, temp);
			break;
		case TMS320_FLAG_cc:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, cc, temp);
			break;
		case TMS320_FLAG_ss:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, ss, temp);
			break;
		case TMS320_FLAG_dd:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, dd, temp);
			break;
		case TMS320_FLAG_mm:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, mm, temp);
			break;
		case TMS320_FLAG_vv:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, vv, temp);
			break;
		case TMS320_FLAG_tt:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			set_field_value(dasm, tt, temp);
			break;

		case TMS320_FLAG_XSSS:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, XSSS, temp);
			break;
		case TMS320_FLAG_XDDD:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, XDDD, temp);
			break;
		case TMS320_FLAG_FSSS:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, FSSS, temp);
			break;
		case TMS320_FLAG_FDDD:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, FDDD, temp);
			break;
		case TMS320_FLAG_XACS:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, XACS, temp);
			break;
		case TMS320_FLAG_XACD:
			temp = get_bits(dasm->opcode64, flag->f, 4);
			set_field_value(dasm, XACD, temp);
			break;

		case TMS320_FLAG_SS:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			if (!field_valid(dasm, SS)) {
				set_field_value(dasm, SS, temp);
			} else {
				set_field_value(dasm, SS2, temp);
			}
			break;
		case TMS320_FLAG_DD:
			temp = get_bits(dasm->opcode64, flag->f, 2);
			if (!field_valid(dasm, DD)) {
				set_field_value(dasm, DD, temp);
			} else {
				set_field_value(dasm, DD2, temp);
			}
			break;

		case TMS320_FLAG_XXX:
			temp = get_bits(dasm->opcode64, flag->f, 3);
			set_field_value(dasm, Xmem_reg, temp);
			break;
		case TMS320_FLAG_MMM:
			temp = get_bits(dasm->opcode64, flag->f, 3);
			if (!field_valid(dasm, Xmem_mmm)) {
				set_field_value(dasm, Xmem_mmm, temp);
			} else {
				set_field_value(dasm, Ymem_mmm, temp);
			}
			break;
		case TMS320_FLAG_Y:
			temp = get_bits(dasm->opcode64, flag->f, 1) << 0;
			if (!field_valid(dasm, Ymem_reg)) {
				set_field_value(dasm, Ymem_reg, temp);
			} else {
				field_value(dasm, Ymem_reg) |= temp;
			}
			break;
		case TMS320_FLAG_YY:
			temp = get_bits(dasm->opcode64, flag->f, 2) << 1;
			if (!field_valid(dasm, Ymem_reg)) {
				set_field_value(dasm, Ymem_reg, temp);
			} else {
				field_value(dasm, Ymem_reg) |= temp;
			}
			break;

		default:
			printf("TODO: unknown opcode flag %02x\n", flag->v);
			return 0;
		}
	}

	return 1;
}

int run_m_list(tms320_dasm_t * dasm)
{
	insn_mask_t * mask;

	if (!dasm->insn->m_list)
		return 1;

	for (mask = dasm->insn->m_list; !m_list_last(mask); mask++) {
		/* match bits in range [f, f + n] with mask's value */
		if (get_bits(dasm->opcode64, mask->f, mask->n) != mask->v)
			return 0;
	}

	return 1;
}

int vreplace(char * string, const char * token, const char * fmt, va_list args)
{
	char data[64];
	char * pos;

	pos = strstr(string, token);
	if (!pos)
		return 0;

	vsnprintf(data, sizeof(data), fmt, args);

	memmove(pos + strlen(data), pos + strlen(token), strlen(pos + strlen(token)) + 1);
	memmove(pos, data, strlen(data));

	return 1;
}

int replace(char * string, const char * token, const char * fmt, ...)
{
	int result;
	va_list args;

	va_start(args, fmt);
	result = vreplace(string, token, fmt, args);
	va_end(args);

	return result;
}

void substitute(char * string, const char * token, const char * fmt, ...)
{
	int result;
	va_list args;

	do {
		va_start(args, fmt);
		result = vreplace(string, token, fmt, args);
		va_end(args);
	} while (result);
}

const char * get_xreg_str(ut8 key, char * str)
{
	static const char * table[16] = {
		"ac0", "ac1", "ac2", "ac3", "xsp", "xssp", "xdp", "xcdp",
		"xar0", "xar1", "xar2", "xar3", "xar4", "xar5", "xar6", "xar7",
	};

	return table[ key & 15 ];
}

const char * get_freg_str(ut8 key, char * str)
{
	static const char * table[16] = {
		"ac0", "ac1", "ac2", "ac3", "t0", "t1", "t2", "t3",
		"ar0", "ar1", "ar2", "ar3", "ar4", "ar5", "ar6", "ar7",
	};

	return table[ key & 15 ];
}

const char * get_swap_str(ut8 key, char * str)
{
	switch (key) {
	case 0: return "swap ac0, ac2";
	case 1: return "swap ac1, ac3";
	case 4: return "swap t0, t2";
	case 5: return "swap t1, t3";
	case 8: return "swap ar0, ar2";
	case 9: return "swap ar1, ar3";
	case 12: return "swap ar4, t0";
	case 13: return "swap ar5, t1";
	case 14: return "swap ar6, t2";
	case 15: return "swap ar7, t3";
	case 16: return "swapp ac0, ac2";
	case 20: return "swapp t0, t2";
	case 24: return "swapp ar0, ar2";
	case 28: return "swapp ar4, t0";
	case 30: return "swapp ar6, t2";
	case 44: return "swap4 ar4, t0";
	case 56: return "swap ar0, ar1";
	}

	return "invalid";
}

const char * get_relop_str(ut8 key, char * str)
{
	static const char * table[] = {
		"==", "<", ">=", "!="
	};

	return table[ key & 3 ];
}

const char * get_cond_str(ut8 key, char * str)
{
	/* 000 FSSS ... 101 FSSS */
	if ((key >> 4) <= 5) {
		static const char * op[6] = { "==", "!=", "<", "<=", ">", ">=" };
		sprintf(str, "%s %s 0", get_freg_str(key & 15, NULL), op[(key >> 4) & 7]);
		return str;
	}

	/* 110 00SS */
	if ((key >> 2) == 0x18) {
		sprintf(str, "overflow(ac%d)", key & 3);
		return str;
	}

	/* 111 00SS */
	if ((key >> 2) == 0x1C) {
		sprintf(str, "!overflow(ac%d)", key & 3);
		return str;
	}

	switch (key) {
	case 0x64: return "tc1";
	case 0x65: return "tc2";
	case 0x66: return "carry";
	case 0x74: return "!tc1";
	case 0x75: return "!tc2";
	case 0x76: return "!carry";
		/* "&" operation */
	case 0x68: return "tc1 & tc2";
	case 0x69: return "tc1 & !tc2";
	case 0x6A: return "!tc1 & tc2";
	case 0x6B: return "!tc1 & !tc2";
		/* "|" operation */
	case 0x78: return "tc1 | tc2";
	case 0x79: return "tc1 | !tc2";
	case 0x7A: return "!tc1 | tc2";
	case 0x7B: return "!tc1 | !tc2";
		/* "^" operation */
	case 0x7C: return "tc1 ^ tc2";
	case 0x7D: return "tc1 ^ !tc2";
	case 0x7E: return "!tc1 ^ tc2";
	case 0x7F: return "!tc1 ^ !tc2";
	}

	return "invalid";
}

const char * get_v_str(ut8 key, char * str)
{
	static const char * table[2] = {
		"carry", "tc2",
	};

	return table[ key & 1 ];
}

const char * get_t_str(ut8 key, char * str)
{
	static const char * table[2] = {
		"tc1", "tc2",
	};

	return table[ key & 1 ];
}

const char * get_cmem_str(ut8 key, char * str)
{
	static const char * table[4] = {
		"*cdp", "*cdp+", "*cdp-", "*(cdp+t0)",
	};

	return table[ key & 3 ];
}

const char * get_smem_str(ut8 key, char * str)
{
	// direct memory

	if ((key & 0x01) == 0) {
#ifdef IDA_COMPATIBLE_MODE
		sprintf(str, "*sp(#%Xh)", key >> 1);
#else
		sprintf(str, "@0x%02X", key >> 1);
#endif
		return str;
	}

	// indirect memory

	switch (key) {
	case 0x11: return "abs16(k16)";
	case 0x31: return "*(k23)";
	case 0x51: return "port(k16)";
	case 0x71: return "*cdp";
	case 0x91: return "*cdp+";
	case 0xB1: return "*cdp-";
	case 0xD1: return "*cdp(K16)";
	case 0xF1: return "*+cdp(K16)";
	}

	switch (key & 0x1F) {
	case 0x01: return "*ARn";
	case 0x03: return "*ARn+";
	case 0x05: return "*ARn-";
		// TODO:
		//	C54CM:0 => *(ARn + T0)
		//	C54CM:1 => *(ARn + AR0)
	case 0x07: return "*(ARn + t0)";
		// TODO:
		//	C54CM:0 => *(ARn - t0)
		//	C54CM:1 => *(ARn - AR0)
	case 0x09: return "*(ARn - t0)";
		// TODO:
		//	C54CM:0 => *ARn(t0)
		//	C54CM:1 => *ARn(AR0)
	case 0x0B: return "*ARn(t0)";
	case 0x0D: return "*ARn(k16)";
	case 0x0F: return "*+ARn(k16)";
		// TODO:
		//	ARMS:0 => *(ARn + T1)
		//	ARMS:1 => *ARn(short(1))
	case 0x13: return "*(ARn + t1)";
		// TODO:
		//	ARMS:0 => *(ARn - T1)
		//	ARMS:1 => *ARn(short(2))
	case 0x15: return "*(ARn - t1)";
		// TODO:
		//	ARMS:0 => *ARn(T1)
		//	ARMS:1 => *ARn(short(3))
	case 0x17: return "*ARn(t1)";
		// TODO:
		//	ARMS:0 => *+ARn
		//	ARMS:1 => *ARn(short(4))
	case 0x19: return "*+ARn";
		// TODO:
		//	ARMS:0 => *-ARn
		//	ARMS:1 => *ARn(short(5))
	case 0x1B: return "*-ARn";
		// TODO:
		//	ARMS:0 => *(ARn + t0b)
		//	ARMS:1 => *ARn(short(6))
	case 0x1D: return "*(ARn + t0b)";
		// TODO:
		//	ARMS:0 => *(arn - t0b)
		//	ARMS:1 => *arn(short(7))
	case 0x1F: return "*(ARn - t0b)";
	}

	return "invalid";
}

const char * get_mmm_str(ut8 key, char * str)
{
	switch (key & 7) {
	default:
	case 0x00: return "*ARn";
	case 0x01: return "*ARn+";
	case 0x02: return "*ARn-";
		// TODO:
		//	C54CM:0 => *(ARn + T0)
		//	C54CM:1 => *(ARn + AR0)
	case 0x03: return "*(ARn + t0)";
	case 0x04: return "*(ARn + t1)";
		// TODO:
		//	C54CM:0 => *(ARn - t0)
		//	C54CM:1 => *(ARn - AR0)
	case 0x05: return "*(ARn - t0)";
	case 0x06: return "*(ARn - t1)";
		// TODO:
		//	C54CM:0 => *ARn(T0)
		//	C54CM:1 => *ARn(AR0)
	case 0x07: return "*ARn(t0)";
	};
}

/*
 * syntax decoders
 */

void decode_bits(tms320_dasm_t * dasm)
{
	// rounding
	if (field_valid(dasm, R))
		substitute(dasm->syntax, "[r]", "%s", field_value(dasm, R) ? "r" : "");

	// unsigned
	if (field_valid(dasm, u))
		substitute(dasm->syntax, "[u]", "%s", field_value(dasm, u) ? "u" : "");

	// 40 keyword
	if (field_valid(dasm, g))
		substitute(dasm->syntax, "[40]", "%s", field_value(dasm, g) ? "40" : "");

	// T3 update
	if (field_valid(dasm, U))
		substitute(dasm->syntax, "[T3 = ]", "%s", field_value(dasm, U) ? "t3=" : "");
}

void decode_braces(tms320_dasm_t * dasm)
{
	char * pos;

	pos = strstr(dasm->syntax, "[(saturate]");
	if (pos) {
		replace(pos, "[)", ")[");
		replace(dasm->syntax, "[(saturate]", "%s", "(saturate");
	}

	if (field_valid(dasm, R)) {
		pos = strstr(dasm->syntax, "[rnd(]");
		if (pos) {
			replace(pos, "[)", "%s", field_value(dasm, R) ? ")[" : "[");
			replace(dasm->syntax, "[rnd(]", "%s", field_value(dasm, R) ? "rnd(" : "");
		}
	}

	if (field_valid(dasm, u)) {
		pos = strstr(dasm->syntax, "[uns(]");
		if (pos) {
			replace(pos, "[)", "%s", field_value(dasm, u) ? ")[" : "[");
			replace(dasm->syntax, "[uns(]", "%s", field_value(dasm, u) ? "uns(" : "");
		}
	}

	if (field_valid(dasm, uu)) {
		bool parallel = !!strstr (dasm->syntax, "::");

		// first
		replace(dasm->syntax, "[uns(]", "%s", field_value(dasm, uu) & 2 ? "uns(" : "");
		replace(dasm->syntax, "[)]", "%s", field_value(dasm, uu) & 2 ? ")" : "");

		if (parallel) {
			replace(dasm->syntax, "[uns(]", "%s", field_value(dasm, uu) & 2 ? "uns(" : "");
			replace(dasm->syntax, "[)]", "%s", field_value(dasm, uu) & 2 ? ")" : "");
		}

		// second
		replace(dasm->syntax, "[uns(]", "%s", field_value(dasm, uu) & 1 ? "uns(" : "");
		replace(dasm->syntax, "[)]", "%s", field_value(dasm, uu) & 1 ? ")" : "");

		if (parallel) {
			replace(dasm->syntax, "[uns(]", "%s", field_value(dasm, uu) & 1 ? "uns(" : "");
			replace(dasm->syntax, "[)]", "%s", field_value(dasm, uu) & 1 ? ")" : "");
		}
	}

	// remove rudiments

	substitute(dasm->syntax, "[]", "%s", "");
}

void decode_constants(tms320_dasm_t * dasm)
{
	// signed constant

	if (field_valid(dasm, K8))
		substitute(dasm->syntax, "K8", "0x%02X", field_value(dasm, K8));
	if (field_valid(dasm, K16))
		substitute(dasm->syntax, "K16", "0x%04X", be16(field_value(dasm, K16)));

	// unsigned constant

	if (field_valid(dasm, k4))
		substitute(dasm->syntax, "K4", "0x%01X", field_value(dasm, k4));
	if (field_valid(dasm, k5))
		substitute(dasm->syntax, "k5", "0x%02X", field_value(dasm, k5));
	if (field_valid(dasm, k8))
		substitute(dasm->syntax, "k8", "0x%02X", field_value(dasm, k8));

	if (field_valid(dasm, k12))
		substitute(dasm->syntax, "k12", "0x%03X", be16(field_value(dasm, k12)));
	if (field_valid(dasm, k16))
		substitute(dasm->syntax, "k16", "0x%04X", be16(field_value(dasm, k16)));

	if (field_valid(dasm, k4) && field_valid(dasm, k3))
		substitute(dasm->syntax, "k7", "0x%02X", (field_value(dasm, k3) << 4) | field_value(dasm, k4));
	if (field_valid(dasm, k4) && field_valid(dasm, k5))
		substitute(dasm->syntax, "k9", "0x%03X", (field_value(dasm, k5) << 4) | field_value(dasm, k4));
	if (field_valid(dasm, k4) && field_valid(dasm, k8))
		substitute(dasm->syntax, "k12", "0x%03X", (field_value(dasm, k8) << 4) | field_value(dasm, k4));

	// dasm address label

	if (field_valid(dasm, D16))
		substitute(dasm->syntax, "D16", "0x%04X", be16(field_value(dasm, D16)));

	// immediate shift value

	if (field_valid(dasm, SHFT))
		substitute(dasm->syntax, "#SHFT", "0x%01X", field_value(dasm, SHFT));
	if (field_valid(dasm, SHIFTW))
		substitute(dasm->syntax, "#SHIFTW", "0x%02X", field_value(dasm, SHIFTW));
}

void decode_addresses(tms320_dasm_t * dasm)
{
	// program address label

	if (field_valid(dasm, L7))
		substitute(dasm->syntax, "L7", "0x%02X", field_value(dasm, L7));
	if (field_valid(dasm, L8))
		substitute(dasm->syntax, "L8", "0x%02X", field_value(dasm, L8));
	if (field_valid(dasm, L16))
		substitute(dasm->syntax, "L16", "0x%04X", be16(field_value(dasm, L16)));

	// program address label

	if (field_valid(dasm, l1) && field_valid(dasm, l3))
		substitute(dasm->syntax, "l4", "0x%01X", (field_value(dasm, l3) << 1) | field_value(dasm, l1));

	// program memory address

	if (field_valid(dasm, l7))
		substitute(dasm->syntax, "pmad", "0x%02X", field_value(dasm, l7));
	if (field_valid(dasm, l16))
		substitute(dasm->syntax, "pmad", "0x%04X", be16(field_value(dasm, l16)));

	// program or dasm address label

	if (field_valid(dasm, P8))
		substitute(dasm->syntax, "P8", "0x%02X", field_value(dasm, P8));
	if (field_valid(dasm, P24))
		substitute(dasm->syntax, "P24", "0x%06X", be24(field_value(dasm, P24)));
}

void decode_swap(tms320_dasm_t * dasm)
{
	char tmp[64];

	if (field_valid(dasm, k6))
		substitute(dasm->syntax, "SWAP ( )", get_swap_str(field_value(dasm, k6), tmp));
}

void decode_relop(tms320_dasm_t * dasm)
{
	if (field_valid(dasm, cc))
		substitute(dasm->syntax, "RELOP", get_relop_str(field_value(dasm, cc), NULL));
}

void decode_cond(tms320_dasm_t * dasm)
{
	char tmp[64];

	if (field_valid(dasm, CCCCCCC))
		substitute(dasm->syntax, "cond", "%s", get_cond_str(field_value(dasm, CCCCCCC), tmp));

	substitute(dasm->syntax, "[label, ]", "");
}

void decode_registers(tms320_dasm_t * dasm)
{
	ut8 code = 0;

	// transition register

	if (field_valid(dasm, r))
		substitute(dasm->syntax, "TRNx", "trn%d", field_value(dasm, r));

	// source and destination temporary registers

	if (field_valid(dasm, ss))
		substitute(dasm->syntax, "Tx", "t%d", field_value(dasm, ss));

	if (field_valid(dasm, dd))
		substitute(dasm->syntax, "Tx", "t%d", field_value(dasm, dd));

	// shifted in/out bit values

	if (field_valid(dasm, vv)) {
		substitute(dasm->syntax, "BitIn", "%s", get_v_str(field_value(dasm, vv) >> 1, NULL));
		substitute(dasm->syntax, "BitOut", "%s", get_v_str(field_value(dasm, vv) >> 0, NULL));
	}

	// source and destination of CRC instruction

	if (field_valid(dasm, t))
		substitute(dasm->syntax, "TCx", "%s", get_t_str(field_value(dasm, t), NULL));

	if (field_valid(dasm, tt)) {
		substitute(dasm->syntax, "TCx", "%s", get_t_str(field_value(dasm, tt) >> 0, NULL));
		substitute(dasm->syntax, "TCy", "%s", get_t_str(field_value(dasm, tt) >> 1, NULL));
	}

	// source or destination accumulator or extended register

	if (field_valid(dasm, XSSS)) {
		substitute(dasm->syntax, "xsrc", "%s", get_xreg_str(field_value(dasm, XSSS), NULL));
		substitute(dasm->syntax, "XAsrc", "%s", get_xreg_str(field_value(dasm, XSSS), NULL));
	}

	if (field_valid(dasm, XDDD)) {
		substitute(dasm->syntax, "xdst", "%s", get_xreg_str(field_value(dasm, XDDD), NULL));
		substitute(dasm->syntax, "XAdst", "%s", get_xreg_str(field_value(dasm, XDDD), NULL));
	}

	// source or destination accumulator, auxiliary or temporary register

	if (field_valid(dasm, FSSS) && field_valid(dasm, FDDD)) {
		if (field_value(dasm, FSSS) == field_value(dasm, FDDD))
			substitute(dasm->syntax, "[src,] dst", "dst");
		else
			substitute(dasm->syntax, "[src,] dst", "src, dst");
	}

	if (field_valid(dasm, FSSS) && field_valid(dasm, FDDD)) {
		substitute(dasm->syntax, "src1", "%s", get_freg_str(field_value(dasm, FSSS), NULL));
		substitute(dasm->syntax, "src2", "%s", get_freg_str(field_value(dasm, FDDD), NULL));

		substitute(dasm->syntax, "dst1", "%s", get_freg_str(field_value(dasm, FSSS), NULL));
		substitute(dasm->syntax, "dst2", "%s", get_freg_str(field_value(dasm, FDDD), NULL));
	}


	code &= 0;
	code |= field_valid(dasm, FSSS) ? 0x01 : 0x00;
	code |= field_valid(dasm, FDDD) ? 0x02 : 0x00;

	switch (code) {
	case 0x01:	// FSSS
		substitute(dasm->syntax, "TAx", "%s", get_freg_str(field_value(dasm, FSSS), NULL));
		break;
	case 0x02:	//      FDDD
		substitute(dasm->syntax, "TAx", "%s", get_freg_str(field_value(dasm, FDDD), NULL));
		substitute(dasm->syntax, "TAy", "%s", get_freg_str(field_value(dasm, FDDD), NULL));
		break;
	case 0x03:	// FSSS FDDD
		substitute(dasm->syntax, "TAx", "%s", get_freg_str(field_value(dasm, FSSS), NULL));
		substitute(dasm->syntax, "TAy", "%s", get_freg_str(field_value(dasm, FDDD), NULL));
		break;
	}

	if (field_valid(dasm, FSSS)) {
		substitute(dasm->syntax, "src", "%s", get_freg_str(field_value(dasm, FSSS), NULL));
	}

	if (field_valid(dasm, FDDD)) {
		substitute(dasm->syntax, "dst", "%s", get_freg_str(field_value(dasm, FDDD), NULL));
	}

	if (field_valid(dasm, XACS))
		substitute(dasm->syntax, "XACsrc", "%s", get_xreg_str(field_value(dasm, XACS), NULL));

	if (field_valid(dasm, XACD))
		substitute(dasm->syntax, "XACdst", "%s", get_xreg_str(field_value(dasm, XACD), NULL));


	// source and destination accumulator registers

	code &= 0;
	code |= field_valid(dasm, SS) ? 0x01 : 0x00;
	code |= field_valid(dasm, SS2) ? 0x02 : 0x00;
	code |= field_valid(dasm, DD) ? 0x10 : 0x00;
	code |= field_valid(dasm, DD2) ? 0x20 : 0x00;

	switch (code) {
	case 0x01:	// SS
		substitute(dasm->syntax, "ACx", "ac%d", field_value(dasm, SS));
		break;
	case 0x03:	// SSSS
		substitute(dasm->syntax, "ACx", "ac%d", field_value(dasm, SS));
		substitute(dasm->syntax, "ACy", "ac%d", field_value(dasm, SS2));
		break;
	case 0x11:	// SS   DD
		if (field_value(dasm, SS) == field_value(dasm, DD)) {
			substitute(dasm->syntax, "[, ACy]", "");
			substitute(dasm->syntax, "[ACx,] ACy", "ACy");
		} else {
			substitute(dasm->syntax, "[, ACy]", ", ACy");
			substitute(dasm->syntax, "[ACx,] ACy", "ACx, ACy");
		}
		substitute(dasm->syntax, "ACx", "ac%d", field_value(dasm, SS));
		substitute(dasm->syntax, "ACy", "ac%d", field_value(dasm, DD));
		break;
	case 0x33:	// SSSS DDDD
		substitute(dasm->syntax, "ACx", "ac%d", field_value(dasm, SS));
		substitute(dasm->syntax, "ACy", "ac%d", field_value(dasm, SS2));
		substitute(dasm->syntax, "ACz", "ac%d", field_value(dasm, DD));
		substitute(dasm->syntax, "ACw", "ac%d", field_value(dasm, DD2));
		break;
	case 0x10:	//      DD
		substitute(dasm->syntax, "ACx", "ac%d", field_value(dasm, DD));
		break;
	case 0x30:	//      DDDD
		substitute(dasm->syntax, "ACx", "ac%d", field_value(dasm, DD));
		substitute(dasm->syntax, "ACy", "ac%d", field_value(dasm, DD2));
		break;
	}
}

void decode_addressing_modes(tms320_dasm_t * dasm)
{
	// Cmem

	if (field_valid(dasm, mm))
		substitute(dasm->syntax, "Cmem", "%s", get_cmem_str(field_value(dasm, mm), NULL));

	// Xmem and Ymem

	if (field_valid(dasm, Xmem_reg) && field_valid(dasm, Xmem_mmm)) {
		substitute(dasm->syntax, "Xmem", "%s", get_mmm_str(field_value(dasm, Xmem_mmm), NULL));
		substitute(dasm->syntax, "ARn", "ar%d", field_value(dasm, Xmem_reg));
	}

	if (field_valid(dasm, Ymem_reg) && field_valid(dasm, Ymem_mmm)) {
		substitute(dasm->syntax, "Ymem", "%s", get_mmm_str(field_value(dasm, Ymem_mmm), NULL));
		substitute(dasm->syntax, "ARn", "ar%d", field_value(dasm, Ymem_reg));
	}

	// Lmem and Smem

	if (field_valid(dasm, AAAAAAAI)) {
		char str[64], tmp[64];

		snprintf(tmp, sizeof(tmp), "%s", get_smem_str(field_value(dasm, AAAAAAAI), str));

		if (field_value(dasm, AAAAAAAI) & 1) {
			if (strstr(tmp, "k16")) {
				substitute(tmp, "k16", "0x%04X", be16(*(ut16 *)(&dasm->stream + dasm->length)));
				dasm->length += 2;
			} else if (strstr(tmp, "k23")) {
				substitute(tmp, "k23", "0x%06X", be24(*(ut32 *)(&dasm->stream + dasm->length) & 0x7FFFFF));
				dasm->length += 3;
			} else if (strstr(tmp, "K16")) {
				substitute(tmp, "K16", "0x%04X", be16(*(ut16 *)(&dasm->stream + dasm->length)));
				dasm->length += 2;
			}

			substitute(tmp, "ARn", "ar%d", field_value(dasm, AAAAAAAI) >> 5);
		}

		substitute(dasm->syntax, "Smem", "%s", tmp);
		substitute(dasm->syntax, "Lmem", "%s", tmp);
	}
}

void decode_qualifiers(tms320_dasm_t * dasm)
{
	switch (dasm->stream[dasm->length]) {
	case 0x98:
		// 1001 1000 - mmap
		break;

	case 0x99:
		// 1001 1001 - port(Smem)
		break;
	case 0x9a:
		// 1001 1010 - port(Smem)
		break;

	case 0x9c:
		// 1001 1100 - <insn>.LR
		set_field_value(dasm, q_lr, 1);
		break;
	case 0x9d:
		// 1001 1101 - <insn>.CR
		set_field_value(dasm, q_cr, 1);
		break;
	}
}

static insn_item_t * finalize(tms320_dasm_t * dasm)
{
	// remove odd spaces

	substitute(dasm->syntax, "  ", "%s", " ");

	// add some qualifiers

	if (field_value(dasm, q_lr))
		replace(dasm->syntax, " ", ".lr ");
	if (field_value(dasm, q_cr))
		replace(dasm->syntax, " ", ".cr ");

	return dasm->insn;
}

insn_item_t * decode_insn(tms320_dasm_t * dasm)
{
	dasm->length = dasm->head->size;

	snprintf(dasm->syntax, sizeof(dasm->syntax), \
		 field_valid(dasm, E) && field_value(dasm, E) ? "|| %s" : "%s", dasm->insn->syntax);

	decode_bits(dasm);
	decode_braces(dasm);
	decode_qualifiers(dasm);

	decode_constants(dasm);
	decode_addresses(dasm);

	decode_swap(dasm);
	decode_relop(dasm);
	decode_cond(dasm);

	decode_registers(dasm);
	decode_addressing_modes(dasm);

	return finalize(dasm);

}

insn_item_t * decode_insn_head(tms320_dasm_t * dasm)
{
	run_f_list(dasm);

	if (dasm->insn->i_list) {
		dasm->insn = dasm->insn->i_list;
		while (!i_list_last(dasm->insn)) {
			if (run_m_list(dasm) && run_f_list(dasm))
				break;
			dasm->insn++;
		}
	}

	if (!i_list_last(dasm->insn))
		return decode_insn(dasm);

	return NULL;
}

static ut8 c55x_e_list[] = {
	0xF8, 0x60,	/* 0110 0lll */
	0xF0, 0xA0,	/* 1010 FDDD */
	0xFC, 0xB0,	/* 1011 00DD */
	0xF0, 0xC0,	/* 1100 FSSS */
	0xFC, 0xBC,	/* 1011 11SS */
	0x00, 0x00,
};

insn_head_t * lookup_insn_head(tms320_dasm_t * dasm) {
	ut8 * e_list = NULL;
	/* handle some exceptions */

	if (tms320_f_get_cpu (dasm) == TMS320_F_CPU_C55X) {
		e_list = c55x_e_list;
	}
	while (e_list && (e_list[0] && e_list[1])) {
		if ((dasm->opcode & e_list[0]) == e_list[1]) {
			const char *key = sdb_fmt (0, "%c", e_list[1]);
			dasm->head = ht_find (dasm->map, key, NULL);
			break;
		}
		e_list += 2;
	}
	if (!dasm->head) {
		const char *key = sdb_fmt (0, "%c", dasm->opcode);
		dasm->head = ht_find (dasm->map, key, NULL);
		if (!dasm->head) {
			const char *key = sdb_fmt (0, "%c", (dasm->opcode & 0xfe));
			dasm->head = ht_find (dasm->map, key, NULL);
		}
	}
	dasm->insn = dasm->head ? &dasm->head->insn : NULL;
	return dasm->head;
}

static void init_dasm(tms320_dasm_t * dasm, const ut8 * stream, int len)
{
	strcpy(dasm->syntax, "invalid");
	memcpy(dasm->stream, stream, min(sizeof(dasm->stream), len));

	dasm->status = 0;
	dasm->length = 0;

	memset(&dasm->f, 0, sizeof(dasm->f));

	dasm->head = NULL;
	dasm->insn = NULL;
}

static int full_insn_size(tms320_dasm_t * dasm)
{
	int qualifier_size = 0;

	if (field_value(dasm, q_cr))
		qualifier_size = 1;
	if (field_value(dasm, q_lr))
		qualifier_size = 1;

	return dasm->length + qualifier_size;
}

/*
 * TMS320 disassembly engine public interface
 */

int tms320_dasm(tms320_dasm_t * dasm, const ut8 * stream, int len)
{
	init_dasm(dasm, stream, len);

	if (tms320_f_get_cpu(dasm) != TMS320_F_CPU_C55X_PLUS) {
		if (lookup_insn_head(dasm) && decode_insn_head(dasm)) {
			if (dasm->length > len)
				dasm->status |= TMS320_S_INVAL;
		}
	} else {
		c55x_plus_disassemble(dasm, stream, len);
	}

	if (strstr(dasm->syntax, "invalid"))
		dasm->status |= TMS320_S_INVAL;

	if (dasm->status & TMS320_S_INVAL)
		strcpy(dasm->syntax, "invalid"), dasm->length = 1;

	return full_insn_size(dasm);
}

static insn_head_t c55x_list[] = {
#  include "c55x/table.h"
};

static void tsm320_free_kv(HtKv *kv) {
	free (kv->key);
	free (kv);
}

int tms320_dasm_init(tms320_dasm_t * dasm) {
	int i = 0;

	if (dasm->map) {
		/* already initialized */
		return 0;
	}

	dasm->map = ht_new (NULL, tsm320_free_kv, NULL);
	if (!dasm->map) {
		return 0;
	}
	for (i = 0; i < ARRAY_SIZE(c55x_list); i++) {
		const char *key = sdb_fmt (0, "%c", c55x_list[i].byte);
		ht_insert (dasm->map, key, &c55x_list[i]);
	}

	tms320_f_set_cpu(dasm, TMS320_F_CPU_C55X);

	return 0;
}

int tms320_dasm_fini(tms320_dasm_t * dasm) {
	if (dasm) {
		if (dasm->map) {
			ht_free (dasm->map);
		}
		/* avoid double free */
		memset (dasm, 0, sizeof (tms320_dasm_t));
	}
	return 0;
}
