// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.

#include <cstdio>
#include <cstring>
#include <disasm.h>        // Bochs
#include <sstream>

#if defined(HAS_LLVM)
// PowerPC.h defines PC.
// This conflicts with a function that has an argument named PC
#undef PC
#include <llvm-c/Disassembler.h>
#include <llvm-c/Target.h>
#endif

#include <wx/button.h>
#include <wx/listctrl.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>

#include "Common/CommonTypes.h"
#include "Common/GekkoDisassembler.h"
#include "Common/StringUtil.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PPCAnalyst.h"
#include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/JitCommon/JitCache.h"
#include "DolphinWX/Globals.h"
#include "DolphinWX/WxUtils.h"
#include "DolphinWX/Debugger/JitWindow.h"

#if defined(HAS_LLVM)
// This class declaration should be in the header
// Due to the conflict with the PC define and the function with PC as an argument
// it has to be in this file instead.
// Once that conflict is resolved this can be moved to the header
class HostDisassemblerLLVM : public HostDisassembler
{
public:
	HostDisassemblerLLVM(const std::string host_disasm, int inst_size = -1, const std::string cpu = "");
	~HostDisassemblerLLVM()
	{
		if (m_can_disasm)
			LLVMDisasmDispose(m_llvm_context);
	}

private:
	bool m_can_disasm;
	LLVMDisasmContextRef m_llvm_context;
	int m_instruction_size;

	std::string DisassembleHostBlock(const u8* code_start, const u32 code_size, u32* host_instructions_count) override;
};

HostDisassemblerLLVM::HostDisassemblerLLVM(const std::string host_disasm, int inst_size, const std::string cpu)
	: m_can_disasm(false), m_instruction_size(inst_size)
{
	LLVMInitializeAllTargetInfos();
	LLVMInitializeAllTargetMCs();
	LLVMInitializeAllDisassemblers();

	m_llvm_context = LLVMCreateDisasmCPU(host_disasm.c_str(), cpu.c_str(), nullptr, 0, 0, nullptr);

	// Couldn't create llvm context
	if (!m_llvm_context)
		return;

	LLVMSetDisasmOptions(m_llvm_context,
		LLVMDisassembler_Option_AsmPrinterVariant |
		LLVMDisassembler_Option_PrintLatency);

	m_can_disasm = true;
}

std::string HostDisassemblerLLVM::DisassembleHostBlock(const u8* code_start, const u32 code_size, u32 *host_instructions_count)
{
	if (!m_can_disasm)
		return "(No LLVM context)";

	u8* disasmPtr = (u8*)code_start;
	const u8 *end = code_start + code_size;

	std::ostringstream x86_disasm;
	while ((u8*)disasmPtr < end)
	{
		char inst_disasm[256];
		size_t inst_size = LLVMDisasmInstruction(m_llvm_context, disasmPtr, (u64)(end - disasmPtr), (u64)disasmPtr, inst_disasm, 256);
		if (!inst_size)
		{
			x86_disasm << "Invalid inst:";

			if (m_instruction_size != -1)
			{
				// If we are on an architecture that has a fixed instruction size
				// We can continue onward past this bad instruction.
				std::string inst_str = "";
				for (int i = 0; i < m_instruction_size; ++i)
					inst_str += StringFromFormat("%02x", disasmPtr[i]);

				x86_disasm << inst_str << std::endl;
				disasmPtr += m_instruction_size;
			}
			else
			{
				// We can't continue if we are on an architecture that has flexible instruction sizes
				// Dump the rest of the block instead
				std::string code_block = "";
				for (int i = 0; (disasmPtr + i) < end; ++i)
					code_block += StringFromFormat("%02x", disasmPtr[i]);

				x86_disasm << code_block << std::endl;
				break;
			}
		}
		else
		{
			x86_disasm << inst_disasm << std::endl;
			disasmPtr += inst_size;
		}

		(*host_instructions_count)++;
	}

	return x86_disasm.str();
}
#endif

