/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2014  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 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 PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PrecompiledHeader.h"
#include "CtrlDisassemblyView.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/Debug.h"
#include "DebugTools/MipsAssembler.h"

#include "DebugEvents.h"
#include "BreakpointWindow.h"
#include "AppConfig.h"
#include "System.h"
#include "DisassemblyDialog.h"

#include <wx/mstream.h>
#include <wx/clipbrd.h>
#include <wx/file.h>

#include "Resources/Breakpoint_Active.h"
#include "Resources/Breakpoint_Inactive.h"

wxBEGIN_EVENT_TABLE(CtrlDisassemblyView, wxWindow)
	EVT_PAINT(CtrlDisassemblyView::paintEvent)
	EVT_MOUSEWHEEL(CtrlDisassemblyView::mouseEvent)
	EVT_LEFT_DOWN(CtrlDisassemblyView::mouseEvent)
	EVT_LEFT_DCLICK(CtrlDisassemblyView::mouseEvent)
	EVT_RIGHT_DOWN(CtrlDisassemblyView::mouseEvent)
	EVT_RIGHT_UP(CtrlDisassemblyView::mouseEvent)
	EVT_MOTION(CtrlDisassemblyView::mouseEvent)
	EVT_KEY_DOWN(CtrlDisassemblyView::keydownEvent)
	EVT_CHAR(CtrlDisassemblyView::keydownEvent)
	EVT_SCROLLWIN_LINEUP(CtrlDisassemblyView::scrollbarEvent)
	EVT_SCROLLWIN_LINEDOWN(CtrlDisassemblyView::scrollbarEvent)
	EVT_SCROLLWIN_PAGEUP(CtrlDisassemblyView::scrollbarEvent)
	EVT_SCROLLWIN_PAGEDOWN(CtrlDisassemblyView::scrollbarEvent)
	EVT_SIZE(CtrlDisassemblyView::sizeEvent)
	EVT_SET_FOCUS(CtrlDisassemblyView::focusEvent)
	EVT_KILL_FOCUS(CtrlDisassemblyView::focusEvent)
wxEND_EVENT_TABLE()

enum DisassemblyMenuIdentifiers
{
	ID_DISASM_COPYADDRESS = 1,
	ID_DISASM_COPYINSTRUCTIONHEX,
	ID_DISASM_COPYINSTRUCTIONDISASM,
	ID_DISASM_DISASSEMBLETOFILE,
	ID_DISASM_ASSEMBLE,
	ID_DISASM_RUNTOHERE,
	ID_DISASM_SETPCTOHERE,
	ID_DISASM_TOGGLEBREAKPOINT,
	ID_DISASM_FOLLOWBRANCH,
	ID_DISASM_GOTOINMEMORYVIEW,
	ID_DISASM_KILLFUNCTION,
	ID_DISASM_RENAMEFUNCTION,
	ID_DISASM_REMOVEFUNCTION,
	ID_DISASM_ADDFUNCTION
};

class NonAutoSelectTextCtrl: public wxTextCtrl
{
public:
	NonAutoSelectTextCtrl(wxWindow *parent, wxWindowID id,
		const wxString& value = wxEmptyString,
		const wxPoint& pos = wxDefaultPosition,
		const wxSize& size = wxDefaultSize,
		long style = 0,
		const wxValidator& validator = wxDefaultValidator,
		const wxString& name = wxTextCtrlNameStr)
		 : wxTextCtrl(parent,id,value,pos,size,style,validator,name)
	{

	}
#ifdef _WIN32

#define WM_GETDLGCODE		0x0087
#define DLGC_HASSETSEL		0x0008

	virtual WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
	{
		WXLRESULT result = wxTextCtrl::MSWWindowProc(nMsg,wParam,lParam);
		if (nMsg == WM_GETDLGCODE)
			result &= ~DLGC_HASSETSEL;
		return result;
	}
#endif
};

class TextEntryDialog: public wxDialog
{
public:
	TextEntryDialog(wxWindow* parent, wxString title, wxString defaultText)
		: wxDialog(parent,wxID_ANY,title)
	{
		wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
	
		textControl = new NonAutoSelectTextCtrl(this,wxID_ANY,L"",wxDefaultPosition,wxDefaultSize,0);
		textControl->SetValue(defaultText);
		textControl->SetSelection(textControl->GetLastPosition(),textControl->GetLastPosition());
		sizer->Add(textControl,0,wxEXPAND);

		wxBoxSizer* buttons = new wxBoxSizer(wxHORIZONTAL);
		okButton = new wxButton(this,wxID_OK,L"OK");
		okButton->SetDefault();
		cancelButton = new wxButton(this,wxID_CANCEL,L"Cancel");

		buttons->Add(okButton);
		buttons->Add(cancelButton);
		sizer->Add(buttons,0,wxEXPAND);
	
		SetSizer(sizer);
		sizer->Layout();
		sizer->Fit(this);

		textControl->SetFocus();
		textControl->SetFocusFromKbd();
	}
	wxString getText() { return textControl->GetValue(); }

private:
	NonAutoSelectTextCtrl* textControl;
	wxButton* okButton;
	wxButton* cancelButton;
};

inline wxIcon _wxGetIconFromMemory(const unsigned char *data, int length) {
   wxMemoryInputStream is(data, length);
   wxBitmap b = wxBitmap(wxImage(is, wxBITMAP_TYPE_ANY, -1), -1);
   wxIcon icon;
   icon.CopyFromBitmap(b);
   return icon;
}

