/*
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 "MemoryRegions.h"
#include "SymbolManager.h"
#include "DebuggerCoreInterface.h"
#include "Util.h"
#include "State.h"
#include <sstream>
#include <iostream>
#include <string>
#include <cstring>
#include <QApplication>
#include <QFile>
#include <QTextStream>
#include <QStringList>
#include <sys/mman.h>

//------------------------------------------------------------------------------
// Name: MemoryRegions(SymbolManager &sym)
// Desc: constructor
//------------------------------------------------------------------------------
MemoryRegions::MemoryRegions(SymbolManager &sym) : QAbstractItemModel(0), m_PID(0), m_SymbolManager(sym) {
}

//------------------------------------------------------------------------------
// Name: ~MemoryRegions()
// Desc: destructor
//------------------------------------------------------------------------------
MemoryRegions::~MemoryRegions() {
}

//------------------------------------------------------------------------------
// Name: setPID(edb::pid_t pid)
// Desc: 
//------------------------------------------------------------------------------
void MemoryRegions::setPID(edb::pid_t pid) {
	m_PID = pid;
	m_Regions.clear();
	sync();
}

//------------------------------------------------------------------------------
// Name: clear()
// Desc: 
//------------------------------------------------------------------------------
void MemoryRegions::clear() {
	m_PID = 0;
	m_Regions.clear();
}

namespace {
	//------------------------------------------------------------------------------
	// Name: proccessMapLine(SymbolManager &symbols, const QString &line, MemRegion &region)
	// Desc: parses the data from a line of a memory map file
	//------------------------------------------------------------------------------
	bool proccessMapLine(SymbolManager &symbols, const QString &line, MemRegion &region) {

		bool ret = false;
	#if defined (Q_OS_LINUX)
		const QStringList items = line.split(" ", QString::SkipEmptyParts);
		if(items.size() >= 3) {
			bool ok;
			const QStringList bounds = items[0].split("-");
			if(bounds.size() == 2) {
				region.start = bounds[0].toULongLong(&ok, 16);
				if(ok) {
					region.end = bounds[1].toULongLong(&ok, 16);
					if(ok) {
						region.base = items[2].toULongLong(&ok, 16);
						if(ok) {
							const QString perms	= items[1];
							region.m_Permissions = 0;
							if(perms[0] == 'r') region.m_Permissions |= PROT_READ;
							if(perms[1] == 'w') region.m_Permissions |= PROT_WRITE;
							if(perms[2] == 'x') region.m_Permissions |= PROT_EXEC;

							if(items.size() >= 6) {
								region.name = items[5];
							}

							// if the region has a name, is mapped starting
							// at the begining of the file, and is executable, sounds 
							// like a module mapping!
							if(!region.name.isEmpty()) {
								if(region.base == edb::nullptr) {
									if(region.executable()) {
										symbols.loadSymbolFile(region.name, region.start);
									}
								}
							}

							ret = true;
						}
					}
				}
			}
		}
	#elif defined (Q_OS_FREEBSD)

		// TODO: use QT's way of doing this
		std::istringstream ss(qPrintable(line));
		std::string perms;

		std::string temp[9]
		//0x8048000 0x8057000 13 0 0xc27e64a4 r-x 2 0 0x0 COW NC vnode /usr/local/libexec/gam_server

		// read the starting address in hex
		if(ss >> std::hex >> region.start) {

			// read the ending address and then the permissions
			if(ss >> std::hex >> region.end) {
				if(ss >> temp[0] >> temp[1] >> temp[2] >> perms) {
					if(ss >> temp[3] >> temp[4] >> temp[5] >> temp[6] >> temp[7]>> temp[8]) {
						std::string name;

						char buf[256];
						ss.getline(buf, sizeof(buf), '\n');
						name = buf;

						// create a bitmask for the permissions
						region.m_Permissions = 0;
						if(perms[0] == 'r') region.m_Permissions |= MemRegion::READ;
						if(perms[1] == 'w') region.m_Permissions |= MemRegion::WRITE;
						if(perms[2] == 'x') region.m_Permissions |= MemRegion::EXEC;

						region.name = QString(name.c_str()).trimmed();

						ret = true;
					}
				}
			}
		}
	#else
		#error "Unsupported Platform"
	#endif
		return ret;
	}
}

//------------------------------------------------------------------------------
// Name: sync()
// Desc: reads a memory map file line by line
//------------------------------------------------------------------------------
void MemoryRegions::sync() {

	m_Regions.clear();

	if(m_PID != 0) {
#if defined (Q_OS_LINUX)
		const QString mapFile(QString("/proc/%1/maps").arg(m_PID));
#elif defined (Q_OS_FREEBSD)
		const QString mapFile(QString("/proc/%1/map").arg(m_PID));
#else
	#error "Unsupported Platform"
#endif
	
		QFile file(mapFile);
        if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
		
			QTextStream in(&file);
			QString line = in.readLine();
		
			while(!line.isNull()) {
				MemRegion region;
				if(proccessMapLine(m_SymbolManager, line, region)) {
					m_Regions.push_back(region);
				}
				line = in.readLine();
			}
		}
	}
	reset();
}

//------------------------------------------------------------------------------
// Name: findRegion(edb::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::findRegion(edb::address_t address) const {
	foreach(const MemRegion &i, m_Regions) {
		if(address >= i.start && address < i.end) {
			return true;
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: findRegion(edb::address_t address, MemRegion &region) const
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::findRegion(edb::address_t address, MemRegion &region) const {

	foreach(const MemRegion &i, m_Regions) {
		if(address >= i.start && address < i.end) {
			region = i;
			return true;
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QVariant MemoryRegions::data(const QModelIndex &index, int role) const {

	if(index.isValid() && role == Qt::DisplayRole) {
	
		const MemRegion &region = m_Regions[index.row()];
	
		switch(index.column()) {
		case 0:
			return QString("%1").arg(region.start, EDB_MAX_HEX, 16, QChar('0'));
		case 1:
			return QString("%1").arg(region.end, EDB_MAX_HEX, 16, QChar('0'));
		case 2:
			return QString("%1%2%3").arg(region.readable() ? 'r' : '-').arg(region.writable() ? 'w' : '-').arg(region.executable() ? 'x' : '-' );
		case 3:
			return region.name;
		}
	}
	
	return QVariant();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QModelIndex MemoryRegions::index(int row, int column, const QModelIndex &parent) const {
	Q_UNUSED(parent);

	if(row >= rowCount(parent) || column >= columnCount(parent)) {
		return QModelIndex();
	}
	
	return createIndex(row, column, const_cast<MemRegion *>(&m_Regions[row]));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QModelIndex MemoryRegions::parent(const QModelIndex &index) const {
	Q_UNUSED(index);
	return QModelIndex(); 
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int MemoryRegions::rowCount(const QModelIndex &parent) const {
	Q_UNUSED(parent);
	return m_Regions.size();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int MemoryRegions::columnCount(const QModelIndex &parent) const {
	Q_UNUSED(parent);
	return 4;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QVariant MemoryRegions::headerData(int section, Qt::Orientation orientation, int role) const {
	if(role == Qt::DisplayRole && orientation == Qt::Horizontal) {
		switch(section) {
		case 0:
			return tr("Start Address");
		case 1:
			return tr("End Address");
		case 2:
			return tr("Permissions");
		case 3:
			return tr("Name");
		}	
	}
	
	return QVariant();
}

