/*
Copyright (C) 2006 - 2009 Evan Teran
                   eteran@alum.rit.edu

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "DebuggerMain.h"
#include "AnalyzerInterface.h"
#include "ArchProcessorInterface.h"
#include "BinaryInfo.h"
#include "ByteShiftArray.h"
#include "CommentServer.h"
#include "Configuration.h"
#include "DebuggerCoreInterface.h"
#include "Debugger.h"
#include "DebuggerInternal.h"
#include "DebuggerOps.h"
#include "DebuggerPluginInterface.h"
#include "DialogArguments.h"
#include "DialogAttach.h"
#include "DialogMemoryRegions.h"
#include "DialogOptions.h"
#include "DialogPlugins.h"
#include "Expression.h"
#include "Instruction.h"
#include "QHexView.h"
#include "RecentFileManager.h"
#include "SessionFileInterface.h"
#include "State.h"
#include "SymbolManager.h"
#include "version.h"

#include <QCloseEvent>
#include <QDir>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QMessageBox>
#include <QSettings>
#include <QStringListModel>
#include <QtDebug>
#include <QTimer>
#include <QToolButton>
#include <QVector>

#include <boost/bind.hpp>
#include <memory>

//#define ENABLE_THREAD_DEBUGGING

//------------------------------------------------------------------------------
// Name: DebuggerMain(QWidget *parent)
// Desc: constructor
//------------------------------------------------------------------------------
DebuggerMain::DebuggerMain(QWidget *parent) : DebuggerUI(parent), 
		m_DialogArguments(new DialogArguments),
		m_Timer(new QTimer(this)),
		m_RecentFileManager(new RecentFileManager(this)),
		m_StackCommentServer(new CommentServer),
		m_StackViewLocked(false),
		m_StepRun(false) {

	setupUI();
	
	// TODO: not ready yet
#ifndef ENABLE_THREAD_DEBUGGING
	ui.comboThreads->hide();
	ui.labelThreads->hide();
#endif

	m_ThreadListModel = new QStringListModel(this);
	ui.comboThreads->setModel(m_ThreadListModel);

	// connect the timer to the debug event 
	connect(m_Timer, SIGNAL(timeout()), this, SLOT(nextDebugEvent()));
	
	// create a context menu for the tab bar as well
	connect(ui.tabWidget, SIGNAL(customContextMenuRequested(int, const QPoint &)), this, SLOT(tabContextMenu(int, const QPoint &)));
		
	// setup the list model for instruction details list
	m_ListModel = new QStringListModel(this);
	ui.listView->setModel(m_ListModel);
	
	// setup the recent file manager
	ui.action_Recent_Files->setMenu(m_RecentFileManager->createMenu());
	connect(m_RecentFileManager, SIGNAL(fileSelected(const QString &)), SLOT(doOpenFile(const QString &)));

	// make us the default event handler
	edb::v1::setDebugEventHandler(this);

	// enable the arch processor
	edb::v1::archProcessor().setupRegisterView(ui.registerList);
			
	// default the working directory to ours
	m_WorkingDirectory = QDir().absolutePath();
}

//------------------------------------------------------------------------------
// Name: setupUI()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::setupUI() {
	// setup the global pointers as early as possible. 
	// NOTE:  this should never be changed after this point
	// NOTE:  this is important that this happens BEFORE any components which
	// read settings as it could end up being a memory leak (and therefore never
	// calling it's destructor which writes the settings to disk!).
	edb::v1::debuggerUI = this;

	ui.setupUi(this);
	
	// add toggles for the dock windows
	ui.menu_View->addAction(ui.registersDock->toggleViewAction());
	ui.menu_View->addAction(ui.dataDock->toggleViewAction());
	ui.menu_View->addAction(ui.stackDock->toggleViewAction());
	ui.menu_View->addAction(ui.toolBar->toggleViewAction());
	
	// make sure our widgets use custom context menus
	ui.registerList->setContextMenuPolicy(Qt::CustomContextMenu);
	ui.cpuView->setContextMenuPolicy(Qt::CustomContextMenu);
	
	// set the listbox to about 4 lines
	const QFontMetrics &fm = ui.listView->fontMetrics();
	ui.listView->setFixedHeight(fm.height() * 4);
	
	setupStackView();
	setupTabButtons();
	
	// remove the one in the designer and put in our built one.
	// it's a bit ugly to do it this way, but the designer wont let me 
	// make a tabless entry..and the ones i create look slightly diff 
	// (probably lack of layout manager in mine...
	ui.tabWidget->clear();
	mnuDumpCreateTab();
	
	// apply any fonts we may have stored
	doApplyDefaultFonts();
	
	// apply the default setting for showing address seperators
	doApplyDefaultShowSeperator();
}

//------------------------------------------------------------------------------
// Name: setupStackView()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::setupStackView() {
	
	m_StackView = QSharedPointer<QHexView>(new QHexView(this));
	ui.stackDock->setWidget(m_StackView.data());
	
	// setup the context menu
	m_StackView->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_StackView.data(), SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(mnuStackContextMenu(const QPoint &)));
	
	// we placed a view in the designer, so just set it here
	// this may get transitioned to heap allocated, we'll see
	m_StackViewInfo.view = m_StackView;
	
	// setup the comment server for the stack viewer
	m_StackView->setCommentServer(m_StackCommentServer);
	
}

//------------------------------------------------------------------------------
// Name: ~DebuggerMain()
// Desc: destructor
//------------------------------------------------------------------------------
DebuggerMain::~DebuggerMain() {
	doDetachFromProcess(edb::v1::config().closeBehaviour == Configuration::Terminate);
}

//------------------------------------------------------------------------------
// Name: closeEvent(QCloseEvent *)
// Desc: triggered on main window close, saves window state
//------------------------------------------------------------------------------
void DebuggerMain::closeEvent(QCloseEvent *event) {
	QSettings settings;
	const QByteArray state = saveState();
	settings.beginGroup("Window");
	settings.setValue("window.state", state);
	settings.setValue("window.width", width());
	settings.setValue("window.height", height());
	settings.setValue("window.stack.show_address.enabled", m_StackView->showAddress());
	settings.setValue("window.stack.show_hex.enabled", m_StackView->showHexDump());
	settings.setValue("window.stack.show_ascii.enabled", m_StackView->showAsciiDump());
	settings.setValue("window.stack.show_comments.enabled", m_StackView->showComments());
	settings.setValue("window.stack.row_width", m_StackView->rowWidth());
	settings.setValue("window.stack.word_width", m_StackView->wordWidth());
	settings.endGroup();
	event->accept();
}

//------------------------------------------------------------------------------
// Name: showEvent(QShowEvent *)
// Desc: triggered on show, restores window state
//------------------------------------------------------------------------------
void DebuggerMain::showEvent(QShowEvent *) {
	QSettings settings;
	settings.beginGroup("Window");
	const QByteArray state	= settings.value("window.state").value<QByteArray>();
	const int width			= settings.value("window.width", -1).value<int>();
	const int height		= settings.value("window.height", -1).value<int>();
	
	if(width != -1) {
		resize(width, size().height());
	}
	
	if(height != -1) {
		resize(size().width(), height);
	}
	
	m_StackView->setShowAddress(settings.value("window.stack.show_address.enabled", true).value<bool>());
	m_StackView->setShowHexDump(settings.value("window.stack.show_hex.enabled", true).value<bool>());
	m_StackView->setShowAsciiDump(settings.value("window.stack.show_ascii.enabled", false).value<bool>());
	m_StackView->setShowComments(settings.value("window.stack.show_comments.enabled", true).value<bool>());

	int rowWidth = settings.value("window.stack.row_width", 1).value<int>();
	int wordWidth = settings.value("window.stack.word_width", QT_POINTER_SIZE).value<int>();
	
	// normalize values
	if(wordWidth != 1 && wordWidth != 2 && wordWidth != 4 && wordWidth != 8) {
		wordWidth = QT_POINTER_SIZE;
	}
	
	if(rowWidth != 1 && rowWidth != 2 && rowWidth != 4 && rowWidth != 8 && rowWidth != 16) {
		rowWidth = 1;
	}
	
	m_StackView->setRowWidth(rowWidth);
	m_StackView->setWordWidth(wordWidth);
	
	settings.endGroup();
	restoreState(state);
}

//------------------------------------------------------------------------------
// Name: on_actionAbout_QT_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_actionAbout_QT_triggered() {
	QMessageBox::aboutQt(this, tr("About QT"));
}

//------------------------------------------------------------------------------
// Name: doApplyDefaultFonts()
// Desc: applies the confirations fonts to all neccessary widgets
//------------------------------------------------------------------------------
void DebuggerMain::doApplyDefaultFonts() {

	QFont font;
	const Configuration &config = edb::v1::config();

	// set some default fonts
	if(font.fromString(config.stackFont)) {
		m_StackView->setFont(font);
	}
	
	if(font.fromString(config.registersFont)) {
		ui.registerList->setFont(font);
	}

	if(font.fromString(config.disassemblyFont)) {
		ui.cpuView->setFont(font);
	}
	
	if(font.fromString(config.dataFont)) {
		Q_FOREACH(QSharedPointer<DataViewInfo> dataView, m_DataRegions) {
			dataView->view->setFont(font);
		}
	}
}

//------------------------------------------------------------------------------
// Name: setupTabButtons()
// Desc: creates the add/remove tab buttons in the data view
//------------------------------------------------------------------------------
void DebuggerMain::setupTabButtons() {
	// add the corner widgets to the data view
	m_AddTab = new QToolButton(ui.tabWidget);
	m_DelTab = new QToolButton(ui.tabWidget);
	
	m_AddTab->setToolButtonStyle(Qt::ToolButtonIconOnly);
	m_DelTab->setToolButtonStyle(Qt::ToolButtonIconOnly);
	m_AddTab->setIcon(QIcon(":/debugger/images/edb16-addtab.png"));
	m_DelTab->setIcon(QIcon(":/debugger/images/edb16-deltab.png"));
	m_AddTab->setAutoRaise(true);
	m_DelTab->setAutoRaise(true);
	m_AddTab->setEnabled(false);
	m_DelTab->setEnabled(false);
	
	ui.tabWidget->setCornerWidget(m_AddTab, Qt::TopLeftCorner);
	ui.tabWidget->setCornerWidget(m_DelTab, Qt::TopRightCorner);
	
	connect(m_AddTab, SIGNAL(clicked()), SLOT(mnuDumpCreateTab()));
	connect(m_DelTab, SIGNAL(clicked()), SLOT(mnuDumpDeleteTab()));
}

//------------------------------------------------------------------------------
// Name: on_registerList_customContextMenuRequested(const QPoint &pos)
// Desc: context menu handler for register view
//------------------------------------------------------------------------------
void DebuggerMain::on_registerList_customContextMenuRequested(const QPoint &pos) {
	QTreeWidgetItem *const item = ui.registerList->itemAt(pos);
	if(item != 0 && !ui.registerList->isCatagory(item)) {		
		// a little bit cheesy of a solution, but should work nicely
		const Register value = edb::v1::archProcessor().valueFromItem(*item);
		if(value.valid() && value.isType(Register::TYPE_GPR | Register::TYPE_IP | Register::TYPE_COND)) {
			QMenu menu;
			menu.addAction(tr("&Follow In Dump"), this, SLOT(mnuRegisterFollowInDump()));
			menu.addAction(tr("&Follow In Dump (New Tab)"), this, SLOT(mnuRegisterFollowInDumpNewTab()));
			menu.addAction(tr("&Follow In Stack"), this, SLOT(mnuRegisterFollowInStack()));

			doAddPluginContextMenu<&DebuggerPluginInterface::registerViewContextMenu>(&menu);

			menu.exec(ui.registerList->mapToGlobal(pos));
		}
	}
}

//------------------------------------------------------------------------------
// Name: on_cpuView_breakPointToggled(edb::address_t address)
// Desc: handler for toggling the breakpoints
//------------------------------------------------------------------------------
void DebuggerMain::on_cpuView_breakPointToggled(edb::address_t address) {
	edb::v1::toggleBreakpoint(address);
}

//------------------------------------------------------------------------------
// Name: on_registerList_itemDoubleClicked(QTreeWidgetItem *item)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_registerList_itemDoubleClicked(QTreeWidgetItem *item) {	
	Q_CHECK_PTR(item);
	const Register reg = edb::v1::archProcessor().valueFromItem(*item);
	
	if(reg.valid() && reg.isType(Register::TYPE_ANY)) {
		edb::reg_t r = reg.value();
		if(edb::v1::getValueFromUser(r, tr("Register Value"))) {
		
			State state;
			edb::v1::debuggerBase->getState(state);
			state.setRegister(reg.name(), r);
			edb::v1::debuggerBase->setState(state);
			updateGUI();
		}
	}
}

//------------------------------------------------------------------------------
// Name: on_action_About_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_About_triggered() {
	QMessageBox::about(this, tr("About EDB"),
		tr(
		"<p>EDB (Evan's Debugger) is designed to be an easy to use, modular, and cross platform debugger.</p>"
		"<p>More information and updates can be found at <a href=\"http://www.codef00.com\">http://www.codef00.com</a></p>"
		"<p>You can also report bugs an feature requests at <a href=\"http://bugs.codef00.com\">http://bugs.codef00.com</a></p>"
		"<p>Written by Evan Teran.</p>"
		"<p>version: %1</p>"
		).arg(version_string));
}

//------------------------------------------------------------------------------
// Name: doApplyDefaultShowSeperator()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doApplyDefaultShowSeperator() {
	const bool show = edb::v1::config().showAddressSeperator;
	
	ui.cpuView->setShowAddressSeparator(show);
	m_StackView->setShowAddressSeparator(show);
	Q_FOREACH(QSharedPointer<DataViewInfo> dataView, m_DataRegions) {
		dataView->view->setShowAddressSeparator(show);
	}
}

//------------------------------------------------------------------------------
// Name: on_action_Configure_Debugger_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Configure_Debugger_triggered() {
	
	edb::v1::dialogOptions()->exec();

	// reload symbols in case they changed, or our symbol files changes
	edb::v1::reloadSymbols();

	// re-read the memory region information
	edb::v1::memoryRegions().sync();

	// apply the selected fonts
	doApplyDefaultFonts();

	// apply changes to the GUI options
	doApplyDefaultShowSeperator();

	// show changes
	refreshGUI();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doMemoryFollow(bool ok, edb::address_t address, bool (*follow_func)(edb::address_t)) {
	if(ok && !(*follow_func)(address)) {
		QMessageBox::information(this, 
			tr("No Memory Found"), 
			tr("There appears to be no memory at that location (<strong>0x%1</strong>)").arg(edb::v1::formatPointer(address)));
	}
}

//------------------------------------------------------------------------------
// Name: followRegisterInDump(bool tabbed)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::followRegisterInDump(bool tabbed) {
	bool ok;
	const edb::address_t address = get_follow_register(ok);
	if(ok && !edb::v1::dumpData(address, tabbed)) {
		QMessageBox::information(this, 
			tr("No Memory Found"), 
			tr("There appears to be no memory at that location (<strong>0x%1</strong>)").arg(edb::v1::formatPointer(address)));
	}
}

//------------------------------------------------------------------------------
// Name: mnuStackGotoESP()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackGotoESP() {
	doMemoryFollow(true, edb::v1::currentState().stackPointer(), edb::v1::dumpStack);
}

//------------------------------------------------------------------------------
// Name: mnuStackGotoEBP()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackGotoEBP() {
	doMemoryFollow(true, edb::v1::currentState().framePointer(), edb::v1::dumpStack);
}

//------------------------------------------------------------------------------
// Name: mnuCPUJumpToEIP()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUJumpToEIP() {
	doMemoryFollow(true, edb::v1::currentState().instructionPointer(), edb::v1::jumpToAddress);
}

//------------------------------------------------------------------------------
// Name: mnuCPUJumpToAddress()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUJumpToAddress() {
	bool ok;
	const edb::address_t address = get_goto_expression(ok);
	doMemoryFollow(ok, address, edb::v1::jumpToAddress);
}

//------------------------------------------------------------------------------
// Name: mnuDumpGotoAddress()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpGotoAddress() {
    bool ok;
	const edb::address_t address = get_goto_expression(ok);
	doMemoryFollow(ok, address, edb::v1::dumpData);
}

//------------------------------------------------------------------------------
// Name: mnuStackGotoAddress()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackGotoAddress() {
    bool ok;
	const edb::address_t address = get_goto_expression(ok);
	doMemoryFollow(ok, address, edb::v1::dumpStack);
}

//------------------------------------------------------------------------------
// Name: mnuRegisterFollowInStack()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuRegisterFollowInStack() {
	bool ok;
	const edb::address_t address = get_follow_register(ok);
	doMemoryFollow(ok, address, edb::v1::dumpStack);
}

//------------------------------------------------------------------------------
// Name: doFollowInStack(const T &hv)
// Desc: 
//------------------------------------------------------------------------------
template <class T>
void DebuggerMain::doFollowInStack(const T &hv) {
	bool ok;
	const edb::address_t address = get_follow_address(hv, ok);
	doMemoryFollow(ok, address, edb::v1::dumpStack);
}

//------------------------------------------------------------------------------
// Name: doFollowInDump(const T &hv)
// Desc: 
//------------------------------------------------------------------------------
template <class T>
void DebuggerMain::doFollowInDump(const T &hv) {
	bool ok;
	const edb::address_t address = get_follow_address(hv, ok);
	doMemoryFollow(ok, address, edb::v1::dumpData);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpFollowInDump() {
	doFollowInDump(qobject_cast<QHexView *>(ui.tabWidget->currentWidget()));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpFollowInStack() {
	doFollowInStack(qobject_cast<QHexView *>(ui.tabWidget->currentWidget()));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackFollowInDump() {
	doFollowInDump(m_StackView);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackFollowInStack() {
	doFollowInStack(m_StackView);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_actionApplication_Arguments_triggered() {
	m_DialogArguments->exec();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_actionApplication_Working_Directory_triggered() {
	const QString new_dir = QFileDialog::getExistingDirectory(
    		this,
    		tr("Application Working Directory"),
    		QString(),
    		QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);

	if(!new_dir.isEmpty()) {
		m_WorkingDirectory = new_dir;
	}
}

//------------------------------------------------------------------------------
// Name: mnuStackPush()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackPush() {
	edb::reg_t value = 0;
	State state;
	edb::v1::debuggerBase->getState(state);
	
	// ask for a replacement
	if(edb::v1::getValueFromUser(value, tr("Enter value to push"))) {

		// if they said ok, do the push, just like the hardware would do
		edb::v1::pushValue(state, value);

		// update the state
		edb::v1::debuggerBase->setState(state);
		updateGUI();
	}
}

//------------------------------------------------------------------------------
// Name: mnuStackPop()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackPop() {
	State state;
	edb::v1::debuggerBase->getState(state);
	edb::v1::popValue(state);
	edb::v1::debuggerBase->setState(state);
	updateGUI();
}

//------------------------------------------------------------------------------
// Name: on_cpuView_customContextMenuRequested(const QPoint &pos)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_cpuView_customContextMenuRequested(const QPoint &pos) {
	QMenu menu;
	// TODO: add comment
	//menu.addAction(tr("Add &Comment"), this, SLOT(""));
	//menu.addSeparator();
	menu.addAction(tr("&Goto Address"), this, SLOT(mnuCPUJumpToAddress()));
	menu.addAction(tr("&Goto EIP"), this, SLOT(mnuCPUJumpToEIP()));
	
	const edb::address_t address 	= ui.cpuView->selectedAddress();
	int size						= ui.cpuView->selectedSize();

	quint8 buffer[edb::Instruction::MAX_SIZE + 1];
	if(edb::v1::getInstructionBytes(address, buffer, size)) {
		edb::Instruction insn(buffer, size, address, std::nothrow);
		if(insn.valid()) {
		
			switch(insn.type()) {
			case edb::Instruction::OP_JMP:
			case edb::Instruction::OP_CALL:
			case edb::Instruction::OP_JCC:
				if(insn.operand(0).generalType() == edb::Operand::TYPE_REL) {
					QAction *const action = menu.addAction(tr("&Follow"), this, SLOT(mnuCPUFollow()));
					action->setData(static_cast<qlonglong>(insn.operand(0).relativeTarget()));
				}
				break;
			default:
				for(std::size_t i = 0; i < insn.operandCount(); ++i) {
					if(insn.operand(i).generalType() == edb::Operand::TYPE_IMMEDIATE) {
						QAction *const action = menu.addAction(tr("Follow Constant In &Dump"), this, SLOT(mnuCPUFollowInDump()));
						action->setData(static_cast<qlonglong>(insn.operand(i).immediate()));
						
						QAction *const action2 = menu.addAction(tr("Follow Constant In &Stack"), this, SLOT(mnuCPUFollowInStack()));
						action2->setData(static_cast<qlonglong>(insn.operand(i).immediate()));
					}
				}
			}
		}
	}
	
	menu.addSeparator();
	menu.addAction(tr("&Set EIP to this Instruction"), this, SLOT(mnuCPUSetEIP()));
	menu.addSeparator();
	menu.addAction(tr("&Edit Bytes"), this, SLOT(mnuCPUModify()));
	menu.addAction(tr("&Fill with 00's"), this, SLOT(mnuCPUFillZero()));
	menu.addAction(tr("Fill with &NOPs"), this, SLOT(mnuCPUFillNop()));
	menu.addSeparator();
	menu.addAction(tr("&Add Breakpoint"), this, SLOT(mnuCPUAddBreakpoint()));
	menu.addAction(tr("Add &Conditional Breakpoint"), this, SLOT(mnuCPUAddConditionalBreakpoint()));
	menu.addAction(tr("&Remove Breakpoint"), this, SLOT(mnuCPURemoveBreakpoint()));
	
	doAddPluginContextMenu<&DebuggerPluginInterface::cpuViewContextMenu>(&menu);
	
	menu.exec(ui.cpuView->viewport()->mapToGlobal(pos));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUFollow() {
	QAction *const action = qobject_cast<QAction *>(sender());
	if(action != 0) {
		const edb::address_t address = action->data().toULongLong();
		doMemoryFollow(true, address, edb::v1::jumpToAddress);
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUFollowInDump() {
	QAction *const action = qobject_cast<QAction *>(sender());
	if(action != 0) {
		const edb::address_t address = action->data().toULongLong();
		doMemoryFollow(true, address, edb::v1::dumpData);
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUFollowInStack() {
	QAction *const action = qobject_cast<QAction *>(sender());
	if(action != 0) {
		const edb::address_t address = action->data().toULongLong();
		doMemoryFollow(true, address, edb::v1::dumpStack);
	}
}

//------------------------------------------------------------------------------
// Name: mnuStackToggleLock(bool locked)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackToggleLock(bool locked) {
	m_StackViewLocked = locked;
}

//------------------------------------------------------------------------------
// Name: mnuStackContextMenu(const QPoint &pos)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackContextMenu(const QPoint &pos) {
	
	std::auto_ptr<QMenu> menu(m_StackView->createStandardContextMenu());
	menu->addSeparator();
	menu->addAction(tr("Follow Address In &Dump"), this, SLOT(mnuStackFollowInDump()));
	menu->addAction(tr("Follow Address In &Stack"), this, SLOT(mnuStackFollowInStack()));
	menu->addAction(tr("&Goto Address"), this, SLOT(mnuStackGotoAddress()));
	menu->addAction(tr("Goto E&SP"), this, SLOT(mnuStackGotoESP()));
	menu->addAction(tr("Goto E&BP"), this, SLOT(mnuStackGotoEBP()));
	menu->addSeparator();
	menu->addAction(tr("&Edit Bytes"), this, SLOT(mnuStackModify()));
	menu->addSeparator();
	menu->addAction(tr("&Push DWORD"), this, SLOT(mnuStackPush()));
	menu->addAction(tr("P&op DWORD"), this, SLOT(mnuStackPop()));
	
	// lockable stack feature
	menu->addSeparator();
	QAction *const action = new QAction(tr("&Lock Stack"), this);
    action->setCheckable(true);
    action->setChecked(m_StackViewLocked);
	menu->addAction(action);
	connect(action, SIGNAL(toggled(bool)), SLOT(mnuStackToggleLock(bool)));
	
	doAddPluginContextMenu<&DebuggerPluginInterface::stackViewContextMenu>(menu);

	menu->exec(m_StackView->mapToGlobal(pos));
}

//------------------------------------------------------------------------------
// Name: mnuDumpContextMenu(const QPoint &pos)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpContextMenu(const QPoint &pos) {
	QHexView *const s = qobject_cast<QHexView *>(sender());
	
	Q_CHECK_PTR(s);
	
	std::auto_ptr<QMenu> menu(s->createStandardContextMenu());
	menu->addSeparator();
	menu->addAction(tr("Follow Address In &Dump"), this, SLOT(mnuDumpFollowInDump()));
	menu->addAction(tr("Follow Address In &Stack"), this, SLOT(mnuDumpFollowInStack()));
	menu->addAction(tr("&Goto Address"), this, SLOT(mnuDumpGotoAddress()));
	menu->addSeparator();
	menu->addAction(tr("&Edit Bytes"), this, SLOT(mnuDumpModify()));
	menu->addSeparator();
	menu->addAction(tr("&Save To File"), this, SLOT(mnuDumpSaveToFile()));
	
	doAddPluginContextMenu<&DebuggerPluginInterface::dataViewContextMenu>(menu);
	
	menu->exec(s->mapToGlobal(pos));
}

//------------------------------------------------------------------------------
// Name: mnuDumpSaveToFile()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpSaveToFile() {
	QHexView *const s = qobject_cast<QHexView *>(ui.tabWidget->currentWidget());
	
	Q_CHECK_PTR(s);

	const QString filename = QFileDialog::getSaveFileName(
		this,
		tr("Save File"),
		m_LastOpenDirectory);
		
	if(!filename.isEmpty()) {
		QFile file(filename);
		file.open(QIODevice::WriteOnly);
		if(file.isOpen()) {
			file.write(s->allBytes());
		}			
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doCPUFill(quint8 byte) {
	const edb::address_t address 	= ui.cpuView->selectedAddress();
	const unsigned int size			= ui.cpuView->selectedSize();
	
	if(size != 0) {
		if(edb::v1::overwriteCheck(address, size)) {
			QByteArray bytes(size, byte);

			edb::v1::debuggerBase->writeBytes(address, bytes.data(), size);

			// do a refresh, not full update
			refreshGUI();
		}
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUAddBreakpoint() {
	const edb::address_t address = ui.cpuView->selectedAddress();
	edb::v1::createBreakpoint(address);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUAddConditionalBreakpoint() {
	bool ok;
	const edb::address_t address = ui.cpuView->selectedAddress();	
	const QString condition = QInputDialog::getText(this, tr("Set Breakpoint Condition"), tr("Expression:"), QLineEdit::Normal, QString(), &ok);
	if(ok) {
		edb::v1::createBreakpoint(address);
		if(!condition.isEmpty()) {
			edb::v1::debuggerBase->setBreakpointCondition(address, condition);
		}
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPURemoveBreakpoint() {
	const edb::address_t address = ui.cpuView->selectedAddress();
	edb::v1::removeBreakpoint(address);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUFillZero() {
	doCPUFill(0x00);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUFillNop() {
	doCPUFill(0x90);
}

//------------------------------------------------------------------------------
// Name: mnuCPUSetEIP()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUSetEIP() {
	const edb::address_t address = ui.cpuView->selectedAddress();
	State state;
	edb::v1::debuggerBase->getState(state);
	state.setInstructionPointer(address);
	edb::v1::debuggerBase->setState(state);
	updateGUI();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuCPUModify() {
	const edb::address_t address 	= ui.cpuView->selectedAddress();
	const unsigned int size			= ui.cpuView->selectedSize();
	
	quint8 buf[edb::Instruction::MAX_SIZE];		
	
	Q_ASSERT(size <= sizeof(buf));
	
	const bool ok = edb::v1::debuggerBase->readBytes(address, buf, size);
	if(ok) {
		QByteArray bytes = QByteArray::fromRawData(reinterpret_cast<const char *>(buf), size);	
		edb::v1::modifyBytes(address, size, bytes, 0x00);
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
template <class T>
void DebuggerMain::doModifyBytes(const T &hv) {
	if(hv) {
		const edb::address_t address	= hv->selectedBytesAddress();
		const unsigned int size 		= hv->selectedBytesSize();
		QByteArray bytes				= hv->selectedBytes();
		
		edb::v1::modifyBytes(address, size, bytes, 0x00);
	}
}

//------------------------------------------------------------------------------
// Name: mnuDumpModify()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpModify() {
	doModifyBytes(qobject_cast<QHexView *>(ui.tabWidget->currentWidget()));
}

//------------------------------------------------------------------------------
// Name: mnuStackModify()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::mnuStackModify() {
	doModifyBytes(m_StackView);
}

//------------------------------------------------------------------------------
// Name: breakpointConditionTrue(const QString &condition) const
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::breakpointConditionTrue(const QString &condition) {
	
	Expression<edb::address_t> expr(condition, &edb::v1::getVariable, 0, &edb::v1::getDWORD, 0);

	bool ok;
	ExpressionError err;
	const int conditionVal = expr.evaluateExpression(ok, err);

	if(!ok) {
		QMessageBox::information(this, tr("Error In Breakpoint Expression!"), err.what());
		// TODO: remove the breakpoint
		return true;
	}
	
	return conditionVal;		
}

//------------------------------------------------------------------------------
// Name: handle_trap()
// Desc: returns true if we should resume as if this trap never happened
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::handle_trap() {

	// we just got a DebugEvent::sigtrap, there are a few possible causes
	// #1: we hit a 0xcc breakpoint, if so, then we want to stop
	// #2: we did a step
	// #3: we are tracing (repeated stepping)
	// #4: we hit a hardware breakpoint!	
	State state;
	edb::v1::debuggerBase->getState(state);

	const edb::address_t previous_ip = state.instructionPointer() - edb::v1::debuggerBase->breakpointSize();

	// look it up in our breakpoint list, make sure it is one of OUR int3s!
	// if it is, we need to backup EIP and pause ourselves
	QSharedPointer<Breakpoint> bp = edb::v1::debuggerBase->findBreakpoint(previous_ip);
	if(bp && bp->enabled()) {
	
		// TODO: check if the breakpoint was corrupted
		bp->hit();					

		// back up eip the size of a breakpoint, since we executed a breakpoint 
		// instead of the real code that belongs there
		state.setInstructionPointer(previous_ip);
		edb::v1::debuggerBase->setState(state);
		edb::v1::syncronizeState();
				
		#if 1
		const QString bpCondition = bp->condition;

		// handle conditional breakpoints
		if(!bpCondition.isEmpty()) {
			if(!breakpointConditionTrue(bpCondition)) {
				return edb::DEBUG_CONTINUE;
			}
		}
		#endif
		
		// if it's a one time breakpoint then we should remove it upon
		// triggering, this is mainly used for 'run-until' situations
		// like step over

		if(bp->oneTime()) {
			edb::v1::debuggerBase->removeBreakpoint(bp->address());
		}
	}

	if(m_StepRun) {
		return edb::DEBUG_CONTINUE;
	} else {
		return edb::DEBUG_STOP;
	}
}

//------------------------------------------------------------------------------
// Name: handle_event_stopped(const DebugEvent &event)
// Desc: 
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::handle_event_stopped(const DebugEvent &event) {

	// ok we just came in from a stop, we need to test some things, 
	// generally, we will want to check if it was a step, if it was, was it 
	// because we just hit a break point or because we wanted to run, but had 
	// to step first in case were were on a breakpoint already...

	if(event.isError()) {
		const DebugEvent::Message message = event.errorDescription();
		QMessageBox::information(this, message.caption, message.message);
		return edb::DEBUG_STOP;
	}

	switch(event.stopCode()) {
	case DebugEvent::sigstop:
		// user asked to pause the debugged process
		return edb::DEBUG_STOP;

	case DebugEvent::sigtrap:
		return handle_trap();

	default:
		QMessageBox::information(this, tr("Stop Event"),
			tr(
			"<p>The debugged has recieved a stop event. <strong>%1</strong></p>"
			"<p>If you would like to pass this event to the application press Shift+[F7/F8/F9]</p>"
			"<p>If you would like to ignore this event, press [F7/F8/F9]</p>").arg(event.stopCode()));
		return edb::DEBUG_STOP;
	}
}

//------------------------------------------------------------------------------
// Name: handle_event_exited(const DebugEvent &event)
// Desc: 
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::handle_event_exited(const DebugEvent &event) {
	QMessageBox::information(
		this,
		tr("Application Exited"),
		tr("The debugged application exited normally with exit code %1.").arg(event.exitCode())
		);
		
	on_action_Detach_triggered();
	return edb::DEBUG_STOP;
}

//------------------------------------------------------------------------------
// Name: handle_event_signaled(const DebugEvent &event)
// Desc: 
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::handle_event_signaled(const DebugEvent &event) {
	switch(event.signalCode()) {
	case DebugEvent::sigkill:
		QMessageBox::information(this, tr("Application Killed"), tr("The debugged application was killed."));
		break;

	default:
		qDebug() << "Unknown Signal Recieved! " << event.signalCode();
		break;
	}
	
	on_action_Detach_triggered();
	return edb::DEBUG_STOP;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::currentInstructionIsReturn() const {
	const edb::address_t address = edb::v1::currentState().instructionPointer();
	
	quint8 buffer[edb::Instruction::MAX_SIZE + 1];
	int size = sizeof(buffer);
	
	if(edb::v1::getInstructionBytes(address, buffer, size)) {
		edb::Instruction insn(buffer, size, address, std::nothrow);
		if(insn.valid() && insn.type() == edb::Instruction::OP_RET) {
			return true;
		}
	}
	
	return false;
}

//------------------------------------------------------------------------------
// Name: handleEvent(const DebugEvent &event)
// Desc: 
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::handleEvent(const DebugEvent &event) {
	
	Q_CHECK_PTR(edb::v1::debuggerBase);

	edb::EVENT_STATUS resume_status;
	switch(event.reason()) {
	// most events
	case DebugEvent::EVENT_STOPPED:
		resume_status = handle_event_stopped(event);
		break;

	// usually an abnormal exit event (unhandled signal/etc)
	case DebugEvent::EVENT_SIGNALED:
		resume_status = handle_event_signaled(event);
		break;
	
	// normal exit
	case DebugEvent::EVENT_EXITED:
		resume_status = handle_event_exited(event);
		break;
		
	default:
		Q_ASSERT(false);
		return edb::DEBUG_EXCEPTION_NOT_HANDLED;
	}
	
	// re-enable any breakpoints we previously disabled
	if(m_ReenableBreakpoint) {
		m_ReenableBreakpoint->enable();
		m_ReenableBreakpoint.clear();
	}
	
	return resume_status;
}

//------------------------------------------------------------------------------
// Name: debugEventHandler(const DebugEvent &event)
// Desc: 
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::debugEventHandler(const DebugEvent &event) {
	DebugEventHandlerInterface *const handler = edb::v1::debugEventHandler();
	Q_CHECK_PTR(handler);
	return handler->handleEvent(event);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::update_tab_caption(const QSharedPointer<QHexView> &view, edb::address_t start, edb::address_t end) {
	const int index = ui.tabWidget->indexOf(view.data());
	const QString caption = ui.tabWidget->data(index).toString();
	
	if(caption.isEmpty()) {
		ui.tabWidget->setTabText(index, tr("%1-%2").arg(edb::v1::formatPointer(start), edb::v1::formatPointer(end)));
	} else {
		ui.tabWidget->setTabText(index, tr("[%1] %2-%3").arg(caption, edb::v1::formatPointer(start), edb::v1::formatPointer(end)));
	}
}

//------------------------------------------------------------------------------
// Name: updateData(const QSharedPointer<DataViewInfo> &v)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::updateData(const QSharedPointer<DataViewInfo> &v) {

	Q_CHECK_PTR(v);
	
	QSharedPointer<QHexView> view(v->view);
	
	Q_CHECK_PTR(view);

	v->update();
	
	update_tab_caption(view, v->region.start, v->region.end);
}

//------------------------------------------------------------------------------
// Name: clearData(const QSharedPointer<DataViewInfo> &v)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::clearData(const QSharedPointer<DataViewInfo> &v) {

	Q_CHECK_PTR(v);
	
	QSharedPointer<QHexView> view(v->view);
	
	Q_CHECK_PTR(view);

	view->clear();
	view->scrollTo(0);
	
	update_tab_caption(view, edb::nullptr, edb::nullptr);
}

//------------------------------------------------------------------------------
// Name: doJumpToAddress(edb::address_t address, const MemRegion &r, bool scrollTo)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doJumpToAddress(edb::address_t address, const MemRegion &r, bool scrollTo) {
	ui.cpuView->setAddressOffset(r.start);
	ui.cpuView->setRegion(r);
	if(scrollTo && !ui.cpuView->addressShown(address)) {
		ui.cpuView->scrollTo(address);
	}
}

//------------------------------------------------------------------------------
// Name: update_disassembly(edb::address_t address, const MemRegion &r)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::update_disassembly(edb::address_t address, const MemRegion &r) {
	ui.cpuView->setCurrentAddress(address);	
	doJumpToAddress(address, r, true);
	m_ListModel->setStringList(edb::v1::archProcessor().updateInstructionInfo(address));
}

//------------------------------------------------------------------------------
// Name: update_stack_view()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::update_stack_view() {
	if(!edb::v1::dumpStack(edb::v1::currentState().stackPointer(), !m_StackViewLocked)) {
		m_StackView->clear();
		m_StackView->scrollTo(0);
	}
}

//------------------------------------------------------------------------------
// Name: update_cpu_view(MemRegion &region)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::update_cpu_view(MemRegion &region) {
	
	const edb::address_t address = edb::v1::currentState().instructionPointer();

	if(!edb::v1::memoryRegions().findRegion(address, region)) {
		ui.cpuView->clear();
		ui.cpuView->scrollTo(0);
		m_ListModel->setStringList(QStringList());
	} else {
		update_disassembly(address, region);
	}
}

//------------------------------------------------------------------------------
// Name: update_data_views()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::update_data_views() {

	// update all data views with the current region data
	Q_FOREACH(QSharedPointer<DataViewInfo> info, m_DataRegions) {

		// make sure the regions are still valid..
		if(edb::v1::memoryRegions().findRegion(info->region.start)) {
			updateData(info);
		} else {
			clearData(info);
		}
	}
}

//------------------------------------------------------------------------------
// Name: refreshGUI()
// Desc: refreshed all the different displays
//------------------------------------------------------------------------------
void DebuggerMain::refreshGUI() {
	ui.cpuView->repaint();
	m_StackView->repaint();
	
	Q_FOREACH(QSharedPointer<DataViewInfo> info, m_DataRegions) {
		info->view->repaint();
	}
	
	m_ListModel->setStringList(edb::v1::archProcessor().updateInstructionInfo(edb::v1::currentState().instructionPointer()));
}

//------------------------------------------------------------------------------
// Name: updateGUI()
// Desc: updates all the different displays
//------------------------------------------------------------------------------
void DebuggerMain::updateGUI() {

	edb::v1::syncronizeState();
	MemRegion region;
	update_cpu_view(region);
	update_data_views();
	update_stack_view();
	edb::v1::archProcessor().updateRegisterView(region.name);
	update_thread_list();
}

//------------------------------------------------------------------------------
// Name: isTrap(const DebugEvent &event)
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::isTrap(const DebugEvent &event) {
	return event.stopped() && event.stopCode() == DebugEvent::sigtrap;
}

//------------------------------------------------------------------------------
// Name: resumeStatus(bool pass_exception)
// Desc: 
//------------------------------------------------------------------------------
edb::EVENT_STATUS DebuggerMain::resumeStatus(bool pass_exception) {
	if(pass_exception && m_LastEvent.stopped() && !isTrap(m_LastEvent)) {
		return edb::DEBUG_EXCEPTION_NOT_HANDLED;
	} else {
		return edb::DEBUG_CONTINUE;
	}
}

//------------------------------------------------------------------------------
// Name: resume_execution(bool pass_exception, DEBUG_MODE mode)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::resume_execution(bool pass_exception, DEBUG_MODE mode) {
		
	if(m_StepRun) {
		m_StepRun = false;
		edb::v1::debuggerBase->resume(edb::DEBUG_CONTINUE);
	} else {
	
		// if neccessary pass the trap to the application, otherwise just resume
		// as normal
		const edb::EVENT_STATUS resume_status = resumeStatus(pass_exception);

		// if we are on a breakpoint, disable it
		m_ReenableBreakpoint = edb::v1::debuggerBase->findBreakpoint(edb::v1::currentState().instructionPointer());
		if(m_ReenableBreakpoint) {
			m_ReenableBreakpoint->disable();

			m_StepRun = (mode == MODE_RUN);

			edb::v1::debuggerBase->step(resume_status);
		} else {
			// we get here, we are not sitting on a BP, we can just directly do what we wanted
			m_StepRun = false;

			if(mode == MODE_RUN) {
				edb::v1::debuggerBase->resume(resume_status);
			} else {
				edb::v1::debuggerBase->step(resume_status);
			}
		}
	}

	// set the state to 'running'
	update_menu_state(RUNNING);
}

//------------------------------------------------------------------------------
// Name: on_action_Run_Pass_Signal_To_Application_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Run_Pass_Signal_To_Application_triggered() {
	resume_execution(true, MODE_RUN);	
}

//------------------------------------------------------------------------------
// Name: on_action_Step_Into_Pass_Signal_To_Application_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Step_Into_Pass_Signal_To_Application_triggered() {
	resume_execution(true, MODE_STEP);
}

//------------------------------------------------------------------------------
// Name: on_action_Run_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Run_triggered() {
	resume_execution(false, MODE_RUN);
}

//------------------------------------------------------------------------------
// Name: on_action_Step_Into_triggered(0
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Step_Into_triggered() {
	resume_execution(false, MODE_STEP);
}

//------------------------------------------------------------------------------
// Name: on_action_Detach_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Detach_triggered() {
	doDetachFromProcess(false);
}

//------------------------------------------------------------------------------
// Name: on_action_Kill_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Kill_triggered() {
	doDetachFromProcess(true);
}

//------------------------------------------------------------------------------
// Name: on_action_Step_Over_Pass_Signal_To_Application_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Step_Over_Pass_Signal_To_Application_triggered() {
	stepOver(
		boost::bind(&DebuggerMain::on_action_Run_Pass_Signal_To_Application_triggered, this),
		boost::bind(&DebuggerMain::on_action_Step_Into_Pass_Signal_To_Application_triggered, this));
}

//------------------------------------------------------------------------------
// Name: on_action_Step_Over_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Step_Over_triggered() {
	stepOver(
		boost::bind(&DebuggerMain::on_action_Run_triggered, this),
		boost::bind(&DebuggerMain::on_action_Step_Into_triggered, this));
}

//------------------------------------------------------------------------------
// Name: on_actionRun_Until_Return_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_actionRun_Until_Return_triggered() {
	// TODO: implement this
	// we need a generic 'run until <condition>' function
	// likely callback based
}

//------------------------------------------------------------------------------
// Name: on_action_Pause_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Pause_triggered() {
	Q_CHECK_PTR(edb::v1::debuggerBase);
	edb::v1::debuggerBase->pause();
}

//------------------------------------------------------------------------------
// Name: cleanupDebugger()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::cleanupDebugger() {
	
	m_Timer->stop();

	edb::v1::memoryRegions().clear();
	edb::v1::symbolManager().clear();
	edb::v1::archProcessor().reset();
	edb::internal::resetState();
	ui.comboThreads->clear();

	// clear up the data view
	while(ui.tabWidget->count() > 1) {
		mnuDumpDeleteTab();
	}
	
	ui.tabWidget->setData(0, QString());
	
	Q_ASSERT(!m_DataRegions.isEmpty());
	m_DataRegions.first()->region = MemRegion();
		
	setWindowTitle(tr("EDB"));
	
	updateGUI();
}

//------------------------------------------------------------------------------
// Name: sessionFileName() const
// Desc: 
//------------------------------------------------------------------------------
QString DebuggerMain::sessionFileName() const {
	QString ret;
	if(!m_ProgramExecutable.isEmpty()) {
		const QString basename = edb::v1::basename(m_ProgramExecutable);
		ret = edb::v1::config().sessionPath + "/" + basename + ".edb";
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: doDetachFromProcess(bool kill)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doDetachFromProcess(bool kill) {

	SessionFileInterface *const sessionFile = edb::v1::sessionFileHandler();
	if(sessionFile != 0) {
		const QString filename = sessionFileName();
		if(!filename.isEmpty()) {
			sessionFile->saveSessionData(filename, m_ProgramExecutable);
			m_ProgramExecutable.clear();
		}
	}

	if(edb::v1::debuggerBase != 0) {
		if(kill)	edb::v1::debuggerBase->kill();
		else		edb::v1::debuggerBase->detach();
	}

	cleanupDebugger();
	update_menu_state(TERMINATED);
}

//------------------------------------------------------------------------------
// Name: setInitialDebuggerState()
// Desc: resets all of the basic data to sane defaults
//------------------------------------------------------------------------------
void DebuggerMain::setInitialDebuggerState() {

	update_menu_state(PAUSED);
	m_Timer->start(0);

	edb::v1::symbolManager().loadSymbols(edb::v1::config().symbolPath);
	
	edb::v1::memoryRegions().setPID(edb::v1::debuggerBase->pid());
	edb::v1::memoryRegions().sync();
	
	update_thread_list();

	Q_ASSERT(m_DataRegions.size() > 0);
	
	m_DataRegions.first()->region = edb::v1::primaryDataRegion();
	
	edb::internal::resetState();

	AnalyzerInterface *const analyzer = edb::v1::analyzer();
	if(analyzer != 0) {
		analyzer->invalidateAnalysis();
	}
	
	m_StepRun = false;
	
	set_debugger_caption(edb::v1::getProcessExecutable());

	m_ProgramExecutable.clear();
	if(edb::v1::debuggerBase->pid() != 0) {
		m_ProgramExecutable	= edb::v1::getProcessExecutable();		
	}

	SessionFileInterface *const sessionFile = edb::v1::sessionFileHandler();
	if(sessionFile != 0) {
		const QString filename = sessionFileName();
		if(!filename.isEmpty()) {
			sessionFile->loadSessionData(filename, m_ProgramExecutable);
		}
	}
	
	// create our binary info object for the primary code module
	m_BinaryInfo.reset(edb::v1::getBinaryInfo(edb::v1::primaryCodeRegion()));
	
	m_StackCommentServer->clear();
	if(m_BinaryInfo) {
		m_StackCommentServer->setComment(m_BinaryInfo->entryPoint(), "<entry point>");
	}
}

//------------------------------------------------------------------------------
// Name: testNativeBinary()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::testNativeBinary() {
	if(m_BinaryInfo && !m_BinaryInfo->nativeBinary()) {	
		QMessageBox::warning(
			this,
			tr("Not A Native Binary"),
			tr("The program you just attached to was built for a different architecture than the one that EDB was built for. "
			"For example a 32-bit binary on x86-64. "
			"This is not supported yet, so you may need to use a version of EDB that was compiled for the same architecture as your target program")
			);
	}
}

//------------------------------------------------------------------------------
// Name: setInitialBreakpoint(const QString &s)
// Desc: sets the initial breakpoint so we can stop at the entry point of the 
//       application
//------------------------------------------------------------------------------
void DebuggerMain::setInitialBreakpoint(const QString &s) {

	edb::address_t entryPoint = 0;
	
	if(edb::v1::config().initialBreakpoint == Configuration::MainSymbol) {
		const QString mainSymbol = edb::v1::basename(s) + "::main";
		const QSharedPointer<SymbolManager::Symbol> sym = edb::v1::symbolManager().find(mainSymbol);

		if(sym) {
			entryPoint = sym->address;
		} else if(edb::v1::config().findMain) {
			entryPoint = edb::v1::locateMainFunction();
		}
	}
	
	if(entryPoint == 0 || edb::v1::config().initialBreakpoint == Configuration::EntryPoint) {
		if(m_BinaryInfo) {
			entryPoint = m_BinaryInfo->entryPoint();
		}
	}

	if(entryPoint != 0) {
		edb::v1::debuggerBase->addBreakpoint(entryPoint, true);
	}
}

//------------------------------------------------------------------------------
// Name: on_action_Restart_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Restart_triggered() {
	
	Q_CHECK_PTR(edb::v1::debuggerBase);

	m_WorkingDirectory	= edb::v1::getProcessCurrentWorkingDirectory();	
	QStringList args	= edb::v1::getProcessArguments();
	const QString s		= edb::v1::getProcessExecutable();

	if(!args.empty()) {
		args.removeFirst();
	}

	if(!s.isEmpty()) {
		doDetachFromProcess(true);
		commonOpen(s, args);
	}
}

//------------------------------------------------------------------------------
// Name: commonOpen(const QString &s, const QStringList &args)
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::commonOpen(const QString &s, const QStringList &args) {
	
	bool ret = false;
	if(!QFile(s).exists()) {
		QMessageBox::information(
			this,
			tr("Could Not Open"),
			tr("The specified file (%1) does not appear to exist, please check priviledges and try again.").arg(s));
	} else {

		const QString tty = create_tty();

		if(edb::v1::debuggerBase->open(s, m_WorkingDirectory, args, tty)) {
			setInitialDebuggerState();
			testNativeBinary();
			setInitialBreakpoint(s);
			ret = true;		
		} else {
			QMessageBox::information(
				this,
				tr("Could Not Open"),
				tr("Failed to open and attach to process, please check priviledges and try again."));
		}
	}

	
	updateGUI();
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: doRun(const QString &s, const QStringList &args)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doRun(const QString &s, const QStringList &args) {
	const bool ok = commonOpen(s, args);
	if(ok) {
		m_RecentFileManager->addFile(s);
	}
}

//------------------------------------------------------------------------------
// Name: doOpenFile(const QString &s)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doOpenFile(const QString &s) {
	if(!s.isEmpty()) {
		m_LastOpenDirectory = QFileInfo(s).canonicalFilePath();
	
		doDetachFromProcess(false);
		
		doRun(s, m_DialogArguments->arguments());
	}
}

//------------------------------------------------------------------------------
// Name: doAttach(edb::pid_t pid)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::doAttach(edb::pid_t pid) {
	
	// TODO: we need a core concept of debugger capabilities which
	// may restrict some actions
	
	if(pid == edb::v1::debuggerBase->pid()) {
		QMessageBox::information(this, tr("EDB"), tr("You are already debugging that process!"));
	} else {
		doDetachFromProcess(false);

		if(edb::v1::debuggerBase->attach(pid)) {
				
			m_WorkingDirectory = edb::v1::getProcessCurrentWorkingDirectory();

			setInitialDebuggerState();
			
			QStringList args = edb::v1::getProcessArguments();
			if(!args.empty()) {
				args.removeFirst();
			}
			
			m_DialogArguments->setArguments(args);
		} else {
			QMessageBox::information(this, tr("EDB"), tr("Failed to attach to process, please check priviledges and try again."));
		}

		updateGUI();
	}
}

//------------------------------------------------------------------------------
// Name: on_action_Open_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Open_triggered() {

	// TODO: we need a core concept of debugger capabilities which
	// may restrict some actions

	const QString filename = QFileDialog::getOpenFileName(
		this,
		tr("Choose a file"),
		m_LastOpenDirectory);
		
	doOpenFile(filename);
}

//------------------------------------------------------------------------------
// Name: on_action_Attach_triggered()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Attach_triggered() {

	// TODO: we need a core concept of debugger capabilities which
	// may restrict some actions

	static DialogAttach *const dlg = new DialogAttach(this);
	const int ret = dlg->exec();
	
	if(ret == QDialog::Accepted) {
		bool ok;
		const edb::pid_t pid = dlg->selectedPID(ok);
		if(ok) {
			doAttach(pid);
		}
	}
}

//------------------------------------------------------------------------------
// Name: on_action_Memory_Regions_triggered()
// Desc: displays the memory regions dialog, and optionally dumps some data
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Memory_Regions_triggered() {

	// TODO: we need a core concept of debugger capabilities which
	// may restrict some actions
	
	static DialogMemoryRegions *const dlg = new DialogMemoryRegions(this);	
	const int ret = dlg->exec();
	
	if(ret == QDialog::Accepted) {
		const MemRegion *const region = dlg->selectedRegion();
		if(region != 0) {
			edb::v1::dumpData(region->start);
		}
	}
}

//------------------------------------------------------------------------------
// Name: mnuDumpCreateTab()
// Desc: duplicates the current tab creating a new one
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpCreateTab() {
	create_data_tab();
	m_DelTab->setEnabled(ui.tabWidget->count() > 1);
}

//------------------------------------------------------------------------------
// Name: mnuDumpDeleteTab() 
// Desc: handles removing of a memory view tab
//------------------------------------------------------------------------------
void DebuggerMain::mnuDumpDeleteTab() {
	delete_data_tab();
	m_DelTab->setEnabled(ui.tabWidget->count() > 1);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
template <QList<QAction *> (DebuggerPluginInterface::*F)(), class T>
void DebuggerMain::doAddPluginContextMenu(const T &menu) {
	Q_FOREACH(QObject *plugin, edb::v1::pluginList()) {
		DebuggerPluginInterface *const p = qobject_cast<DebuggerPluginInterface *>(plugin);
		if(p != 0) {
			const QList<QAction *> acts = (p->*F)();
			if(!acts.isEmpty()) {
				menu->addSeparator();
				menu->addActions(acts);
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_action_Plugins_triggered() {
	static QDialog *const dlg = new DialogPlugins(this);
	dlg->show();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::jumpToAddress(edb::address_t address) {	
	MemRegion region;
	if(edb::v1::memoryRegions().findRegion(address, region)) {		
		doJumpToAddress(address, region, true);
		return true;
	}
	
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::dumpDataRange(edb::address_t address, edb::address_t end_address, bool new_tab) {		
	MemRegion region;
	if(edb::v1::memoryRegions().findRegion(address, region)) {
		if(new_tab) {
			mnuDumpCreateTab();
		}

		QSharedPointer<DataViewInfo> info = currentDataViewInfo();

		if(info) {
			info->region = region;

			if(info->region.contains(end_address)) {
				info->region.end = end_address;
			}

			if(info->region.contains(address)) {
				info->region.start = address;
			}

			updateData(info);
			return true;
		}
	}

	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::dumpData(edb::address_t address, bool new_tab) {
	MemRegion region;
	if(edb::v1::memoryRegions().findRegion(address, region)) {
		if(new_tab) {
			mnuDumpCreateTab();
		}

		QSharedPointer<DataViewInfo> info = currentDataViewInfo();

		if(info) {
			info->region = region;
			updateData(info);
			info->view->scrollTo(address - info->region.start);
			return true;
		}
	}

	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerMain::dumpStack(edb::address_t address, bool scroll_to) {
	const MemRegion last_region = m_StackViewInfo.region;

	if(edb::v1::memoryRegions().findRegion(address, m_StackViewInfo.region)) {
		m_StackViewInfo.update();
		if(scroll_to || m_StackViewInfo.region != last_region) {
			m_StackView->scrollTo(address - m_StackViewInfo.region.start);
		}
		return true;
	}
	
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::update_thread_list() {
#ifdef ENABLE_THREAD_DEBUGGING
	const QList<edb::tid_t> threads = edb::v1::debuggerBase->threadIDs();
	QStringList list;
	Q_FOREACH(edb::tid_t tid, threads) {
		list << tr("%1").arg(tid);
	}
	m_ThreadListModel->setStringList(list);
	ui.comboThreads->setCurrentIndex(0);
#endif
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::on_comboThreads_currentIndexChanged(int index) {
	if(index != -1) {
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::tabContextMenu(int index, const QPoint &pos) {
	QMenu menu;
	QAction *const actionAdd	= menu.addAction(tr("&Set Label"));
	QAction *const actionClear	= menu.addAction(tr("&Clear Label"));
	QAction *const chosen = menu.exec(ui.tabWidget->mapToGlobal(pos));

	if(chosen == actionAdd) {
		bool ok;
		const QString text = QInputDialog::getText(
			this,
			tr("Set Caption"),
			tr("Caption:"),
			QLineEdit::Normal,
			ui.tabWidget->data(index).toString(),
			&ok);

		if (ok && !text.isEmpty()) {
			ui.tabWidget->setData(index, text);
		}
	} else if(chosen == actionClear) {
		ui.tabWidget->setData(index, QString());
	}

	updateGUI();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerMain::nextDebugEvent() {

	Q_CHECK_PTR(edb::v1::debuggerBase);

	DebugEvent e;
	if(edb::v1::debuggerBase->waitForDebugEvent(e, 10)) {
			
		m_LastEvent = e;

		// syncronize the current state
		edb::v1::syncronizeState();
		
		// TODO: figure out a way to do this less often, if they map an obscene number of
		// regions, this really slows things down
		edb::v1::memoryRegions().sync();
			
		edb::EVENT_STATUS status = debugEventHandler(e);
		switch(status) {
		case edb::DEBUG_STOP:
			m_StepRun = false;
			updateGUI();
			if(edb::v1::debuggerBase->pid() != 0) {
				update_menu_state(PAUSED);
			} else {
				update_menu_state(TERMINATED);
			}
			break;
		case edb::DEBUG_CONTINUE:
			resume_execution(false, MODE_RUN);
			break;
		case edb::DEBUG_EXCEPTION_NOT_HANDLED:
			resume_execution(true, MODE_RUN);
			break;
		}

		// TODO: make the system use this information, this is huge! it will allow us to
		// have restorable breakpoints...even in libraries!
#if 0
		if(m_BinaryInfo) {
			// NOTE: TEST TEST TEST
			qDebug("DEBUG POINTER: %p", m_BinaryInfo->debugPointer());
		}
#endif
	}
}