std::string HostDisassembler::DisassembleBlock(u32* address, u32* host_instructions_count, u32* code_size)
{
	if (!jit)
	{
		*host_instructions_count = 0;
		*code_size = 0;
		return "(No JIT active)";
	}

	int block_num = jit->GetBlockCache()->GetBlockNumberFromStartAddress(*address);
	if (block_num < 0)
	{
		for (int i = 0; i < 500; i++)
		{
			block_num = jit->GetBlockCache()->GetBlockNumberFromStartAddress(*address - 4 * i);
			if (block_num >= 0)
				break;
		}

		if (block_num >= 0)
		{
			JitBlock* block = jit->GetBlockCache()->GetBlock(block_num);
			if (!(block->originalAddress <= *address &&
			    block->originalSize + block->originalAddress >= *address))
				block_num = -1;
		}

		// Do not merge this "if" with the above - block_num changes inside it.
		if (block_num < 0)
		{
			host_instructions_count = 0;
			code_size = 0;
			return "(No translation)";
		}
	}

	JitBlock* block = jit->GetBlockCache()->GetBlock(block_num);

	const u8* code = (const u8*)jit->GetBlockCache()->GetCompiledCodeFromBlock(block_num);

	*code_size = block->codeSize;
	*address = block->originalAddress;
	return DisassembleHostBlock(code, block->codeSize, host_instructions_count);
}

HostDisassemblerX86::HostDisassemblerX86()
{
	m_disasm.set_syntax_intel();
}

std::string HostDisassemblerX86::DisassembleHostBlock(const u8* code_start, const u32 code_size, u32* host_instructions_count)
{
	u64 disasmPtr = (u64)code_start;
	const u8* end = code_start + code_size;

	std::ostringstream x86_disasm;
	while ((u8*)disasmPtr < end)
	{
		char inst_disasm[256];
		disasmPtr += m_disasm.disasm64(disasmPtr, disasmPtr, (u8*)disasmPtr, inst_disasm);
		x86_disasm << inst_disasm << std::endl;
		(*host_instructions_count)++;
	}

	return x86_disasm.str();
}

CJitWindow::CJitWindow(wxWindow* parent, wxWindowID id, const wxPoint& pos,
		const wxSize& size, long style, const wxString& name)
: wxPanel(parent, id, pos, size, style, name)
{
	wxBoxSizer* sizerBig   = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer* sizerSplit = new wxBoxSizer(wxHORIZONTAL);
	sizerSplit->Add(ppc_box = new wxTextCtrl(this, wxID_ANY, "(ppc)",
				wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE), 1, wxEXPAND);
	sizerSplit->Add(x86_box = new wxTextCtrl(this, wxID_ANY, "(x86)",
				wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE), 1, wxEXPAND);
	sizerBig->Add(block_list = new JitBlockList(this, wxID_ANY,
				wxDefaultPosition, wxSize(100, 140),
				wxLC_REPORT | wxSUNKEN_BORDER | wxLC_ALIGN_LEFT | wxLC_SINGLE_SEL | wxLC_SORT_ASCENDING),
				0, wxEXPAND);
	sizerBig->Add(sizerSplit, 2, wxEXPAND);

	sizerBig->Add(button_refresh = new wxButton(this, wxID_ANY, _("&Refresh")));
	button_refresh->Bind(wxEVT_BUTTON, &CJitWindow::OnRefresh, this);

	SetSizer(sizerBig);

	sizerSplit->Fit(this);
	sizerBig->Fit(this);

#if defined(_M_X86) && defined(HAS_LLVM)
	m_disassembler.reset(new HostDisassemblerLLVM("x86_64-none-unknown"));
#elif defined(_M_X86)
	m_disassembler.reset(new HostDisassemblerX86());
#elif defined(_M_ARM_64) && defined(HAS_LLVM)
	m_disassembler.reset(new HostDisassemblerLLVM("aarch64-none-unknown", 4, "cortex-a57"));
#elif defined(_M_ARM_32) && defined(HAS_LLVM)
	m_disassembler.reset(new HostDisassemblerLLVM("armv7-none-unknown", 4, "cortex-a15"));
#else
	m_disassembler.reset(new HostDisassembler());
#endif
}

void CJitWindow::OnRefresh(wxCommandEvent& /*event*/)
{
	block_list->Update();
}