CtrlDisassemblyView::CtrlDisassemblyView(wxWindow* parent, DebugInterface* _cpu)
	: wxWindow(parent,wxID_ANY,wxDefaultPosition,wxDefaultSize,wxWANTS_CHARS|wxBORDER_SIMPLE|wxVSCROLL), cpu(_cpu)
{
	manager.setCpu(cpu);
	windowStart = 0x100000;
	rowHeight = getDebugFontHeight()+2;
	charWidth = getDebugFontWidth();
	displaySymbols = true;
	visibleRows = 1;

	bpEnabled = _wxGetIconFromMemory(res_Breakpoint_Active::Data,res_Breakpoint_Active::Length);
	bpDisabled = _wxGetIconFromMemory(res_Breakpoint_Inactive::Data,res_Breakpoint_Inactive::Length);

	menu.Append(ID_DISASM_COPYADDRESS,				L"Copy Address");
	menu.Append(ID_DISASM_COPYINSTRUCTIONHEX,		L"Copy Instruction (Hex)");
	menu.Append(ID_DISASM_COPYINSTRUCTIONDISASM,	L"Copy Instruction (Disasm)");
	menu.Append(ID_DISASM_DISASSEMBLETOFILE,		L"Disassemble to File");
	menu.AppendSeparator();
	menu.Append(ID_DISASM_ASSEMBLE,					L"Assemble Opcode");
	menu.AppendSeparator();
	menu.Append(ID_DISASM_RUNTOHERE,				L"Run to Cursor");
	menu.Append(ID_DISASM_SETPCTOHERE,				L"Jump to Cursor");
	menu.Append(ID_DISASM_TOGGLEBREAKPOINT,			L"Toggle Breakpoint");
	menu.Append(ID_DISASM_FOLLOWBRANCH,				L"Follow Branch");
	menu.AppendSeparator();
	menu.Append(ID_DISASM_GOTOINMEMORYVIEW,			L"Go to in Memory View");
	menu.AppendSeparator();
	menu.Append(ID_DISASM_ADDFUNCTION,				L"Add Function Here");
	menu.Append(ID_DISASM_RENAMEFUNCTION,			L"Rename Function");
	menu.Append(ID_DISASM_REMOVEFUNCTION,			L"Remove Function");
	menu.Bind(wxEVT_MENU, &CtrlDisassemblyView::onPopupClick, this);

	SetScrollbar(wxVERTICAL,100,1,201,true);
	SetDoubleBuffered(true);
	calculatePixelPositions();
	setCurAddress(windowStart);
}

#ifdef _WIN32
WXLRESULT CtrlDisassemblyView::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
	switch (nMsg)
	{
	case 0x0104:		// WM_SYSKEYDOWN, make f10 usable
		if (wParam == 0x79)	// f10
		{
			postEvent(debEVT_STEPOVER,0);
			return 0;
		}
		break;
	}

	return wxWindow::MSWWindowProc(nMsg,wParam,lParam);
}
#endif

void CtrlDisassemblyView::scanFunctions()
{
	if (!cpu->isAlive())
		return;

	manager.analyze(windowStart,manager.getNthNextAddress(windowStart,visibleRows)-windowStart);
}

void CtrlDisassemblyView::postEvent(wxEventType type, wxString text)
{
   wxCommandEvent event( type, GetId() );
   event.SetEventObject(this);
   event.SetClientData(cpu);
   event.SetString(text);
   wxPostEvent(this,event);
}

void CtrlDisassemblyView::postEvent(wxEventType type, int value)
{
   wxCommandEvent event( type, GetId() );
   event.SetEventObject(this);
   event.SetClientData(cpu);
   event.SetInt(value);
   wxPostEvent(this,event);
}

void CtrlDisassemblyView::paintEvent(wxPaintEvent & evt)
{
	wxPaintDC dc(this);
	render(dc);
}

void CtrlDisassemblyView::redraw()
{
	wxClientDC dc(this);
	render(dc);
}

bool CtrlDisassemblyView::getDisasmAddressText(u32 address, char* dest, bool abbreviateLabels, bool showData)
{
	if (displaySymbols)
	{
		const std::string addressSymbol = symbolMap.GetLabelString(address);
		if (!addressSymbol.empty())
		{
			for (int k = 0; addressSymbol[k] != 0; k++)
			{
				// abbreviate long names
				if (abbreviateLabels && k == 16 && addressSymbol[k+1] != 0)
				{
					*dest++ = '+';
					break;
				}
				*dest++ = addressSymbol[k];
			}
			*dest++ = ':';
			*dest = 0;
			return true;
		} else {
			sprintf(dest,"    %08X",address);
			return false;
		}
	} else {
		if (showData)
			sprintf(dest,"%08X %08X",address,cpu->read32(address));
		else
			sprintf(dest,"%08X",address);
		return false;
	}
}

wxColor scaleColor(wxColor color, float factor)
{
	unsigned char r = color.Red();
	unsigned char g = color.Green();
	unsigned char b = color.Blue();
	unsigned char a = color.Alpha();

	r = std::min(255,std::max((int)(r*factor),0));
	g = std::min(255,std::max((int)(g*factor),0));
	b = std::min(255,std::max((int)(b*factor),0));

	return wxColor(r,g,b,a);
}

