/*
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 "DebuggerCore.h"
#include "State.h"
#include "Util.h"
#include "DebugEvent.h"
#include <QStringList>
#include <QProcess>
#include <cerrno>
#include <climits>
#include <cstring>
#include <cstdio>
#include <cstdlib>

#ifdef Q_OS_UNIX
#include "DebuggerCoreUNIX.inl"
#endif

#ifdef Q_OS_MACX
#include "DebuggerCoreOSX.inl"
#endif

#ifdef Q_OS_LINUX
#include "DebuggerCoreLinux.inl"
#endif

#ifdef Q_OS_WIN32
#include "DebuggerCoreWindows.inl"
#endif

// int 3
const quint8 DebuggerCore::m_BreakInstruction[] = { 0xcc };

//------------------------------------------------------------------------------
// Name: ~DebuggerCore()
// Desc: destructor
//------------------------------------------------------------------------------
DebuggerCore::~DebuggerCore() {
	detach();
}

//------------------------------------------------------------------------------
// Name: executionBreakpointSize() const
// Desc: returns the size of a breakpoint
//------------------------------------------------------------------------------
int DebuggerCore::executionBreakpointSize() const {
	return sizeof(m_BreakInstruction);
}

//------------------------------------------------------------------------------
// Name: executionBreakpointData() const
// Desc: returns the value of a breakpoint
//------------------------------------------------------------------------------
QByteArray DebuggerCore::executionBreakpointData() const {
	return QByteArray(reinterpret_cast<const char *>(m_BreakInstruction), sizeof(m_BreakInstruction));
}

//------------------------------------------------------------------------------
// Name: pageSize() const
// Desc: returns the size of a page on this system
//------------------------------------------------------------------------------
edb::address_t DebuggerCore::pageSize() const {
	return m_PageSize;
}

//------------------------------------------------------------------------------
// Name: clearBreakpoints()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::clearBreakpoints() {
	if(attached()) {
		foreach(const Breakpoint &bp, m_Breakpoints) {
			removeBreakpoint(bp.address);
		}
	
		m_Breakpoints.clear();
	}
}

//------------------------------------------------------------------------------
// Name: addBreakpoint(edb::address_t address, bool oneTime)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::addBreakpoint(edb::address_t address, bool oneTime) {

	if(findBreakpoint(address) == 0) {
		// backup original bytes
		char prev[sizeof(m_BreakInstruction)];
		if(readBytes(address, prev, sizeof(m_BreakInstruction))) {

			// write the breakpoint
			if(writeBytes(address, executionBreakpointData(), sizeof(m_BreakInstruction))) {

				// create accounting structure
				Breakpoint data;
				data.address = address;
				data.setOneTime(oneTime);
				data.setEnabled(true);
				data.originalBytes = QByteArray(prev, sizeof(m_BreakInstruction));

				m_Breakpoints[address] = data;
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: setBreakpointCondition(edb::address_t address, const QString &condition)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::setBreakpointCondition(edb::address_t address, const QString &condition) {
	Breakpoint *const bp = findBreakpoint(address);
	if(bp != 0) {
		bp->condition = condition;
	}
}

//------------------------------------------------------------------------------
// Name: setBreakpointCondition(edb::address_t address, const QString &condition)
// Desc: 
//------------------------------------------------------------------------------
QString DebuggerCore::getBreakpointCondition(edb::address_t address) {
	QString ret;
	Breakpoint *const bp = findBreakpoint(address);
	if(bp != 0) {
		ret = bp->condition;
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: findBreakpoint(edb::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
Breakpoint *DebuggerCore::findBreakpoint(edb::address_t address) {
	Breakpoint *ret = 0;
	if(attached()) {
		BreakpointState::iterator i = m_Breakpoints.find(address);
		if(i != m_Breakpoints.end()) {
			ret = &(i.value());
		}
	}
	return ret;
}


//------------------------------------------------------------------------------
// Name: removeBreakpoint(edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::removeBreakpoint(edb::address_t address) {

	// TODO: assert paused
	if(attached()) {
		BreakpointState::iterator i = m_Breakpoints.find(address);
		if(i != m_Breakpoints.end()) {
			// this is an existing breakpoint
			if(writeBytes(address, i.value().originalBytes, sizeof(m_BreakInstruction))) {
				m_Breakpoints.erase(i);
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: disableBreakpoint(edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t DebuggerCore::disableBreakpoint(edb::address_t address) {
	if(address != 0) {
		Breakpoint *const bp = findBreakpoint(address);
		if(bp != 0) {
			// this is an existing breakpoint
			if(writeBytes(address, bp->originalBytes, sizeof(m_BreakInstruction))) {
				bp->setEnabled(false);
				return address;
			}	
		}
	}
	return 0;
}

//------------------------------------------------------------------------------
// Name: enableBreakpoint(edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t DebuggerCore::enableBreakpoint(edb::address_t address) {
	if(address != 0) {
		Breakpoint *const bp = findBreakpoint(address);
		if(bp != 0) {
			if(!bp->enabled()) {

				char prev[sizeof(m_BreakInstruction)];
				if(readBytes(address, prev, sizeof(m_BreakInstruction))) {

					if(writeBytes(address, executionBreakpointData(), sizeof(m_BreakInstruction))) {
						bp->setEnabled(true);
						bp->originalBytes = QByteArray(prev, sizeof(m_BreakInstruction));
						return address;
					}
				}
			}
		}
	}
	return 0;
}

//------------------------------------------------------------------------------
// Name: backupBreakpoints(BreakpointState &state) const
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::backupBreakpoints(BreakpointState &state) {
	state = m_Breakpoints;
}

//------------------------------------------------------------------------------
// Name: open(const QString &path, const QString &cwd, const QStringList &args)
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerCore::open(const QString &path, const QString &cwd, const QStringList &args) {
	return open(path, cwd, args, QString());
}

//------------------------------------------------------------------------------
// Name: pid() const
// Desc: returns the pid of the currently debugged process
//------------------------------------------------------------------------------
edb::pid_t DebuggerCore::pid() const {
	return m_PID;
}

//------------------------------------------------------------------------------
// Name: attached() const
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerCore::attached() const {
	return pid() != 0;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QList<edb::tid_t> DebuggerCore::threadIDs() const {
	return QList<edb::tid_t>();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
edb::tid_t DebuggerCore::activeThread() const {
	return m_ActiveThread;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::setActiveThread(edb::tid_t) {

}

Q_EXPORT_PLUGIN2(DebuggerCore, DebuggerCore)
