#include "stdafx_gui.h"
#if 0
#include "Utilities/Log.h"
#include "Emu/Memory/Memory.h"
#include "Emu/System.h"
#include "DisAsmFrame.h"
#include "Emu/FS/vfsLocalFile.h"
#include "Emu/CPU/CPUThreadManager.h"
#include "Emu/Cell/PPCThread.h"
#include "Gui/DisAsmFrame.h"
#include "Emu/Cell/PPUDecoder.h"
#include "Emu/Cell/PPUDisAsm.h"
#include "Emu/Cell/SPUDecoder.h"
#include "Emu/Cell/SPUDisAsm.h"

DisAsmFrame::DisAsmFrame(PPCThread& cpu)
	: wxFrame(nullptr, wxID_ANY, "DisAsm")
	, CPU(cpu)
{
	exit = false;
	count = 0;
	wxBoxSizer* s_panel = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer* s_b_panel =  new wxBoxSizer(wxHORIZONTAL);

	m_disasm_list = new wxListView(this);
	m_disasm_list->Bind(wxEVT_MOUSEWHEEL, &DisAsmFrame::MouseWheel, this);

	wxButton* b_fprev = new wxButton(this, wxID_ANY, L"<<");
	wxButton* b_prev  = new wxButton(this, wxID_ANY, L"<");
	wxButton* b_next  = new wxButton(this, wxID_ANY, L">");
	wxButton* b_fnext = new wxButton(this, wxID_ANY, L">>");
	b_fprev->Bind(wxEVT_BUTTON, &DisAsmFrame::fPrev, this);
	b_prev->Bind(wxEVT_BUTTON, &DisAsmFrame::Prev, this);
	b_next->Bind(wxEVT_BUTTON, &DisAsmFrame::Next, this);
	b_fnext->Bind(wxEVT_BUTTON, &DisAsmFrame::fNext, this);

	wxButton* b_dump  = new wxButton(this, wxID_ANY, L"Dump code");
	b_dump->Bind(wxEVT_BUTTON, &DisAsmFrame::Dump, this);

	wxButton* b_setpc = new wxButton(this, wxID_ANY, L"Set PC");
	b_setpc->Bind(wxEVT_BUTTON, &DisAsmFrame::SetPc, this);

	s_b_panel->Add(b_fprev);
	s_b_panel->Add(b_prev);
	s_b_panel->AddSpacer(5);
	s_b_panel->Add(b_next);
	s_b_panel->Add(b_fnext);
	s_b_panel->AddSpacer(8);
	s_b_panel->Add(b_dump);

	s_panel->Add(s_b_panel);
	s_panel->Add(b_setpc);
	s_panel->Add(m_disasm_list);

	m_disasm_list->InsertColumn(0, "PC");
	m_disasm_list->InsertColumn(1, "ASM");

	m_disasm_list->SetColumnWidth(0, 50);

	for(uint i=0; i<LINES_OPCODES; ++i)
		m_disasm_list->InsertItem(i, -1);

	SetSizerAndFit(s_panel);
	SetSize(50, 660);

	Bind(wxEVT_SIZE, &DisAsmFrame::OnResize, this);
}

void DisAsmFrame::OnResize(wxSizeEvent& event)
{
	const wxSize size(GetClientSize());
	m_disasm_list->SetSize( size.GetWidth(), size.GetHeight() - 25 );
	m_disasm_list->SetColumnWidth( 1, size.GetWidth() - (m_disasm_list->GetColumnWidth(0) + 8) );

	event.Skip();
}

void DisAsmFrame::AddLine(const wxString line)
{
	static bool finished = false;

	if(finished && Emu.IsRunning())
	{
		count = 0;
		finished = false;
	}
	else if(count >= LINES_OPCODES || !Emu.IsRunning())
	{
		if(Emu.IsRunning()) Emu.Pause();
		finished = true;
		CPU.PC -= 4;
		return;
	}

	m_disasm_list->SetItem(count, 0, wxString::Format("%x", CPU.PC));
	m_disasm_list->SetItem(count, 1, line);

	++count;
}

void DisAsmFrame::Resume()
{
	Emu.Resume();
}

#include <Utilities/MTProgressDialog.h>
#include "Loader/ELF.h"
#include "Loader/ELF32.h"
#include "Loader/ELF64.h"
std::vector<Elf64_Shdr>* shdr_arr_64 = NULL;
std::vector<Elf32_Shdr>* shdr_arr_32 = NULL;
ELF64Loader* l_elf64 = NULL;
ELF32Loader* l_elf32 = NULL;
bool ElfType64 = false;