void CtrlDisassemblyView::drawBranchLine(wxDC& dc, std::map<u32,int>& addressPositions, BranchLine& line)
{
	u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows);
	
	int winBottom = GetSize().GetHeight();

	int topY;
	int bottomY;
	if (line.first < windowStart)
	{
		topY = -1;
	} else if (line.first >= windowEnd)
	{
		topY = GetSize().GetHeight()+1;
	} else {
		topY = addressPositions[line.first] + rowHeight/2;
	}
			
	if (line.second < windowStart)
	{
		bottomY = -1;
	} else if (line.second >= windowEnd)
	{
		bottomY = GetSize().GetHeight()+1;
	} else {
		bottomY = addressPositions[line.second] + rowHeight/2;
	}

	if ((topY < 0 && bottomY < 0) || (topY > winBottom && bottomY > winBottom))
	{
		return;
	}

	// highlight line in a different color if it affects the currently selected opcode
	wxColor color;
	if (line.first == curAddress || line.second == curAddress)
	{
		color = wxColor(0xFF257AFA);
	} else {
		color = wxColor(0xFFFF3020);
	}
	
	wxPen pen = wxPen(color);
	dc.SetBrush(wxBrush(color));
	dc.SetPen(wxPen(color));

	int x = pixelPositions.arrowsStart+line.laneIndex*8;

	if (topY < 0)	// first is not visible, but second is
	{
		dc.DrawLine(x-2,bottomY,x+2,bottomY);
		dc.DrawLine(x+2,bottomY,x+2,0);

		if (line.type == LINE_DOWN)
		{
			dc.DrawLine(x,bottomY-4,x-4,bottomY);
			dc.DrawLine(x-4,bottomY,x+1,bottomY+5);
		}
	} else if (bottomY > winBottom) // second is not visible, but first is
	{
		dc.DrawLine(x-2,topY,x+2,topY);
		dc.DrawLine(x+2,topY,x+2,winBottom);
				
		if (line.type == LINE_UP)
		{
			dc.DrawLine(x,topY-4,x-4,topY);
			dc.DrawLine(x-4,topY,x+1,topY+5);
		}
	} else { // both are visible
		if (line.type == LINE_UP)
		{
			dc.DrawLine(x-2,bottomY,x+2,bottomY);
			dc.DrawLine(x+2,bottomY,x+2,topY);
			dc.DrawLine(x+2,topY,x-4,topY);
			
			dc.DrawLine(x,topY-4,x-4,topY);
			dc.DrawLine(x-4,topY,x+1,topY+5);
		} else {
			dc.DrawLine(x-2,topY,x+2,topY);
			dc.DrawLine(x+2,topY,x+2,bottomY);
			dc.DrawLine(x+2,bottomY,x-4,bottomY);
			
			dc.DrawLine(x,bottomY-4,x-4,bottomY);
			dc.DrawLine(x-4,bottomY,x+1,bottomY+5);
		}
	}
}

int getBackgroundColor(unsigned int address)
{
	u32 colors[6] = {0xFFe0FFFF,0xFFFFe0e0,0xFFe8e8FF,0xFFFFe0FF,0xFFe0FFe0,0xFFFFFFe0};
	int n=symbolMap.GetFunctionNum(address);
	if (n==-1) return 0xFFFFFFFF;
	return colors[n%6];
}

std::set<std::string> CtrlDisassemblyView::getSelectedLineArguments() {
	std::set<std::string> args;

	DisassemblyLineInfo line = DisassemblyLineInfo();
	for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) {
		manager.getLine(addr, displaySymbols, line);
		size_t p = 0, nextp = line.params.find(',');
		while (nextp != line.params.npos) {
			args.insert(line.params.substr(p, nextp - p));
			p = nextp + 1;
			nextp = line.params.find(',', p);
		}

		if (p < line.params.size()) {
			args.insert(line.params.substr(p));
		}

		// check for registers in memory opcodes
		p = line.params.find('(');
		nextp = line.params.find(')');
		if (p != line.params.npos && nextp != line.params.npos && nextp > p) {
			args.insert(line.params.substr(p+1, nextp - p - 1));
		}

	}

	return args;
}

void CtrlDisassemblyView::drawArguments(wxDC& dc, const DisassemblyLineInfo &line, int x, int y, wxColor& textColor,
	const std::set<std::string> &currentArguments)
{
	if (line.params.empty())
		return;

	// Don't highlight the selected lines.
	if (isInInterval(selectRangeStart, selectRangeEnd - selectRangeStart, line.info.opcodeAddress))
	{
		dc.DrawText(wxString(line.params.c_str(),wxConvUTF8),x,y);
		return;
	}

	wxColor highlightedColor = wxColor(textColor == 0xFF0000FF ? 0xFFAABB77 : 0xFFAABB00);

	size_t p = 0, nextp = line.params.find(',');
	while (nextp != line.params.npos) {
		const std::string arg = line.params.substr(p, nextp - p);
		if (currentArguments.find(arg) != currentArguments.end() && textColor != 0xFFFFFFFF)
		{
			dc.SetTextForeground(highlightedColor);
		}
		dc.DrawText(wxString(arg.c_str(),wxConvUTF8),x,y);
		x += arg.size()*charWidth;

		p = nextp + 1;
		nextp = line.params.find(',', p);

		dc.SetTextForeground(textColor);
		dc.DrawText(L",",x,y);
		x += charWidth;
	}
	if (p < line.params.size()) {
		const std::string arg = line.params.substr(p);
		if (currentArguments.find(arg) != currentArguments.end() && textColor != 0xFFFFFFFF)
		{
			dc.SetTextForeground(highlightedColor);
		}
		dc.DrawText(wxString(arg.c_str(),wxConvUTF8),x,y);
		dc.SetTextForeground(textColor);
	}
}