void CJitWindow::ViewAddr(u32 em_address)
{
	Show(true);
	Compare(em_address);
	SetFocus();
}

void CJitWindow::Compare(u32 em_address)
{
	// Get host side code disassembly
	u32 host_instructions_count = 0;
	u32 host_code_size = 0;
	std::string host_instructions_disasm;
	host_instructions_disasm = m_disassembler->DisassembleBlock(&em_address, &host_instructions_count, &host_code_size);

	x86_box->SetValue(host_instructions_disasm);

	// == Fill in ppc box
	u32 ppc_addr = em_address;
	PPCAnalyst::CodeBuffer code_buffer(32000);
	PPCAnalyst::BlockStats st;
	PPCAnalyst::BlockRegStats gpa;
	PPCAnalyst::BlockRegStats fpa;
	PPCAnalyst::CodeBlock code_block;
	PPCAnalyst::PPCAnalyzer analyzer;
	analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE);

	code_block.m_stats = &st;
	code_block.m_gpa = &gpa;
	code_block.m_fpa = &fpa;

	if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, 32000) != 0xFFFFFFFF)
	{
		std::ostringstream ppc_disasm;
		for (u32 i = 0; i < code_block.m_num_instructions; i++)
		{
			const PPCAnalyst::CodeOp &op = code_buffer.codebuffer[i];
			std::string opcode = GekkoDisassembler::Disassemble(op.inst.hex, op.address);
			ppc_disasm << std::setfill('0') << std::setw(8) << std::hex << op.address;
			ppc_disasm << " " << opcode << std::endl;
		}

		// Add stats to the end of the ppc box since it's generally the shortest.
		ppc_disasm << std::dec << std::endl;

		// Add some generic analysis
		if (st.isFirstBlockOfFunction)
			ppc_disasm << "(first block of function)" << std::endl;
		if (st.isLastBlockOfFunction)
			ppc_disasm << "(last block of function)" << std::endl;

		ppc_disasm << st.numCycles << " estimated cycles" << std::endl;

		ppc_disasm << "Num instr: PPC: " << code_block.m_num_instructions
		           << " x86: " << host_instructions_count
		           << " (blowup: " << 100 * host_instructions_count / code_block.m_num_instructions - 100
		           << "%)" << std::endl;

		ppc_disasm << "Num bytes: PPC: " << code_block.m_num_instructions * 4
		           << " x86: " << host_code_size
		           << " (blowup: " << 100 * host_code_size / (4 * code_block.m_num_instructions) - 100
		           << "%)" << std::endl;

		ppc_box->SetValue(ppc_disasm.str());
	}
	else
	{
		ppc_box->SetValue(StringFromFormat("(non-code address: %08x)", em_address));
		x86_box->SetValue("---");
	}
}

void CJitWindow::Update()
{

}

void CJitWindow::OnHostMessage(wxCommandEvent& event)
{
	switch (event.GetId())
	{
		case IDM_NOTIFY_MAP_LOADED:
			//NotifyMapLoaded();
			break;
	}
}

// JitBlockList
//================

enum
{
	COLUMN_ADDRESS,
	COLUMN_PPCSIZE,
	COLUMN_X86SIZE,
	COLUMN_NAME,
	COLUMN_FLAGS,
	COLUMN_NUMEXEC,
	COLUMN_COST,  // (estimated as x86size * numexec)
};

JitBlockList::JitBlockList(wxWindow* parent, const wxWindowID id,
		const wxPoint& pos, const wxSize& size, long style)
	: wxListCtrl(parent, id, pos, size, style) // | wxLC_VIRTUAL)
{
	Init();
}

void JitBlockList::Init()
{
	InsertColumn(COLUMN_ADDRESS, _("Address"));
	InsertColumn(COLUMN_PPCSIZE, _("PPC Size"));
	InsertColumn(COLUMN_X86SIZE, _("x86 Size"));
	InsertColumn(COLUMN_NAME, _("Symbol"));
	InsertColumn(COLUMN_FLAGS, _("Flags"));
	InsertColumn(COLUMN_NUMEXEC, _("NumExec"));
	InsertColumn(COLUMN_COST, _("Cost"));
}

void JitBlockList::Update()
{
}
