/*
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 "DebuggerUI.h"
#include "ArchProcessorInterface.h"
#include "Configuration.h"
#include "DebuggerCoreInterface.h"
#include "Debugger.h"
#include "DebuggerPluginInterface.h"
#include "DialogOptions.h"
#include "Expression.h"
#include "QHexView.h"
#include "State.h"

#include <QtDebug>
#include <QDir>
#include <QtDebug>
#include <QInputDialog>

#include <cstring>

#include <sys/types.h>
#include <sys/stat.h>

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
DebuggerUI::DebuggerUI(QWidget *parent) : QMainWindow(parent),
		m_AddTab(0),
		m_DelTab(0),
		m_TTYProc(new QProcess(this)),
		m_GUIState(TERMINATED) {
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
DebuggerUI::~DebuggerUI() {
	// kill our xterm and wait for it to die
	m_TTYProc->kill();
	m_TTYProc->waitForFinished(3000);
}



//------------------------------------------------------------------------------
// Name: updateMenuState(GUI_STATE state)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerUI::updateMenuState(GUI_STATE state) {

	switch(state) {
	case PAUSED:
	#if 0
		ui.actionRun_Until_Return->setEnabled(true);
	#endif
		ui.action_Restart->setEnabled(true);
		ui.action_Run->setEnabled(true);
		ui.action_Pause->setEnabled(false);
		ui.action_Step_Into->setEnabled(true);
		ui.action_Step_Over->setEnabled(true);
		ui.action_Step_Into_Pass_Signal_To_Application->setEnabled(true);
		ui.action_Step_Over_Pass_Signal_To_Application->setEnabled(true);
		ui.action_Run_Pass_Signal_To_Application->setEnabled(true);
		ui.action_Detach->setEnabled(true);
		ui.action_Kill->setEnabled(true);
		m_AddTab->setEnabled(true);
		break;
	case RUNNING:
	#if 0
		ui.actionRun_Until_Return->setEnabled(false);
	#endif
		ui.action_Restart->setEnabled(false);
		ui.action_Run->setEnabled(false);
		ui.action_Pause->setEnabled(true);
		ui.action_Step_Into->setEnabled(false);
		ui.action_Step_Over->setEnabled(false);
		ui.action_Step_Into_Pass_Signal_To_Application->setEnabled(false);
		ui.action_Step_Over_Pass_Signal_To_Application->setEnabled(false);
		ui.action_Run_Pass_Signal_To_Application->setEnabled(false);
		ui.action_Detach->setEnabled(true);
		ui.action_Kill->setEnabled(true);
		m_AddTab->setEnabled(true);
		break;
	case TERMINATED:
	#if 0
		ui.actionRun_Until_Return->setEnabled(false);
	#endif
		ui.action_Restart->setEnabled(false);
		ui.action_Run->setEnabled(false);
		ui.action_Pause->setEnabled(false);
		ui.action_Step_Into->setEnabled(false);
		ui.action_Step_Over->setEnabled(false);
		ui.action_Step_Into_Pass_Signal_To_Application->setEnabled(false);
		ui.action_Step_Over_Pass_Signal_To_Application->setEnabled(false);
		ui.action_Run_Pass_Signal_To_Application->setEnabled(false);
		ui.action_Detach->setEnabled(false);
		ui.action_Kill->setEnabled(false);
		m_AddTab->setEnabled(false);
		break;
	}
	
	m_GUIState = state;
}

//------------------------------------------------------------------------------
// Name: createTTY()
// Desc: creates a TTY object for our command line I/O
//------------------------------------------------------------------------------
QString DebuggerUI::createTTY() {
#ifdef Q_OS_LINUX
	// we attempt to reuse an open output window
	if(edb::v1::config().ttyEnabled && m_TTYProc->state() != QProcess::Running) {
		const QString command = edb::v1::config().ttyCommand;
		
		if(!command.isEmpty()) {
	
			// ok, creating a new one...	
			// first try to get a 'unique' filename, i would love to use a system 
			// temp file API... but there doesn't seem to be one which will create 
			// a pipe...only ordinary files!
			const QString tempPipe = QString("%1/edb_temp_file_%2_%3").arg(QDir::tempPath()).arg(rand()).arg(getpid());

			// make sure it isn't already there, and then make the pipe
			::unlink(qPrintable(tempPipe));
			::mkfifo(qPrintable(tempPipe), S_IRUSR | S_IWUSR);

			// this is a basic shell script which will output the tty to a file (the pipe),
			// ignore kill sigs, close all standard IO, and then just hang
			const QString shellScript = QString(
		    	"tty > %1;"
				"trap \"\" INT QUIT TSTP;"
				"exec<&-; exec>&-;"
				"while :; do sleep 3600; done"
				).arg(tempPipe);

			// parse up the command from the options, white space delimited
			QStringList procargs = edb::v1::parseCommandLine(command);
			const QString ttyCommand = procargs.takeFirst();

			// start constructing the arguments for the term

			// TODO: figure out what to do if the user wants gnome-terminal, it uses "--title", not
			// "-title"...
			procargs << "-title" << tr("EDB Output") << "-e" << QString("sh -c '%1'").arg(shellScript);

			// make the tty process object and connect it's death signal to our cleanup
			connect(m_TTYProc, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(ttyProcFinished(int, QProcess::ExitStatus)));

			m_TTYProc->start(ttyCommand, procargs);

			if(m_TTYProc->waitForStarted(3000)) {
				// get the output, this should block until the xterm actually gets a chance to write it
				QFile file(tempPipe);
				if(file.open(QIODevice::ReadOnly)) {
					m_TTYFile = file.readLine().trimmed();
				}
			} else {
				qDebug().nospace() << "Could not launch the desired terminal [" << ttyCommand << "], please check that it exists and you have proper permissions.";
				m_TTYFile.clear();
			}

			// cleanup, god i wish there was an easier way than this!
			::unlink(qPrintable(tempPipe));
		}
	}
#endif	
	return m_TTYFile;
}

//------------------------------------------------------------------------------
// Name: ttyProcFinished(int exitCode, QProcess::ExitStatus exitStatus)
// Desc: cleans up the data associated with a TTY when the terminal dies
//------------------------------------------------------------------------------
void DebuggerUI::ttyProcFinished(int exitCode, QProcess::ExitStatus exitStatus) {
	Q_UNUSED(exitCode);
	Q_UNUSED(exitStatus);
	
	m_TTYFile.clear();
}

//------------------------------------------------------------------------------
// Name: currentTab() const
// Desc: 
//------------------------------------------------------------------------------
int DebuggerUI::currentTab() const {
	return ui.tabWidget->currentIndex();
}

//------------------------------------------------------------------------------
// Name: currentDataViewInfo() const
// Desc: 
//------------------------------------------------------------------------------
QSharedPointer<DataViewInfo> DebuggerUI::currentDataViewInfo() const {
	return m_DataRegions[currentTab()];
}

//------------------------------------------------------------------------------
// Name: setDebuggerCaption(const QString &appname)
// Desc: sets the caption part to also show the application name and pid
//------------------------------------------------------------------------------
void DebuggerUI::setDebuggerCaption(const QString &appname) {
	setWindowTitle(tr("EDB - %1 [%2]").arg(appname).arg(edb::v1::debuggerBase->pid()));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerUI::deleteDataTab() {
	const int currentIndex = currentTab();
	
	// get a pointer to the info we need (before removing it from the list!)
	// this seems redundant to currentDataViewInfo(), but we need the
	// index too, so may as well waste one line to avoid duplicate work
	QSharedPointer<DataViewInfo> info = m_DataRegions[currentIndex];
		
	// remove it from the list
	m_DataRegions.remove(currentIndex);
	
	// remove the tab associated with it
	ui.tabWidget->removeTab(currentIndex);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerUI::createDataTab() {
	const int currentIndex = currentTab();
	
	// duplicate the current region
	DataViewInfo *const newDataView = new DataViewInfo(
			(currentIndex != -1) ? m_DataRegions[currentIndex]->region : MemRegion()
			);
		
	QHexView *const hv = new QHexView(this);

	newDataView->view = QSharedPointer<QHexView>(hv);
		
	// setup the context menu
	hv->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(hv, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(mnuDumpContextMenu(const QPoint &)));
	
	// show the initial data for this new view
	hv->setAddressOffset(newDataView->region.start);
	hv->setData(newDataView->stream);
	
	const Configuration &config = edb::v1::config();
		
	// set the default view options
	hv->setShowAddress(config.dataShowAddress);
	hv->setShowHexDump(config.dataShowHex);
	hv->setShowAsciiDump(config.dataShowAscii);
	hv->setShowComments(config.dataShowComments);
	hv->setRowWidth(config.dataRowWidth);
	hv->setWordWidth(config.dataWordWidth);
	
	// set the default font
	QFont dumpFont;
	dumpFont.fromString(config.dataFont);
	hv->setFont(dumpFont);

	m_DataRegions.push_back(QSharedPointer<DataViewInfo>(newDataView));

	// create the tab!
	ui.tabWidget->addTab(hv, tr("%1-%2").arg(
				edb::v1::formatPointer(newDataView->region.start), 
				edb::v1::formatPointer(newDataView->region.end)));


	ui.tabWidget->setCurrentIndex(ui.tabWidget->count() - 1);
}

//------------------------------------------------------------------------------
// Name: finishPluginSetup(const QMap<QString, QObject *> &plugins)
// Desc: finalizes plugin setup by adding each to the menu, we can do this now 
//       that we have a GUI widget to attach it to
//------------------------------------------------------------------------------
void DebuggerUI::finishPluginSetup(const QMap<QString, QObject *> &plugins) {
	
	// call the init function for each plugin
	Q_FOREACH(QObject *plugin, plugins) {
		DebuggerPluginInterface *const p = qobject_cast<DebuggerPluginInterface *>(plugin);
		if(p != 0) {
			p->init();
		}
	}
	
	// setup the menu for all plugins that which to do so
	DialogOptions *const options = qobject_cast<DialogOptions *>(edb::v1::dialogOptions());
	Q_FOREACH(QObject *plugin, plugins) {
		DebuggerPluginInterface *const p = qobject_cast<DebuggerPluginInterface *>(plugin);
		if(p != 0) {		
			QMenu *const menu = p->menu(this);
			if(menu != 0) {
				ui.menu_Plugins->addMenu(menu);
			}
			
			QWidget *const options_page = p->optionsPage();
					
			if(options_page != 0) {
				options->addOptionsPage(options_page);
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: getGotoExpression(bool &ok)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t DebuggerUI::getGotoExpression(bool &ok) {
    const QString text = QInputDialog::getText(this, tr("Goto Address"), tr("Address:"), QLineEdit::Normal, QString(), &ok);
	
	if (ok && !text.isEmpty()) {
		Expression<edb::address_t> expr(text, &edb::v1::getVariable, 0, &edb::v1::getDWORD, 0);

		ExpressionError err;
		const edb::address_t address = expr.evaluateExpression(ok, err);
		if(ok) {
			return address;
		} else {
			QMessageBox::information(this, tr("Error In Address Expression!"), err.what());
		}
	}
	return 0;
}

//------------------------------------------------------------------------------
// Name: getFollowRegister(bool &ok) const
// Desc: 
//------------------------------------------------------------------------------
edb::reg_t DebuggerUI::getFollowRegister(bool &ok) const {
	ok = false;
	const QTreeWidgetItem *const i = ui.registerList->currentItem();
	edb::reg_t ret = 0;
	if(i != 0) {
		edb::reg_t *const p = edb::v1::archProcessor().valueFromItem(*i, State::REG_GPR | State::REG_IP);

		if(p != 0) {
			ok = true;
			ret = *p;
		}
	}
	
	return ret;
}