void CtrlDisassemblyView::render(wxDC& dc)
{
	// init stuff
	int totalWidth, totalHeight;
	GetSize(&totalWidth,&totalHeight);
	visibleRows = totalHeight / rowHeight;

	// clear background
	wxColor white = wxColor(0xFFFFFFFF);

	dc.SetBrush(wxBrush(white));
	dc.SetPen(wxPen(white));

	int width,height;
	dc.GetSize(&width,&height);
	dc.DrawRectangle(0, 0, width, height);

	if (!cpu->isAlive())
		return;

	wxFont font = pxGetFixedFont(8);
	wxFont boldFont = pxGetFixedFont(8, wxFONTWEIGHT_BOLD);
	font.SetPixelSize(wxSize(charWidth,rowHeight-2));
	boldFont.SetPixelSize(wxSize(charWidth,rowHeight-2));

	bool hasFocus = wxWindow::FindFocus() == this;

	std::map<u32,int> addressPositions;

	unsigned int address = windowStart;

	const std::set<std::string> currentArguments = getSelectedLineArguments();
	DisassemblyLineInfo line = DisassemblyLineInfo();
	for (int i = 0; i < visibleRows+1; i++)
	{
		manager.getLine(address,displaySymbols,line);

		int rowY1 = rowHeight*i;

		addressPositions[address] = rowY1;

		wxColor backgroundColor = wxColor(getBackgroundColor(address));
		wxColor textColor = wxColor(0xFF000000);
		
		if (isInInterval(address,line.totalSize,cpu->getPC()))
		{
			backgroundColor = scaleColor(backgroundColor,1.05f);
		}

		if (address >= selectRangeStart && address < selectRangeEnd)
		{
			if (hasFocus)
			{
				backgroundColor = address == curAddress ? 0xFFFF8822 : 0xFFFF9933;
				textColor = 0xFFFFFFFF;
			} else {
				backgroundColor = 0xFFC0C0C0;
			}
		}

		// display whether the condition of a branch is met
		if (line.info.isConditional && address == cpu->getPC())
		{
			line.params += line.info.conditionMet ? "  ; true" : "  ; false";
		}

		// draw background
		dc.SetBrush(wxBrush(backgroundColor));
		dc.SetPen(wxPen(backgroundColor));
		dc.DrawRectangle(0,rowY1,totalWidth,rowHeight);
		
		// display address/symbol
		bool enabled;
		if (CBreakPoints::IsAddressBreakPoint(address,&enabled))
		{
			if (enabled)
				textColor = 0x0000FF;
			int yOffset = std::max(-1,(rowHeight-14+1)/2);
			dc.DrawIcon(enabled ? bpEnabled : bpDisabled,2,rowY1+1+yOffset);
		}
		
		dc.SetTextForeground(textColor);

		char addressText[64];
		getDisasmAddressText(address,addressText,true,line.type == DISTYPE_OPCODE);

		dc.SetFont(font);
		dc.DrawText(wxString(addressText,wxConvUTF8),pixelPositions.addressStart,rowY1+2);
		drawArguments(dc, line, pixelPositions.argumentsStart, rowY1 + 2, textColor, currentArguments);
		
		if (isInInterval(address,line.totalSize,cpu->getPC()))
			dc.DrawText(L"\u25A0",pixelPositions.opcodeStart-(charWidth+1),rowY1);

		dc.SetFont(boldFont);
		dc.DrawText(wxString(line.name.c_str(),wxConvUTF8),pixelPositions.opcodeStart,rowY1+2);
		
		address += line.totalSize;
	}

	std::vector<BranchLine> branchLines = manager.getBranchLines(windowStart,address-windowStart);
	for (size_t i = 0; i < branchLines.size(); i++)
	{
		drawBranchLine(dc,addressPositions,branchLines[i]);
	}
	
}

void CtrlDisassemblyView::gotoAddress(u32 addr)
{
	u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows);
	u32 newAddress = manager.getStartAddress(addr);

	if (newAddress < windowStart || newAddress >= windowEnd)
	{
		windowStart = manager.getNthPreviousAddress(newAddress,visibleRows/2);
	}

	setCurAddress(addr);
	scanFunctions();
	redraw();
}

void CtrlDisassemblyView::scrollAddressIntoView()
{
	u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows);

	if (curAddress < windowStart)
		windowStart = curAddress;
	else if (curAddress >= windowEnd)
		windowStart =  manager.getNthPreviousAddress(curAddress,visibleRows-1);
	
	scanFunctions();
}

void CtrlDisassemblyView::calculatePixelPositions()
{
	pixelPositions.addressStart = 16;
	pixelPositions.opcodeStart = pixelPositions.addressStart + 18*charWidth;
	pixelPositions.argumentsStart = pixelPositions.opcodeStart + 9*charWidth;
	pixelPositions.arrowsStart = pixelPositions.argumentsStart + 30*charWidth;
}


void CtrlDisassemblyView::followBranch()
{
	DisassemblyLineInfo line = DisassemblyLineInfo();
	manager.getLine(curAddress,true,line);

	if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO)
	{
		if (line.info.isBranch)
		{
			jumpStack.push_back(curAddress);
			gotoAddress(line.info.branchTarget);
		} else if (line.info.hasRelevantAddress)
		{
			// well, not  exactly a branch, but we can do something anyway	
			postEvent(debEVT_GOTOINMEMORYVIEW,line.info.releventAddress);
		}
	} else if (line.type == DISTYPE_DATA)
	{
		// jump to the start of the current line
		postEvent(debEVT_GOTOINMEMORYVIEW,curAddress);
	}
}

