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

//------------------------------------------------------------------------------
// 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)) {
		if(processSymbolFile(m_SymbolDirectory + QDir::separator() + name + ".map", base, filename)) {
			m_SymbolFiles.insert(name);
		}
	}
}

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

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


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

	QMap<edb::address_t, const Symbol *>::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 0;
			}
			--it;
		}

		const Symbol *const sym = it.value();
		if(sym != 0) {
			if(address >= sym->address && address < sym->address + sym->size) {
				return sym;
			}
		}
	}
	
	return 0;
}

//------------------------------------------------------------------------------
// Name: md5FromString(const QString &s, bool &ok)
// Desc: converts a string representing an MD5 into a byte array
//------------------------------------------------------------------------------
QByteArray SymbolManager::md5FromString(const QString &s, bool &ok) {
	QByteArray ret;
	if(s.length() == 16 * 2) {
		for(int i = 0; i < 16; ++i) {
			const quint8 byte = s.mid(i * 2, 2).toUInt(&ok, 16);
			if(ok) {
				ret.push_back(byte);
			} else {
				break;
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// 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) {

	edb::v1::log("loading symbols: %s", qPrintable(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) {
			
				bool ok;
				const QByteArray fileMD5 = md5FromString(md5.c_str(), ok);
				if(ok) {
					const QByteArray actualFileMD5 = edb::v1::getFileMD5(libraryFilename);
					
					if(fileMD5 != actualFileMD5) {
						edb::v1::log("Your symbol file for %s appears to not match the actual file, perhaps you should rebuild your symbols?", qPrintable(libraryFilename));
					}
				}
				const QString prefix = edb::v1::basename(filename.c_str());

				while(file >> std::hex >> sym_start >> std::hex >> sym_end >> sym_type >> sym_name) {
					Symbol sym;
					
					sym.file			= f;
					sym.name_no_prefix	= sym_name.c_str();
					sym.name			= (prefix + "::" + sym.name_no_prefix);
					sym.address			= sym_start;
					sym.size			= sym_end;

					// fixup the base address based on where it is loaded
					if(sym.address < base) {
						sym.address += base;
					}

					m_Symbols.append(sym);

					const Symbol *const p = &m_Symbols.back();

					m_SymbolsByAddress[p->address]	= p;
					m_SymbolsByName[p->name]		= p;
				}
			}
		}
	}
	
	return true;
}

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