/*
 *  ucdisasm.cc - Disassembled usecode trace
 *
 *  Copyright (C) 1999  Jeffrey S. Freedman
 *  Copyright (C) 2000-2022  The Exult Team
 *
 *  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.
 */

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "game.h"
#include "ignore_unused_variable_warning.h"
#include "span.h"
#include "stackframe.h"
#include "ucfunction.h"
#include "ucinternal.h"
#include "uctools.h"
#include "useval.h"

#include <cstdio>
#include <cstring>
#include <iostream>

using std::cout;
using std::strlen;

int Usecode_internal::get_opcode_length(int opcode) {
	if (opcode >= 0 && static_cast<unsigned>(opcode) < opcode_table.size()) {
		return opcode_table[opcode].nbytes + 1;
	}
	return 0;
}

void Usecode_internal::uc_trace_disasm(Stack_frame* frame) {
	uc_trace_disasm(
			frame->locals, frame->num_args + frame->num_vars,
			frame->function->statics, frame->data, frame->externs, frame->code,
			frame->ip);
}

void Usecode_internal::uc_trace_disasm(
		Usecode_value* locals, int num_locals,
		std::vector<Usecode_value>& locstatics, const uint8* data,
		const uint8* externals, const uint8* code, const uint8* ip) {
	ignore_unused_variable_warning(num_locals);
	const int func_ip = static_cast<int>(ip - code);
	const int opcode  = Read1(ip);
	// const uint8 *param_ip = ip;
	const opcode_desc* pdesc = nullptr;

	if (opcode >= 0 && static_cast<unsigned>(opcode) < opcode_table.size()) {
		pdesc = &(opcode_table[opcode]);
	}

	std::printf("      %04X: ", func_ip);

	if (pdesc && pdesc->mnemonic) {
		std::printf("%s", pdesc->mnemonic);
		if (strlen(pdesc->mnemonic) < 4) {
			std::printf("\t");
		}
	} else {
		std::printf("<unknown>");
	}

	if (pdesc && pdesc->nbytes > 0) {
		uint16       varref;
		signed short immed;
		int          offset;
		switch (pdesc->type) {
		case op_byte:
			std::printf("\t%02x", Read1(ip));
			break;
		case op_immed:
			// Print immediate operand
			immed = little_endian::Read2(ip);
			std::printf("\t%04hXH\t\t; %d", immed, immed);
			break;
		case op_immed32: {
			const int immed32 = little_endian::Read4s(ip);
			std::printf("\t%08XH\t\t; %d", immed32, immed32);
			break;
		}
		case op_data_string:
		case op_data_string32: {
			const char* pstr;
			size_t      len;
			// Print data string operand
			if (pdesc->type == op_data_string) {
				offset = little_endian::Read2(ip);
			} else {
				offset = little_endian::Read4s(ip);
			}

			pstr = reinterpret_cast<const char*>(data + offset);
			len  = strlen(pstr);
			if (len > 20) {
				len = 20 - 3;
			}
			std::printf("\tL%04X\t\t; ", offset);
			for (unsigned i = 0; i < len; i++) {
				std::printf("%c", pstr[i]);
			}
			if (len < strlen(pstr)) {
				// String truncated
				std::printf("...");
			}
		} break;
		case op_relative_jump:
			// Print jump desination
			offset = little_endian::Read2(ip);
			std::printf(
					"\t%04X", (offset + func_ip + 1 + pdesc->nbytes) & 0xFFFF);
			break;
		case op_relative_jump32:
			offset = little_endian::Read4s(ip);
			std::printf("\t%04X", offset + func_ip + 1 + pdesc->nbytes);
			break;
		case op_sloop:
			if (pdesc->nbytes == 11) {
				ip++;
			}
			little_endian::Read2(ip);
			little_endian::Read2(ip);
			little_endian::Read2(ip);
			varref = little_endian::Read2(ip);
			offset = little_endian::Read2(ip);
			std::printf(
					"\t[%04X], %04X\t= ", varref,
					(offset + func_ip + 1 + pdesc->nbytes) & 0xFFFF);
			locals[varref].print(cout, true);    // print value (short format)
			break;
		case op_sloop32:
			if (pdesc->nbytes == 13) {
				ip++;
			}
			little_endian::Read2(ip);
			little_endian::Read2(ip);
			little_endian::Read2(ip);
			varref = little_endian::Read2(ip);
			offset = little_endian::Read4s(ip);
			std::printf(
					"\t[%04X], %04X\t= ", varref,
					offset + func_ip + 1 + pdesc->nbytes);
			locals[varref].print(cout, true);    // print value (short format)
			break;
		case op_immed_and_relative_jump:
			immed  = little_endian::Read2(ip);
			offset = little_endian::Read2(ip);
			std::printf(
					"\t%04hXH, %04X", immed,
					(offset + func_ip + 1 + pdesc->nbytes) & 0xFFFF);
			break;
		case op_immedreljump32:
			immed  = little_endian::Read2(ip);
			offset = little_endian::Read4s(ip);
			std::printf(
					"\t%04hXH, %04X", immed,
					offset + func_ip + 1 + pdesc->nbytes);
			break;
		case op_call: {
			const unsigned short func = little_endian::Read2(ip);
			immed                     = Read1(ip);
			tcb::span<const std::string_view> func_table;
			if (Game::get_game_type() == SERPENT_ISLE) {
				if (Game::is_si_beta()) {
					func_table = sibeta_intrinsic_table;
				} else {
					func_table = si_intrinsic_table;
				}
			} else {
				func_table = bg_intrinsic_table;
			}
			std::printf(
					"\t_%s@%d\t; %04X", func_table[func].data(), immed, func);
		} break;
		case op_extcall: {
			// Print extern call
			offset = little_endian::Read2(ip);
			std::printf(
					"\t[%04X]\t\t; %04XH", offset,
					externals[2 * offset] + 256 * externals[2 * offset + 1]);
			break;
		}
		case op_varref:
			// Print variable reference
			varref = little_endian::Read2(ip);
			std::printf("\t[%04X]\t\t= ", varref);
			locals[varref].print(cout, true);    // print value (short)
			break;
		case op_flgref:
			// Print global flag reference
			offset = little_endian::Read2(ip);
			std::printf("\tflag:[%04X]\t= ", offset);
			if (gflags[offset]) {
				std::printf("set");
			} else {
				std::printf("unset");
			}
			break;
		case op_staticref: {
			const sint16 staticref = little_endian::Read2(ip);
			std::printf("[%04X]\t= ", static_cast<uint16>(staticref));
			if (staticref < 0) {
				// global
				if (static_cast<unsigned>(-staticref) < statics.size()) {
					statics[-staticref].print(cout, true);
				} else {
					std::printf("<out of range>");
				}
			} else {
				if (static_cast<unsigned>(staticref) < locstatics.size()) {
					locstatics[staticref].print(cout, true);
				} else {
					std::printf("<out of range>");
				}
			}
			break;
		}
		default:
			// Unknown type
			break;
		}
	}

	// special cases:
	switch (opcode & 0x7f) {
	case 0x05: {    // JNE/JNE32.
		Usecode_value val;
		if (sp <= stack) {
			val = Usecode_value(0);
		} else {
			val = *(sp - 1);
		}

		if (val.is_false()) {
			std::printf("\t\t(jump taken)");
		} else {
			std::printf("\t\t(jump not taken)");
		}
		break;
	}

		/*  maybe predict this?
		case 0x07:       // CMPS/CMPS32.
		{
			int cnt = little_endian::Read2(param_ip);    // # strings.
			bool matched = false;

			// only try to match if we haven't found an answer yet
			while (!matched && !found_answer && cnt-- > 0) {
				Usecode_value s;
				if (sp + cnt <= stack)
					s = Usecode_value(0);
				else
					s = *(sp - cnt - 1);
				const char *str = s.get_str_value();
				if (str && strcmp(str, user_choice) == 0) {
					matched = true;
					found_answer = true;
				}
			}
			if (val.is_false())
				std::printf("\t\t(jump taken)");
			else
				std::printf("\t\t(jump not taken)");
			break;
		}
		break;
		*/
	}

	std::printf("\n");
}