void CtrlDisassemblyView::assembleOpcode(u32 address, std::string defaultText)
{
	u32 encoded;

	if (!cpu->isCpuPaused())
	{
		wxMessageBox( L"Cannot change code while the core is running",  L"Error.", wxICON_ERROR);
		return;
	}

	TextEntryDialog entry(this,L"Assemble opcode",wxString(defaultText.c_str(),wxConvUTF8));
	entry.Layout();

	if (entry.ShowModal() != wxID_OK)
		return;

	wxString op = entry.getText();
	std::string errorText;
	bool result = MipsAssembleOpcode(op.To8BitData(),cpu,address,encoded,errorText);
	if (result)
	{
		SysClearExecutionCache();
		cpu->write32(address,encoded);

		if (address == curAddress)
			gotoAddress(manager.getNthNextAddress(curAddress,1));

		redraw();
	} else {
		wxMessageBox( wxString(errorText.c_str(),wxConvUTF8), L"Error.", wxICON_ERROR);
	}
}


void CtrlDisassemblyView::onPopupClick(wxCommandEvent& evt)
{
	switch (evt.GetId())
	{
	case ID_DISASM_FOLLOWBRANCH:
		followBranch();
		break;
	case ID_DISASM_COPYADDRESS:
		if (wxTheClipboard->Open())
		{
			wchar_t text[64];
			swprintf(text,64,L"%08X",curAddress);

			wxTheClipboard->SetData(new wxTextDataObject(text));
			wxTheClipboard->Close();
		}
		break;
	case ID_DISASM_GOTOINMEMORYVIEW:
		postEvent(debEVT_GOTOINMEMORYVIEW,curAddress);
		break;
	case ID_DISASM_COPYINSTRUCTIONHEX:
		copyInstructions(selectRangeStart,selectRangeEnd,false);
		break;
	case ID_DISASM_COPYINSTRUCTIONDISASM:
		copyInstructions(selectRangeStart,selectRangeEnd,true);
		break;
	case ID_DISASM_SETPCTOHERE:
		cpu->setPc(curAddress);
		redraw();
		break;
	case ID_DISASM_RUNTOHERE:
		postEvent(debEVT_RUNTOPOS,curAddress);
		break;
	case ID_DISASM_DISASSEMBLETOFILE:
		disassembleToFile();
		break;
	case ID_DISASM_RENAMEFUNCTION:
		{		
			u32 funcBegin = symbolMap.GetFunctionStart(curAddress);
			if (funcBegin != 0xFFFFFFFF)
			{
				wxString newName = wxGetTextFromUser(L"Enter the new function name",L"New function name",
					wxString(symbolMap.GetLabelString(funcBegin).c_str(),wxConvUTF8),this);

				if (!newName.empty())
				{
					const wxCharBuffer converted = newName.ToUTF8();
					symbolMap.SetLabelName(converted,funcBegin);
					postEvent(debEVT_MAPLOADED,0);
					redraw();
				}
			}
			else
			{
				wxMessageBox(L"No symbol selected",L"Error",wxICON_ERROR);
			}
			break;
		}
	case ID_DISASM_REMOVEFUNCTION:
		{
			u32 funcBegin = symbolMap.GetFunctionStart(curAddress);
			if (funcBegin != 0xFFFFFFFF)
			{
				u32 prevBegin = symbolMap.GetFunctionStart(funcBegin-1);
				if (prevBegin != 0xFFFFFFFF)
				{
					u32 expandedSize = symbolMap.GetFunctionSize(prevBegin)+symbolMap.GetFunctionSize(funcBegin);
					symbolMap.SetFunctionSize(prevBegin,expandedSize);
				}
					
				symbolMap.RemoveFunction(funcBegin,true);
				symbolMap.SortSymbols();
				symbolMap.UpdateActiveSymbols();
				manager.clear();
					
				postEvent(debEVT_MAPLOADED,0);
			}
			else
			{
				postEvent(debEVT_SETSTATUSBARTEXT,L"WARNING: unable to find function symbol here");
			}

			redraw();
			break;
		}
	case ID_DISASM_ADDFUNCTION:
		{
			u32 prevBegin = symbolMap.GetFunctionStart(curAddress);
			if (prevBegin != 0xFFFFFFFF)
			{
				if (prevBegin == curAddress)
				{
					postEvent(debEVT_SETSTATUSBARTEXT,L"WARNING: There's already a function entry point at this adress");
				}
				else
				{
					char symname[128];
					u32 prevSize = symbolMap.GetFunctionSize(prevBegin);
					u32 newSize = curAddress-prevBegin;
					symbolMap.SetFunctionSize(prevBegin,newSize);

					newSize = prevSize-newSize;
					sprintf(symname,"u_un_%08X",curAddress);
					symbolMap.AddFunction(symname,curAddress,newSize);
					symbolMap.SortSymbols();
					symbolMap.UpdateActiveSymbols();
					manager.clear();
						
					postEvent(debEVT_MAPLOADED,0);
				}
			}
			else
			{
				char symname[128];
				int newSize = selectRangeEnd - selectRangeStart;
				sprintf(symname, "u_un_%08X", selectRangeStart);
				symbolMap.AddFunction(symname, selectRangeStart, newSize);
				symbolMap.SortSymbols();
				symbolMap.UpdateActiveSymbols();
					
				postEvent(debEVT_MAPLOADED,0);
			}

			redraw();
			break;
		}
	case ID_DISASM_ASSEMBLE:
		assembleOpcode(curAddress, disassembleCurAddress());
		break;
	default:
		wxMessageBox( L"Unimplemented.",  L"Unimplemented.", wxICON_INFORMATION);
		break;
	}
}

