/*====================================================================

   filename:     disassemble.cpp
   project:      GameCube DSP Tool (gcdsp)
   created:      2005.03.04
   mail:		  duddie@walla.com

   Copyright (c) 2005 Duddie

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

   ====================================================================*/

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

#include "FileUtil.h"
#include "disassemble.h"
#include "DSPTables.h"

#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif

extern void nop(const UDSPInstruction& opc);

DSPDisassembler::DSPDisassembler(const AssemblerSettings &settings)
	: settings_(settings)
{
}

DSPDisassembler::~DSPDisassembler()
{
	// Some old code for logging unknown ops.
	char filename[MAX_PATH];
	sprintf(filename, "%sUnkOps.txt", FULL_DSP_DUMP_DIR);
	FILE *uo = fopen(filename, "w");
	if (!uo)
		return;

	int count = 0;
	for (std::map<u16, int>::const_iterator iter = unk_opcodes.begin();
		iter != unk_opcodes.end(); ++iter)
	{
		if (iter->second > 0)
		{
			count++;
			fprintf(uo, "OP%04x\t%d", iter->first, iter->second);
			for (int j = 15; j >= 0; j--)  // print op bits
			{
				if ((j & 0x3) == 3)
					fprintf(uo, "\tb");
				fprintf(uo, "%d", (iter->first >> j) & 0x1);
			}
			fprintf(uo, "\n");
		}
	}
	fprintf(uo, "Unknown opcodes count: %d\n", count);
	fclose(uo);
}

bool DSPDisassembler::Disassemble(int start_pc, const std::vector<u16> &code, int base_addr, std::string &text)
{
	const char *tmp1 = "tmp1.bin";
	const char *tmp2 = "tmp.txt";

	// First we have to dump the code to a bin file.
	FILE *f = fopen(tmp1, "wb");
	fwrite(&code[0], 1, code.size() * 2, f);
	fclose(f);

	// Run the two passes.
	return DisFile(tmp1, base_addr, 1, text) && DisFile(tmp1, base_addr, 2, text);
}

char *DSPDisassembler::DisParams(const DSPOPCTemplate& opc, u16 op1, u16 op2, char *strbuf)
{
	char *buf = strbuf;
	for (int j = 0; j < opc.param_count; j++)
	{
		if (j > 0)
			buf += sprintf(buf, ", ");

		u32 val = (opc.params[j].loc >= 1) ? val = op2 : val = op1;
		val &= opc.params[j].mask;
		if (opc.params[j].lshift < 0)
			val = val << (-opc.params[j].lshift);
		else
			val = val >> opc.params[j].lshift;

		u32 type = opc.params[j].type;
		if ((type & 0xff) == 0x10)
			type &= 0xff00;

		if (type & P_REG)
		{
			// Check for _D parameter - if so flip.
			if (type == P_ACC_D)  // Used to be P_ACCM_D TODO verify
				val = (~val & 0x1) | ((type & P_REGS_MASK) >> 8);
			else
				val |= (type & P_REGS_MASK) >> 8;
			type &= ~P_REGS_MASK;
		}

		switch (type)
		{
		case P_REG:
			if (settings_.decode_registers)
				sprintf(buf, "$%s", pdregname(val));
			else
				sprintf(buf, "$%d", val);
			break;

		case P_PRG:
			if (settings_.decode_registers)
				sprintf(buf, "@$%s", pdregname(val));
			else
				sprintf(buf, "@$%d", val);
			break;

		case P_VAL:
		case P_ADDR_I:
		case P_ADDR_D:
			if (settings_.decode_names)
			{
				sprintf(buf, "%s", pdname(val));
			}
			else
				sprintf(buf, "0x%04x", val);
			break;

		case P_IMM:
			if (opc.params[j].size != 2)
			{
				if (opc.params[j].mask == 0x003f) // LSL, LSR, ASL, ASR
					sprintf(buf, "#%d", (val & 0x20) ? (val | 0xFFFFFFC0) : val);  // 6-bit sign extension
				else
					sprintf(buf, "#0x%02x", val);
			}
			else
			{
				sprintf(buf, "#0x%04x", val);
			}
			break;

		case P_MEM:
			if (opc.params[j].size != 2)
				val = (u16)(s16)(s8)val;

			if (settings_.decode_names)
				sprintf(buf, "@%s", pdname(val));
			else
				sprintf(buf, "@0x%04x", val);
			break;

		default:
			ERROR_LOG(DSPLLE, "Unknown parameter type: %x", opc.params[j].type);
			break;
		}

		buf += strlen(buf);
	}

	return strbuf;
}

