/*
Copyright (C) 2006 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 "Debugger.h"
#include "MemoryRegions.h"
#include "DebuggerCoreInterface.h"
#include "DebuggerMain.h"
#include "DialogInputValue.h"
#include "SymbolManager.h"
#include "DialogOptions.h"
#include "Configuration.h"
#include "DialogInputBinaryString.h"
#include "version.h"

#include <QMessageBox>
#include <QHBoxLayout>
#include <QString>
#include <cstdio>
#include <sstream>

DebuggerCoreInterface		*Debugger::debuggerBase			= 0;
QWidget 					*Debugger::debuggerGUI			= 0;
MemoryRegions				*Debugger::memoryRegions		= 0;
SymbolManager				*Debugger::symbolManager		= 0;
DebugEventHandlerInterface	*Debugger::debugEventHandler	= 0;


//------------------------------------------------------------------------------
// Name: intVersion()
// Desc: returns an integer comparable version of our version string
//------------------------------------------------------------------------------
unsigned long Debugger::intVersion() {
	return intVersion(version_string);
}

//------------------------------------------------------------------------------
// Name: intVersion()
// Desc: returns an integer comparable version of a version string in x.y.z 
//       format
//------------------------------------------------------------------------------
unsigned long Debugger::intVersion(const QString &s) {
	unsigned int maj = 0;
	unsigned int min = 0;
	unsigned int rev = 0;
	unsigned long ret = 0;
	
	std::stringstream ss(qPrintable(s));
	if(ss >> maj) {
		if(ss.peek() == '.') {
			ss.ignore(1);
			if(ss >> min) {
				if(ss.peek() == '.') {
					ss.ignore(1);	
					if(ss >> rev) {	
						ret = (maj << 12) | (min << 8) | (rev);
					}
				}
			}
		}
	}
	return ret;
}


//------------------------------------------------------------------------------
// Name: log(const QString &s)
// Desc: writes a single line to the logger
//------------------------------------------------------------------------------
void Debugger::log(const QString &s) {
	std::printf("%s\n", qPrintable(s));
}

//------------------------------------------------------------------------------
// Name: jumpToAddress(BaseTypes::address_t address)
// Desc: sets the disassembly display to a given address, returning success 
//       status
//------------------------------------------------------------------------------
bool Debugger::jumpToAddress(BaseTypes::address_t address) {
	bool ret = false;
	// TODO: remove need for this to see so much of the GUI's private data!
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
	
		MemoryRegions::Region region;
		if(Debugger::memoryRegions != 0) {
			if(Debugger::memoryRegions->findRegion(address, region)) {
				gui->ui.cpuView->setAddressOffset(region.start);
				gui->ui.cpuView->setRegion(region);
				gui->ui.cpuView->scrollTo(address);
				ret = true;
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpDataRange(BaseTypes::address_t address, BaseTypes::address_t end_address, bool newTab)
// Desc: shows a given address through a given end address in the data view, 
//       optionally in a new tab
//------------------------------------------------------------------------------
bool Debugger::dumpDataRange(BaseTypes::address_t address, BaseTypes::address_t end_address, bool newTab) {
	bool ret = false;
	// TODO: remove need for this to see so much of the GUI's private data!
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
	
		MemoryRegions::Region region;
		if(Debugger::memoryRegions != 0) {
			if(Debugger::memoryRegions->findRegion(address, region)) {

				if(newTab) {
					gui->createNewDataTab();
				}

				const int currentIndex = gui->ui.tabWidget->currentIndex();
				DebuggerMain::DataViewInfo *const info = gui->m_DataRegions[currentIndex];
				
				if(info != 0) {
					info->region = region;

					if(end_address >= info->region.start && end_address < info->region.end) {
						info->region.end = end_address;
					}

					if(address >= info->region.start && address < info->region.end) {
						info->region.start = address;
					}

					gui->updateData(info);
					ret = true;
				}
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpDataRange(BaseTypes::address_t address, BaseTypes::address_t end_address)
// Desc: shows a given address through a given end address in the data view
//------------------------------------------------------------------------------
bool Debugger::dumpDataRange(BaseTypes::address_t address, BaseTypes::address_t end_address) {
	return dumpDataRange(address, end_address, false);
}

//------------------------------------------------------------------------------
// Name: dumpStack(BaseTypes::address_t address)
// Desc: shows a given address in the stack view
//------------------------------------------------------------------------------
bool Debugger::dumpStack(BaseTypes::address_t address) {

	bool ret = false;
	if(memoryRegions != 0) {
		DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
		if(gui != 0) {
			if(memoryRegions->findRegion(address, gui->m_StackViewInfo.region)) {
				gui->m_StackViewInfo.update();
				gui->ui.stackView->scrollTo(address - gui->m_StackViewInfo.region.start);
				ret = true;
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpData(BaseTypes::address_t address, bool newTab)
// Desc: shows a given address in the data view, optionally in a new tab
//------------------------------------------------------------------------------
bool Debugger::dumpData(BaseTypes::address_t address, bool newTab) {

	bool ret = false;
	// TODO: remove need for this to see so much of the GUI's private data!
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
		MemoryRegions::Region region;
		if(Debugger::memoryRegions != 0) {
			if(Debugger::memoryRegions->findRegion(address, region)) {
		
				if(newTab) {
					gui->createNewDataTab();
				}

				const int currentIndex = gui->ui.tabWidget->currentIndex();
				DebuggerMain::DataViewInfo *const info = gui->m_DataRegions[currentIndex];

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

//------------------------------------------------------------------------------
// Name: dumpData(BaseTypes::address_t address)
// Desc: shows a given address in the data view
//------------------------------------------------------------------------------
bool Debugger::dumpData(BaseTypes::address_t address) {
	return dumpData(address, false);
}

//------------------------------------------------------------------------------
// Name: removeBreakPoint(BaseTypes::address_t address)
// Desc: 
//------------------------------------------------------------------------------
void Debugger::removeBreakPoint(BaseTypes::address_t address) {
	debuggerBase->removeBreakpoint(address);
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui) {
		gui->ui.cpuView->viewport()->repaint();
	}
}

//------------------------------------------------------------------------------
// Name: createBreakPoint(BaseTypes::address_t address)
// Desc: 
//------------------------------------------------------------------------------
void Debugger::createBreakPoint(BaseTypes::address_t address) {

	if(memoryRegions != 0) {
		MemoryRegions::Region region;
		memoryRegions->sync();
		if(memoryRegions->findRegion(address, region)) {
			if(!region.executable()) {
			
				int ret = QMessageBox::Yes;
			
				if(config()->warnOnNoExecBP) {
					ret = QMessageBox::question(debuggerGUI, "Suspicious breakpoint", 
						"You want to place a breakpoint in a non-executable region.\n"
						"An INT3 breakpoint set on data will not execute and may cause incorrect results or crashes.\n"
						"Do you really want to set a breakpoint here?", 
						QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton);
				}

				if(ret == QMessageBox::Yes) {
					debuggerBase->addBreakpoint(address);
					DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
					if(gui) {
						gui->ui.cpuView->viewport()->repaint();
					}
				}
			} else {
				debuggerBase->addBreakpoint(address);
				DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
				if(gui) {
					gui->ui.cpuView->viewport()->repaint();
				}
			}
		} else {
			QMessageBox::information(debuggerGUI, "Error Setting Breakpoint",
				"Sorry, but setting a breakpoint which is not in a valid region is not allowed.");
		}
	}
}

//------------------------------------------------------------------------------
// Name: toggleBreakPoint(BaseTypes::address_t address)
// Desc: 
//------------------------------------------------------------------------------
void Debugger::toggleBreakPoint(BaseTypes::address_t address) {
	if(debuggerBase->findBreakPoint(address)) {
		removeBreakPoint(address);
	} else {
		createBreakPoint(address);
	}
}

//------------------------------------------------------------------------------
// Name: getValueFromUser(unsigned int &value, const QString &title)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getValueFromUser(unsigned int &value, const QString &title) {
	static DialogInputValue *dlg = 0;
	bool ret = false;
	
	if(dlg == 0) {
		dlg = new DialogInputValue(debuggerGUI);
	}
	
	dlg->setWindowTitle(title);
	dlg->setValue(value);
	if(dlg->exec() == QDialog::Accepted) {
		value = dlg->value();
		ret = true;	
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: getValueFromUser(unsigned int &value)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getValueFromUser(unsigned int &value) {
	return getValueFromUser(value, "Input Value");
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getBinaryStringFromUser(QByteArray &value, int maxLength) {
	return getBinaryStringFromUser(value, "Input Value", maxLength);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getBinaryStringFromUser(QByteArray &value, const QString &title, int maxLength) {
	static DialogInputBinaryString *dlg = 0;
	
	bool ret = false;
	if(dlg == 0) {
		dlg = new DialogInputBinaryString(debuggerGUI);
	}
	
	dlg->setWindowTitle(title);
	dlg->ui.binaryString->setValue(value);
	
	if(value.length() <= maxLength) {	
		dlg->ui.binaryString->setMaxLength(maxLength);
	}
	
	if(dlg->exec() == QDialog::Accepted) {
		value = dlg->ui.binaryString->value();
		ret = true;
	}

	return ret;
}

//------------------------------------------------------------------------------
// Name: dialogOptions()
// Desc: 
//------------------------------------------------------------------------------
QDialog *Debugger::dialogOptions() {
	static DialogOptions *dlg = 0;
	if(dlg == 0) {	
		dlg = new DialogOptions(debuggerGUI);
	}
	return dlg;
}

//------------------------------------------------------------------------------
// Name: config()
// Desc: 
//------------------------------------------------------------------------------
const Configuration *Debugger::config() {
	return dynamic_cast<Configuration *>(dialogOptions());
}

//------------------------------------------------------------------------------
// Name: loadedLibraries()
// Desc: 
//------------------------------------------------------------------------------
QList<QString> Debugger::loadedLibraries() {
	QList<QString> ret;
	
	if(memoryRegions != 0) {
		memoryRegions->sync();
		
		const QVector<MemoryRegions::Region> r = memoryRegions->regions();
		for(QVector<MemoryRegions::Region>::const_iterator it = r.begin(); it != r.end(); ++it) {
			
			// TODO: this line is somewhat system specific...
			if(it->name.startsWith("/")) {
				if(!ret.contains(it->name)) {
					ret.push_back(it->name);
				}
			}
		}
	}
	
	return ret;
}