void CtrlDisassemblyView::keydownEvent(wxKeyEvent& evt)
{
	u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows);

	if (evt.ControlDown())
	{
		switch (evt.GetKeyCode())
		{
		case 'd':
		case 'D':
			toggleBreakpoint(true);
			break;
		case 'e':
		case 'E':
			editBreakpoint();
			break;
		case 'b':
		case 'B':
			{
				BreakpointWindow bpw(this,cpu);
				if (bpw.ShowModal() == wxID_OK)
				{
					bpw.addBreakpoint();
					postEvent(debEVT_UPDATE,0);
				}
			}
			break;
		case 'g':
		case 'G':
			{
				u64 addr;
				if (!executeExpressionWindow(this,cpu,addr))
					return;
				gotoAddress(addr);
			}
			break;
		default:
			evt.Skip();
			break;
		}
	} else {
		if (evt.GetEventType() == wxEVT_CHAR && evt.GetKeyCode() >= 0x20 && evt.GetKeyCode() < 0x80)
		{
			std::string str;
			str += (char) evt.GetKeyCode();

			assembleOpcode(curAddress,str);
			return;
		}

		switch (evt.GetKeyCode())
		{
		case WXK_LEFT:
			if (jumpStack.empty())
			{
				gotoAddress(cpu->getPC());
			} else {
				u32 addr = jumpStack[jumpStack.size()-1];
				jumpStack.pop_back();
				gotoAddress(addr);
			}
			break;
		case WXK_RIGHT:
			followBranch();
			break;
		case WXK_UP:
			setCurAddress(manager.getNthPreviousAddress(curAddress,1), wxGetKeyState(WXK_SHIFT));
			scrollAddressIntoView();
			scanFunctions();
			break;
		case WXK_DOWN:
			setCurAddress(manager.getNthNextAddress(curAddress,1), wxGetKeyState(WXK_SHIFT));
			scrollAddressIntoView();
			scanFunctions();
			break;
		case WXK_TAB:
			displaySymbols = !displaySymbols;
			break;
		case WXK_PAGEUP:
			if (curAddress != windowStart && curAddressIsVisible()) {
				setCurAddress(windowStart, wxGetKeyState(WXK_SHIFT));
				scrollAddressIntoView();
			} else {
				setCurAddress(manager.getNthPreviousAddress(windowStart,visibleRows), wxGetKeyState(WXK_SHIFT));
				scrollAddressIntoView();
			}
			scanFunctions();
			break;
		case WXK_PAGEDOWN:
			if (manager.getNthNextAddress(curAddress,1) != windowEnd && curAddressIsVisible()) {
				setCurAddress(manager.getNthPreviousAddress(windowEnd,1), wxGetKeyState(WXK_SHIFT));
				scrollAddressIntoView();
			} else {
				setCurAddress(manager.getNthNextAddress(windowEnd,visibleRows-1), wxGetKeyState(WXK_SHIFT));
				scrollAddressIntoView();
			}
			scanFunctions();
			break;
		case WXK_F8:
			postEvent(debEVT_STEPOUT,0);
			break;
		case WXK_F10:
			postEvent(debEVT_STEPOVER,0);
			return;
		case WXK_F11:
			if (evt.ShiftDown())
				postEvent(debEVT_STEPOUT,0);
			else
				postEvent(debEVT_STEPINTO,0);
			return;
		default:
			evt.Skip();
			break;
		}
	}

	redraw();
}

void CtrlDisassemblyView::scrollbarEvent(wxScrollWinEvent& evt)
{
	int type = evt.GetEventType();
	if (type == wxEVT_SCROLLWIN_LINEUP)
	{
		windowStart = manager.getNthPreviousAddress(windowStart,1);
		scanFunctions();
	} else if (type == wxEVT_SCROLLWIN_LINEDOWN)
	{
		windowStart = manager.getNthNextAddress(windowStart,1);
		scanFunctions();
	} else if (type == wxEVT_SCROLLWIN_PAGEUP)
	{
		windowStart = manager.getNthPreviousAddress(windowStart,visibleRows);
		scanFunctions();
	} else if (type == wxEVT_SCROLLWIN_PAGEDOWN)
	{
		windowStart = manager.getNthNextAddress(windowStart,visibleRows);
		scanFunctions();
	}

	redraw();
}

void CtrlDisassemblyView::toggleBreakpoint(bool toggleEnabled)
{
	bool enabled;
	if (CBreakPoints::IsAddressBreakPoint(curAddress,&enabled))
	{
		if (!enabled)
		{
			// enable disabled breakpoints
			CBreakPoints::ChangeBreakPoint(curAddress,true);
		} else if (!toggleEnabled && CBreakPoints::GetBreakPointCondition(curAddress) != NULL)
		{
			// don't just delete a breakpoint with a custom condition
			CBreakPoints::RemoveBreakPoint(curAddress);
		} else if (toggleEnabled)
		{
			// disable breakpoint
			CBreakPoints::ChangeBreakPoint(curAddress,false);
		} else {
			// otherwise just remove breakpoint
			CBreakPoints::RemoveBreakPoint(curAddress);
		}
	} else {
		CBreakPoints::AddBreakPoint(curAddress);
	}
}