static void MakeLowerCase(char *ptr)
{
	int i = 0;
	while (ptr[i])
	{
		ptr[i] = tolower(ptr[i]);
		i++;
	}
}

bool DSPDisassembler::DisOpcode(const u16 *binbuf, int base_addr, int pass, u16 *pc, std::string &dest)
{
	char buffer[256];
	char *buf = buffer;

	// Start with 8 spaces, if there's no label.
	buf[0] = ' ';
	buf[1] = '\0';
	buf++;

	if ((*pc & 0x7fff) >= 0x1000)
	{
		*pc++;
		dest.append("; outside memory");
		return false;
	}

	const u32 op1 = binbuf[*pc & 0x0fff];

	const DSPOPCTemplate *opc = NULL;
	const DSPOPCTemplate *opc_ext = NULL;

	// find opcode
	for (int j = 0; j < opcodes_size; j++)
	{
		u16 mask;

		if (opcodes[j].size & P_EXT)
			mask = opcodes[j].opcode_mask & 0xff00;
		else
			mask = opcodes[j].opcode_mask;

		if ((op1 & mask) == opcodes[j].opcode)
		{
			opc = &opcodes[j];
			break;
		}
	}
	const DSPOPCTemplate fake_op = {"CW",		0x0000, 0x0000, nop, nop, 1, 1, {{P_VAL, 2, 0, 0, 0xffff}}, NULL, NULL,};
	if (!opc)
		opc = &fake_op;

	bool extended;
	if ((opc->size & P_EXT) && (op1 & 0x00ff))
		extended = true;
	else
		extended = false;

	if (extended)
	{
		// opcode has an extension
		// find opcode
		for (int j = 0; j < opcodes_ext_size; j++)
		{
			if ((op1 & opcodes_ext[j].opcode_mask) == opcodes_ext[j].opcode)
			{
				opc_ext = &opcodes_ext[j];
				break;
			}
		}
	}

	// printing

	if (settings_.show_pc)
		buf += sprintf(buf, "%04x ", *pc);

	u32 op2;

	// Size 2 - the op has a large immediate.
	if ((opc->size & ~P_EXT) == 2)
	{
		op2 = binbuf[(*pc + 1) & 0x0fff];
		if (settings_.show_hex)
			buf += sprintf(buf, "%04x %04x ", op1, op2);
	}
	else
	{
		op2 = 0;
		if (settings_.show_hex)
			buf += sprintf(buf, "%04x      ", op1);
	}

	char opname[20];
	strcpy(opname, opc->name);
	if (settings_.lower_case_ops)
		MakeLowerCase(opname);
	char ext_buf[20];
	if (extended)
		sprintf(ext_buf, "%s%c%s", opname, settings_.ext_separator, opc_ext->name);
	else
		sprintf(ext_buf, "%s", opname);
	if (settings_.lower_case_ops)
		MakeLowerCase(ext_buf);

	if (settings_.print_tabs)
		buf += sprintf(buf, "%s\t", ext_buf);
	else
		buf += sprintf(buf, "%-12s", ext_buf);

	if (opc->param_count > 0)
		DisParams(*opc, op1, op2, buf);

	buf += strlen(buf);

	// Handle opcode extension.
	if (extended)
	{
		if (opc->param_count > 0)
			buf += sprintf(buf, " ");
		buf += sprintf(buf, ": ");
		if (opc_ext->param_count > 0)
			DisParams(*opc_ext, op1, op2, buf);

		buf += strlen(buf);
	}

	if (opc->opcode_mask == 0)
	{
		// unknown opcode
		unk_opcodes[op1]++;
		sprintf(buf, "\t\t; *** UNKNOWN OPCODE ***");
	}

	if (extended)
		*pc += opc_ext->size;
	else
		*pc += opc->size & ~P_EXT;

	if (pass == 2)
		dest.append(buffer);
	return true;
}

bool DSPDisassembler::DisFile(const char* name, int base_addr, int pass, std::string &output)
{
	FILE* in = fopen(name, "rb");
	if (in == NULL)
	{
		printf("gd_dis_file: No input\n");
		return false;
	}

	fseek(in, 0, SEEK_END);
	int size = (int)ftell(in) & ~1;
	fseek(in, 0, SEEK_SET);
	u16 *binbuf = new u16[size / 2];
	fread(binbuf, 1, size, in);
	fclose(in);

	// Actually do the disassembly.
	for (u16 pc = 0; pc < (size / 2);)
	{
		DisOpcode(binbuf, base_addr, pass, &pc, output);
		if (pass == 2)
			output.append("\n");
	}
	delete [] binbuf;
	return true;
}