class DumperThread : public ThreadBase
{
	volatile uint id;
	PPCDisAsm* disasm;
	PPCDecoder* decoder;
	volatile bool* done;
	volatile u8 cores;
	MTProgressDialog* prog_dial;
	wxArrayString** arr;

public:
	DumperThread() : ThreadBase("DumperThread")
	{
	}

	void Set(uint _id, u8 _cores, bool* _done, MTProgressDialog& _prog_dial, wxArrayString** _arr)
	{
		id = _id;
		cores = _cores;
		done = _done;
		prog_dial = &_prog_dial;
		arr = _arr;

		*done = false;

		if(Emu.GetCPU().GetThreads()[0]->GetType() != CPU_THREAD_PPU)
		{
			SPUDisAsm& dis_asm = *new SPUDisAsm(CPUDisAsm_DumpMode);
			decoder = new SPUDecoder(dis_asm);
			disasm = &dis_asm;
		}
		else
		{
			PPUDisAsm* dis_asm = new PPUDisAsm(CPUDisAsm_DumpMode);
			decoder = new PPUDecoder(dis_asm);
			disasm = dis_asm;
		}
	}

	virtual void Task()
	{
		LOG_NOTICE(HLE, "Start dump in thread %d!", (int)id);
		const u32 max_value = prog_dial->GetMaxValue(id);
		const u32 shdr_count = ElfType64 ? shdr_arr_64->size() : shdr_arr_32->size();

		for(u32 sh=0, vsize=0; sh<shdr_count; ++sh)
		{
			const u64 sh_size = (ElfType64 ? (*shdr_arr_64)[sh].sh_size : (*shdr_arr_32)[sh].sh_size) / 4;
			const u64 sh_addr = (ElfType64 ? (*shdr_arr_64)[sh].sh_addr : (*shdr_arr_32)[sh].sh_addr);

			u64 d_size = sh_size / cores;
			const u64 s_fix = sh_size % cores;
			if(id <= s_fix) d_size++;

			for(u64 off = id * 4, size=0; size<d_size; vsize++, size++)
			{
				prog_dial->Update(id, vsize,
					wxString::Format("%d thread: %d of %d", (int)id + 1, vsize, max_value));

				disasm->dump_pc = sh_addr + off;
				decoder->Decode(vm::read32(disasm->dump_pc));

				arr[id][sh].Add(fmt::FromUTF8(disasm->last_opcode));

				off += (cores - id) * 4;
				off += id * 4;
			}
		}

		LOG_NOTICE(HLE, "Finish dump in thread %d!", (int)id);

		*done = true;
	}

	void OnExit()
	{
		LOG_NOTICE(HLE, "CleanUp dump thread (%d)!", (int)id);
		safe_delete(decoder);
	}
};

struct WaitDumperThread : public ThreadBase
{
	volatile bool* done;
	volatile u8 cores;
	wxString patch;
	MTProgressDialog& prog_dial;
	wxArrayString** arr;

	WaitDumperThread(bool* _done, u8 _cores, wxString _patch, MTProgressDialog& _prog_dial, wxArrayString** _arr) 
		: ThreadBase("WaitDumperThread")
		, done(_done)
		, cores(_cores)
		, patch(_patch)
		, prog_dial(_prog_dial)
		, arr(_arr)
	{
	}

	~WaitDumperThread()
	{
		delete (bool*)done;
		done = NULL;
	}

	virtual void Task()
	{
		for(uint i=0; i<cores; i++)
		{
			while(done[i] == false)
				std::this_thread::sleep_for(std::chrono::milliseconds(1));
		}

		LOG_NOTICE(HLE, "Saving dump is started!");
		const uint length_for_core = prog_dial.GetMaxValue(0);
		const uint length = length_for_core * cores;
		prog_dial.Close();

		wxArrayLong max;
		max.Add(length);
		MTProgressDialog& prog_dial2 = 
			*new MTProgressDialog(NULL, wxDefaultSize, "Saving", "Loading...", max, 1);

		wxFile fd;
		fd.Open(patch, wxFile::write);

		const u32 shdr_count = ElfType64 ? shdr_arr_64->size() : shdr_arr_32->size();

		for(uint sh=0, counter=0; sh<shdr_count; ++sh)
		{
			const u64 sh_size = ElfType64 ? (*shdr_arr_64)[sh].sh_size : (*shdr_arr_32)[sh].sh_size;

			if(!sh_size) continue;
			const uint c_sh_size = sh_size / 4;

			fd.Write(wxString::Format("Start of section header %d (instructions count: %d)\n", sh, c_sh_size));

			for(uint i=0, c=0, v=0; i<c_sh_size; i++, c++, counter++)
			{
				if(c >= cores)
				{
					c = 0;
					v++;
				}

				prog_dial2.Update(0, counter, wxString::Format("Saving data to file: %d of %d", counter, length));

				if(v >= arr[c][sh].GetCount()) continue;

				fd.Write("\t");
				fd.Write(arr[c][sh][v]);
			}

			fd.Write(wxString::Format("End of section header %d\n\n", sh));
		}
		
		LOG_NOTICE(HLE, "CleanUp dump saving!");

		for(uint c=0; c<cores; ++c)
		{
			for(uint sh=0; sh<shdr_count; ++sh) arr[c][sh].Empty();
		}

		delete[] arr;

		//safe_delete(shdr_arr);
		delete l_elf32;
		delete l_elf64;

		prog_dial2.Close();
		fd.Close();

		wxMessageBox("Dumping done.", "rpcs3 message");

		Emu.Stop();
	}
};

