/*
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 "SymbolManager.h"
#include "Debugger.h"
#include "MD5.h"
#include <QFile>
#include <QDir>
#include <QtDebug>
#include <QProcess>
#include <istream>
#include <fstream>
#include <iostream>

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void SymbolManager::loadSymbols(const QString &symbolDirectory) {
	m_SymbolDirectory = symbolDirectory;
	clear();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void SymbolManager::clear() {
	m_SymbolFiles.clear();
	m_Symbols.clear();
	m_SymbolsByAddress.clear();
	m_SymbolsByName.clear();
}

//------------------------------------------------------------------------------
// Name: loadSymbolFile(const QString &filename, edb::address_t base)
// Desc: 
//------------------------------------------------------------------------------
void SymbolManager::loadSymbolFile(const QString &filename, edb::address_t base) {

	const QString name = edb::v1::basename(filename);

	if(!m_SymbolFiles.contains(name)) {
		const QString map_file = QString("%1/%2.map").arg(m_SymbolDirectory, name);
		
#if 0
		// TODO: this is just PoC code, not ripe for release since it isn't portable
		// and doesn't play nice with edb being called from a different directory
		if(!QFile(map_file).exists()) {
			QString cmd = QString("./edb_make_symbolmap.sh %1 > %2/%3.map").arg(filename, m_SymbolDirectory, name);
			QProcess proc;
			proc.start("sh", QStringList() << "-c" << cmd);
			proc.waitForFinished();
		}
#endif

		if(processSymbolFile(map_file, base, filename)) {
			m_SymbolFiles.insert(name);
		}
	}
}

//------------------------------------------------------------------------------
// Name: find(const QString &name) const
// Desc: 
//------------------------------------------------------------------------------
const SymbolManager::SymbolP SymbolManager::find(const QString &name) const {
	QMap<QString, SymbolP>::const_iterator it = m_SymbolsByName.find(name);
	if(it != m_SymbolsByName.end()) {
		return it.value();
	}
	return SymbolP();
}

//------------------------------------------------------------------------------
// Name: find(edb::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
const SymbolManager::SymbolP SymbolManager::find(edb::address_t address) const {
	QMap<edb::address_t, SymbolP>::const_iterator it = m_SymbolsByAddress.find(address);
	if(it != m_SymbolsByAddress.end()) {
		return it.value();
	}
	return SymbolP();
}

//------------------------------------------------------------------------------
// Name: findNearSymbol(edb::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
const SymbolManager::SymbolP SymbolManager::findNearSymbol(edb::address_t address) const {

	QMap<edb::address_t, SymbolP>::const_iterator it = m_SymbolsByAddress.lowerBound(address);
	if(it != m_SymbolsByAddress.end()) {
	
		// not an exact match, we should backup one
		if(address != it.value()->address) {
			// not safe to backup!, return early
			if(it == m_SymbolsByAddress.begin()) {
				return SymbolP();
			}
			--it;
		}

		SymbolP sym = it.value();
		if(sym) {
			if(address >= sym->address && address < sym->address + sym->size) {
				return sym;
			}
		}
	}
	
	return SymbolP();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void SymbolManager::addSymbol(const SymbolManager::SymbolP &symbol) {
	Q_ASSERT(symbol);
	m_Symbols.append(symbol);
	m_SymbolsByAddress[symbol->address]	= symbol;
	m_SymbolsByName[symbol->name]		= symbol;
}

//------------------------------------------------------------------------------
// Name: processSymbolFile(const QString &f, edb::address_t base, const QString &libraryFilename)
// Desc: 
//------------------------------------------------------------------------------
bool SymbolManager::processSymbolFile(const QString &f, edb::address_t base, const QString &libraryFilename) {

	// TODO: support filename starting with "http://" being fetched from a web server

	qDebug() << "loading symbols:" << f;
	std::ifstream file(qPrintable(f));
	if(file) {
		
		edb::address_t sym_start;
		edb::address_t sym_end;
		char sym_type;
		std::string sym_name;
		std::string date;
		std::string md5;
		std::string filename;

		if(std::getline(file, date)) {
			if(file >> md5 >> filename) {
			
				const QByteArray fileMD5 = QByteArray::fromHex(md5.c_str());
				const QByteArray actualFileMD5 = edb::v1::getFileMD5(libraryFilename);

				if(fileMD5 != actualFileMD5) {
					qDebug() << "Your symbol file for" << libraryFilename << "appears to not match the actual file, perhaps you should rebuild your symbols?";
				}

				const QString prefix = edb::v1::basename(QString::fromStdString(filename));

				while(file >> std::hex >> sym_start >> std::hex >> sym_end >> sym_type >> sym_name) {
					SymbolP sym(new Symbol);
					
					sym->file			= f;
					sym->name_no_prefix	= QString::fromStdString(sym_name);
					sym->name			= QString("%1::%2").arg(prefix, sym->name_no_prefix);
					sym->address		= sym_start;
					sym->size			= sym_end;
					sym->type			= sym_type;

					// fixup the base address based on where it is loaded
					if(sym->address < base) {
						sym->address += base;
					}
					
					addSymbol(sym);
				}
				return true;
			}
		}
	}
	
	// TODO: should we return false and try again later?
	return true;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
const QList<SymbolManager::SymbolP> SymbolManager::symbols() const {
	return m_Symbols;
}