void CtrlDisassemblyView::updateStatusBarText()
{
	char text[512];
	DisassemblyLineInfo line = DisassemblyLineInfo();
	manager.getLine(curAddress,true,line);
	
	text[0] = 0;
	if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO)
	{
		if (line.info.isDataAccess)
		{
			if (!cpu->isValidAddress(line.info.dataAddress))
			{
				sprintf(text,"Invalid address %08X",line.info.dataAddress);
			} else if (line.info.lrType == MIPSAnalyst::LOADSTORE_NORMAL && line.info.dataAddress % line.info.dataSize)
			{
				sprintf(text,"Unaligned address %08X",line.info.dataAddress);
			} else {
				switch (line.info.dataSize)
				{
				case 1:
					sprintf(text,"[%08X] = %02X",line.info.dataAddress,cpu->read8(line.info.dataAddress));
					break;
				case 2:
					sprintf(text,"[%08X] = %04X",line.info.dataAddress,cpu->read16(line.info.dataAddress));
					break;
				case 4:
					{
						u32 data;
						if (line.info.lrType != MIPSAnalyst::LOADSTORE_NORMAL)
						{
							u32 address = line.info.dataAddress;
							data = cpu->read32(address & ~3) >> (address & 3) * 8;
							data |= cpu->read32((address + 3) & ~3) << (4 - (address & 3)) * 8;
						} else {
							data = cpu->read32(line.info.dataAddress);
						}

						const std::string addressSymbol = symbolMap.GetLabelString(data);
						if (!addressSymbol.empty())
						{
							sprintf(text,"[%08X] = %s (%08X)",line.info.dataAddress,addressSymbol.c_str(),data);
						} else {
							sprintf(text,"[%08X] = %08X",line.info.dataAddress,data);
						}
						break;
					}
				case 8:
					{
						u64 data;
						if (line.info.lrType != MIPSAnalyst::LOADSTORE_NORMAL)
						{
							u32 address = line.info.dataAddress;
							data = cpu->read64(address & ~7) >> (address & 7) * 8;
							data |= cpu->read64((address + 7) & ~7) << (8 - (address & 7)) * 8;
						} else {
							data = cpu->read64(line.info.dataAddress);
						}

						sprintf(text,"[%08X] = %016" PRIX64,line.info.dataAddress,data);
						break;
					}
				case 16:
					{
						__aligned16 u128 data = cpu->read128(line.info.dataAddress);
						sprintf(text,"[%08X] = %016" PRIX64 "%016" PRIX64,line.info.dataAddress,data._u64[1],data._u64[0]);
						break;
					}
				}
			}
		}

		if (line.info.isBranch)
		{
			const std::string addressSymbol = symbolMap.GetLabelString(line.info.branchTarget);
			if (addressSymbol.empty())
			{
				sprintf(text,"%08X",line.info.branchTarget);
			} else {
				sprintf(text,"%08X = %s",line.info.branchTarget,addressSymbol.c_str());
			}
		}
	} else if (line.type == DISTYPE_DATA)
	{
		u32 start = symbolMap.GetDataStart(curAddress);
		if (start == 0xFFFFFFFF)
			start = curAddress;

		u32 diff = curAddress-start;
		const std::string label = symbolMap.GetLabelString(start);

		if (!label.empty())
		{
			if (diff != 0)
				sprintf(text,"%08X (%s) + %08X",start,label.c_str(),diff);
			else
				sprintf(text,"%08X (%s)",start,label.c_str());
		} else {
			if (diff != 0)
				sprintf(text,"%08X + %08X",start,diff);
			else
				sprintf(text,"%08X",start);
		}
	}

	postEvent(debEVT_SETSTATUSBARTEXT,wxString(text,wxConvUTF8));
}

void CtrlDisassemblyView::mouseEvent(wxMouseEvent& evt)
{
	// left button
	wxEventType type = evt.GetEventType();
	bool hasFocus = wxWindow::FindFocus() == this;

	if (type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_DCLICK || type == wxEVT_RIGHT_DOWN )
	{
		u32 newAddress = yToAddress(evt.GetY());
		bool extend = wxGetKeyState(WXK_SHIFT);

		if (type == wxEVT_RIGHT_DOWN)
		{
			// Maintain the current selection if right clicking into it.
			if (newAddress >= selectRangeStart && newAddress < selectRangeEnd)
				extend = true;
		} else {
			if (curAddress == newAddress && hasFocus)
				toggleBreakpoint(false);
		}

		setCurAddress(newAddress,extend);
		SetFocus();
		SetFocusFromKbd();
	} else if (evt.GetEventType() == wxEVT_RIGHT_UP)
	{
		PopupMenu(&menu,evt.GetPosition());
		return;
	} else if (evt.GetEventType() == wxEVT_MOUSEWHEEL)
	{
		if (evt.GetWheelRotation() > 0)
		{
			windowStart = manager.getNthPreviousAddress(windowStart,3);
			scanFunctions();
		} else if (evt.GetWheelRotation() < 0) {
			windowStart = manager.getNthNextAddress(windowStart,3);
			scanFunctions();
		}
	} else if (evt.GetEventType() == wxEVT_MOTION)
	{
		if (evt.ButtonIsDown(wxMOUSE_BTN_LEFT))
		{
			int newAddress = yToAddress(evt.GetY());
			setCurAddress(newAddress,wxGetKeyState(WXK_SHIFT));
		} else
			return;
	} else {
		evt.Skip();
		return;
	}

	redraw();
}

void CtrlDisassemblyView::sizeEvent(wxSizeEvent& evt)
{
	wxSize s = evt.GetSize();
	visibleRows = s.GetWidth()/rowHeight;
}

u32 CtrlDisassemblyView::yToAddress(int y)
{
	int line = y/rowHeight;
	return manager.getNthNextAddress(windowStart,line);
}