void DisAsmFrame::Dump(wxCommandEvent& WXUNUSED(event)) 
{
	wxFileDialog ctrl( this, L"Select output file...",
		wxEmptyString, "DisAsm.txt", "*.txt", wxFD_SAVE);

	if(ctrl.ShowModal() == wxID_CANCEL) return;

	vfsLocalFile& f_elf = *new vfsLocalFile(nullptr);
	f_elf.Open(Emu.m_path);

	LOG_NOTICE(HLE, "path: %s", Emu.m_path.c_str());
	Elf_Ehdr ehdr;
	ehdr.Load(f_elf);

	if(!ehdr.CheckMagic())
	{
		LOG_ERROR(HLE, "Corrupted ELF!");
		return;
	}
	std::vector<std::string> name_arr;

	switch(ehdr.GetClass())
	{
	case CLASS_ELF64:
		ElfType64 = true;
		l_elf64 = new ELF64Loader(f_elf);
		if(!l_elf64->LoadInfo())
		{
			delete l_elf64;
			return;
		}
		name_arr = l_elf64->shdr_name_arr;
		shdr_arr_64 = &l_elf64->shdr_arr;
		if(l_elf64->shdr_arr.size() <= 0) return;
	break;

	case CLASS_ELF32:
		ElfType64 = false;
		l_elf32 = new ELF32Loader(f_elf);
		if(!l_elf32->LoadInfo())
		{
			delete l_elf32;
			return;
		}

		name_arr = l_elf32->shdr_name_arr;
		shdr_arr_32 = &l_elf32->shdr_arr;
		if(l_elf32->shdr_arr.size() <= 0) return;
	break;

	default: LOG_ERROR(HLE, "Corrupted ELF!"); return;
	}

	PPCDisAsm* disasm;
	PPCDecoder* decoder;

	switch(Emu.GetCPU().GetThreads()[0]->GetType())
	{
	case CPU_THREAD_PPU:
	{
		PPUDisAsm* dis_asm = new PPUDisAsm(CPUDisAsm_DumpMode);
		decoder = new PPUDecoder(dis_asm);
		disasm = dis_asm;
	}
	break;

	case CPU_THREAD_SPU:
	case CPU_THREAD_RAW_SPU:
	{
		SPUDisAsm& dis_asm = *new SPUDisAsm(CPUDisAsm_DumpMode);
		decoder = new SPUDecoder(dis_asm);
		disasm = &dis_asm;
	}
	break;
	}

	const u32 shdr_count = ElfType64 ? shdr_arr_64->size() : shdr_arr_32->size();

	u64 max_count = 0;
	for(u32 sh=0; sh<shdr_count; ++sh)
	{
		const u64 sh_size = (ElfType64 ? (*shdr_arr_64)[sh].sh_size : (*shdr_arr_32)[sh].sh_size) / 4;
		max_count += sh_size;
	}

	wxArrayLong max;
	max.Add(max_count);
	MTProgressDialog& prog_dial = *new MTProgressDialog(NULL, wxDefaultSize, "Saving", "Loading...", max, 1);
	max.Clear();

	wxFile fd(ctrl.GetPath(), wxFile::write);

	for(u32 sh=0, vsize=0; sh<shdr_count; ++sh)
	{
		const u64 sh_size = (ElfType64 ? (*shdr_arr_64)[sh].sh_size : (*shdr_arr_32)[sh].sh_size) / 4;
		const u64 sh_addr = (ElfType64 ? (*shdr_arr_64)[sh].sh_addr : (*shdr_arr_32)[sh].sh_addr);

		const std::string name = sh < name_arr.size() ? name_arr[sh] : "Unknown";

		fd.Write(wxString::Format("Start of section header %s[%d] (instructions count: %d)\n", name.c_str(), sh, sh_size));
		prog_dial.Update(0, vsize, wxString::Format("Disasm %s section", name.c_str()));

		if(Memory.IsGoodAddr(sh_addr))
		{
			for(u64 addr=sh_addr; addr<sh_addr+sh_size; addr++, vsize++)
			{
				disasm->dump_pc = addr;
				decoder->Decode(vm::read32(disasm->dump_pc));
				fd.Write("\t");
				fd.Write(fmt::FromUTF8(disasm->last_opcode));
			}
		}
		fd.Write(wxString::Format("End of section header %s[%d]\n\n", name.c_str(), sh));
	}

	prog_dial.Close();
	Emu.Stop();
	/*
	SYSTEM_INFO si;
	GetSystemInfo(&si);
	const uint cores_count =
		(si.dwNumberOfProcessors < 1 || si.dwNumberOfProcessors > 8 ? 2 : si.dwNumberOfProcessors); 

	wxArrayLong max;
	max.Clear();

	u64 max_count = 0;

	if(ElfType64)
	{
		for(uint sh=0; sh<l_elf64->shdr_arr.GetCount(); ++sh)
		{
			max_count += l_elf64->shdr_arr[sh].sh_size / 4;
		}
	}
	else
	{
		for(uint sh=0; sh<l_elf32->shdr_arr.GetCount(); ++sh)
		{
			max_count += l_elf32->shdr_arr[sh].sh_size / 4;
		}
	}

	for(uint c=0; c<cores_count; ++c) max.Add(max_count / cores_count);
	for(uint c=0; c<max_count % cores_count; ++c) max[c]++;

	MTProgressDialog& prog_dial = *new MTProgressDialog(this, wxDefaultSize, "Dumping...", "Loading", max, cores_count);

	DumperThread* dump = new DumperThread[cores_count];
	wxArrayString** arr = new wxArrayString*[cores_count];

	bool* threads_done = new bool[cores_count];

	for(uint i=0; i<cores_count; ++i)
	{
		arr[i] = new wxArrayString[ElfType64 ? l_elf64->shdr_arr.GetCount() : l_elf32->shdr_arr.GetCount()];
		dump[i].Set(i, cores_count, &threads_done[i], prog_dial, arr);
		dump[i].Start();
	}

	WaitDumperThread& wait_dump = 
		*new WaitDumperThread(threads_done, cores_count, ctrl.GetPath(), prog_dial, arr);
	wait_dump.Start();
	*/
}

void DisAsmFrame::Prev (wxCommandEvent& WXUNUSED(event)) { if(Emu.IsPaused()) { CPU.SetPc( CPU.PC - 4*(LINES_OPCODES+1)); Resume(); } }
void DisAsmFrame::Next (wxCommandEvent& WXUNUSED(event)) { if(Emu.IsPaused()) { CPU.SetPc( CPU.PC - 4*(LINES_OPCODES-1)); Resume(); } }
void DisAsmFrame::fPrev(wxCommandEvent& WXUNUSED(event)) { if(Emu.IsPaused()) { CPU.SetPc( CPU.PC - (4*LINES_OPCODES)*2); Resume(); } }
void DisAsmFrame::fNext(wxCommandEvent& WXUNUSED(event)) { if(Emu.IsPaused()) { Resume(); } }
void DisAsmFrame::SetPc(wxCommandEvent& WXUNUSED(event))
{
	if(!Emu.IsPaused()) return;

	wxDialog diag(this, wxID_ANY, "Set PC", wxDefaultPosition);

	wxBoxSizer* s_panel(new wxBoxSizer(wxVERTICAL));
	wxBoxSizer* s_b_panel(new wxBoxSizer(wxHORIZONTAL));
	wxTextCtrl* p_pc(new wxTextCtrl(&diag, wxID_ANY));

	s_panel->Add(p_pc);
	s_panel->AddSpacer(8);
	s_panel->Add(s_b_panel);

	s_b_panel->Add(new wxButton(&diag, wxID_OK), wxLEFT, 0, 5);
	s_b_panel->AddSpacer(5);
	s_b_panel->Add(new wxButton(&diag, wxID_CANCEL), wxRIGHT, 0, 5);

	diag.SetSizerAndFit( s_panel );

	p_pc->SetLabel(wxString::Format("%x", CPU.PC));

	if(diag.ShowModal() == wxID_OK)
	{
		sscanf(fmt::ToUTF8(p_pc->GetLabel()).c_str(), "%x", &CPU.PC);
		Resume();
	}
}

void DisAsmFrame::MouseWheel(wxMouseEvent& event)
{
	if(!Emu.IsPaused())
	{
		event.Skip();
		return;
	}

	const int value = (event.m_wheelRotation / event.m_wheelDelta);

	if(event.ControlDown())
	{
		CPU.SetPc( CPU.PC - (((4*LINES_OPCODES)*2)*value) );
	}
	else
	{
		CPU.SetPc( CPU.PC - 4*(LINES_OPCODES + (value /** event.m_linesPerAction*/)) );
	}

	Emu.Resume();

	event.Skip();
}
#endif