bool CtrlDisassemblyView::curAddressIsVisible()
{
	u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows);
	return curAddress >= windowStart && curAddress < windowEnd;
}

void CtrlDisassemblyView::scrollStepping(u32 newPc)
{
	u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows);

	newPc = manager.getStartAddress(newPc);
	if (newPc >= windowEnd || newPc >= manager.getNthPreviousAddress(windowEnd,1))
	{
		windowStart = manager.getNthPreviousAddress(newPc,visibleRows-2);
	}
}

std::string CtrlDisassemblyView::disassembleRange(u32 start, u32 size)
{
	std::string result;

	// gather all branch targets without labels
	std::set<u32> branchAddresses;
	for (u32 i = 0; i < size; i += 4)
	{
		MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(cpu,start+i);

		if (info.isBranch && symbolMap.GetLabelString(info.branchTarget).empty())
		{
			if (branchAddresses.find(info.branchTarget) == branchAddresses.end())
			{
				branchAddresses.insert(info.branchTarget);
			}
		}
	}

	u32 disAddress = start;
	bool previousLabel = true;
	DisassemblyLineInfo line = DisassemblyLineInfo();
	while (disAddress < start+size)
	{
		char addressText[64],buffer[512];

		manager.getLine(disAddress,displaySymbols,line);
		bool isLabel = getDisasmAddressText(disAddress,addressText,false,line.type == DISTYPE_OPCODE);

		if (isLabel)
		{
			if (!previousLabel) result += "\r\n";
			sprintf(buffer,"%s\r\n\r\n",addressText);
			result += buffer;
		} else if (branchAddresses.find(disAddress) != branchAddresses.end())
		{
			if (!previousLabel) result += "\r\n";
			sprintf(buffer,"pos_%08X:\r\n\r\n",disAddress);
			result += buffer;
		}

		if (line.info.isBranch && !line.info.isBranchToRegister
			&& symbolMap.GetLabelString(line.info.branchTarget).empty()
			&& branchAddresses.find(line.info.branchTarget) != branchAddresses.end())
		{
			sprintf(buffer,"pos_%08X",line.info.branchTarget);
			line.params = line.params.substr(0,line.params.find("0x")) + buffer;
		}

		sprintf(buffer,"\t%s\t%s\r\n",line.name.c_str(),line.params.c_str());
		result += buffer;
		previousLabel = isLabel;
		disAddress += line.totalSize;
	}

	return result;
}

std::string CtrlDisassemblyView::disassembleCurAddress()
{
	DisassemblyLineInfo line = DisassemblyLineInfo();
	manager.getLine(curAddress, displaySymbols, line);
	return line.name + (line.params.length() > 0 ? " " + line.params : "");
}

void CtrlDisassemblyView::copyInstructions(u32 startAddr, u32 endAddr, bool withDisasm)
{
	if (!wxTheClipboard->Open())
	{
		wxMessageBox( L"Could not open clipboard.",  L"Error", wxICON_ERROR);
		return;
	}

	if (!withDisasm)
	{
		int instructionSize = 4;
		int count = (endAddr - startAddr) / instructionSize;
		int space = count * 32;
		char *temp = new char[space];

		char *p = temp;
		for (u32 pos = startAddr; pos < endAddr; pos += instructionSize)
		{
			p += sprintf(p, "%08X", cpu->read32(pos));

			// Don't leave a trailing newline.
			if (pos + instructionSize < endAddr)
				p += sprintf(p,"\r\n");
		}
		
		wxTheClipboard->SetData(new wxTextDataObject(wxString(temp,wxConvUTF8)));
		delete [] temp;
	} else
	{
		std::string disassembly = disassembleRange(startAddr,endAddr-startAddr);
		wxTheClipboard->SetData(new wxTextDataObject(wxString(disassembly.c_str(),wxConvUTF8)));
	}

	wxTheClipboard->Close();
}

void CtrlDisassemblyView::disassembleToFile()
{
	wxFileDialog dlg(this,wxEmptyString,wxEmptyString,wxEmptyString,L"*.*",wxFD_SAVE);

	if (dlg.ShowModal() == wxID_CANCEL)
		return;
	
	std::string disassembly = disassembleRange(selectRangeStart,selectRangeEnd-selectRangeStart);
	wxFile output(dlg.GetPath(),wxFile::write);
	output.Write(wxString(disassembly.c_str(),wxConvUTF8));
}

void CtrlDisassemblyView::editBreakpoint()
{
	BreakpointWindow win(this,cpu);

	bool exists = false;
	if (CBreakPoints::IsAddressBreakPoint(curAddress))
	{
		auto breakpoints = CBreakPoints::GetBreakpoints();
		for (size_t i = 0; i < breakpoints.size(); i++)
		{
			if (breakpoints[i].addr == curAddress)
			{
				win.loadFromBreakpoint(breakpoints[i]);
				exists = true;
				break;
			}
		}
	}

	if (!exists)
		win.initBreakpoint(curAddress);

	if (win.ShowModal() == wxID_OK)
	{
		if (exists)
			CBreakPoints::RemoveBreakPoint(curAddress);
		win.addBreakpoint();	
		postEvent(debEVT_UPDATE,0);
	}
}

void CtrlDisassemblyView::getOpcodeText(u32 address, char* dest)
{
	DisassemblyLineInfo line = DisassemblyLineInfo();
	address = manager.getStartAddress(address);
	manager.getLine(address,displaySymbols,line);
	sprintf(dest,"%s  %s",line.name.c_str(),line.params.c_str());
}
