/*
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 "Configuration.h"
#include "DebuggerCoreInterface.h"
#include "DebuggerPluginInterface.h"
#include "DebuggerMain.h"
#include "Debugger.h"
#include "version.h"

#include <QApplication>
#include <QDir>
#include <QPluginLoader>
#include <QMessageBox>
#include <QString>
#include <QLibrary>
#include <QtDebug>
#include <QTranslator>
#include <QLibraryInfo>

#include <iostream>
#include <ostream>
#include <ctime>
#include <cstdlib>

namespace {
	//--------------------------------------------------------------------------
	// Name: loadPlugins(const QString &directory)
	// Desc: attemps to load all plugins in a given directory
	//--------------------------------------------------------------------------
	void loadPlugins(const QString &directory) {

		QDir pluginsDir(qApp->applicationDirPath());

		// TODO: attempt to detect the same plugin being loaded twice

		pluginsDir.cd(directory);

		Q_FOREACH(const QString &fileName, pluginsDir.entryList(QDir::Files)) {
			if(QLibrary::isLibrary(fileName)) {
				QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
				loader.setLoadHints(QLibrary::ExportExternalSymbolsHint);

				QObject *const plugin = loader.instance();

				if (plugin != 0) {

					// TODO: handle the case where we find more than one core plugin...
					DebuggerCoreInterface *const core_plugin = qobject_cast<DebuggerCoreInterface *>(plugin);
					if(core_plugin != 0) {
						if(edb::v1::debuggerBase == 0) {
							qDebug("[loadPlugins] Loading Core Plugin: %-25s : %p", qPrintable(fileName), static_cast<void *>(plugin));
							edb::v1::debuggerBase = core_plugin;
						}
					}

					DebuggerPluginInterface *const generic_plugin = qobject_cast<DebuggerPluginInterface *>(plugin);
					if(generic_plugin != 0) {
						if(edb::v1::registerPlugin(fileName, plugin)) {
							// we have a general purpose plugin
							qDebug("[loadPlugins] Loading Plugin: %-30s : %p", qPrintable(fileName), static_cast<void *>(plugin));
						}
					}
				} else {
					qDebug() << "[loadPlugins]" << qPrintable(loader.errorString());
				}
			}
		}
	}

	//--------------------------------------------------------------------------
	// Name: startDebugger(edb::pid_t attach_pid)
	// Desc: starts the main debugger code
	//--------------------------------------------------------------------------
	int startDebugger(edb::pid_t attach_pid, const QString &program, const QStringList &programArgs) {

		qDebug() << "Starting EDB Version:" << version_string;
		qDebug("Please Report Bugs & Requests At: http://bugs.codef00.com/");

		// create the main window object	
		DebuggerMain mainWin;

		// look for some plugins..
		loadPlugins(QDir().absolutePath());
		
		const QString pluginPath = edb::v1::config().pluginPath;
		if(QDir().absolutePath() != pluginPath) {
			loadPlugins(pluginPath);
		}

		// let the plugins setup their menus
		mainWin.finishPluginSetup(edb::v1::pluginList());

		// ok things are initialized to a reasonable degree, let's show the main window
		mainWin.show();

		// have we been asked to attach to a given program?	
		if(attach_pid != 0) {
			mainWin.doAttach(attach_pid);
		} else if(!program.isEmpty()) {
			mainWin.doRun(program, programArgs);
		}

		if(edb::v1::debuggerBase == 0) {
			QMessageBox::warning(0, 
				QObject::tr("EDB Failed To Load A Neccessary Plugin"), 
				QObject::tr(
				"Failed to successfully load the debugger core plugin. Please make sure it exists and that the plugin path is correctly configured.\n"
				"This is normal if EDB has not been previously run or the configuration file has been removed."));

			edb::v1::dialogOptions()->exec();
			
			QMessageBox::warning(0,
				QObject::tr("EDB"), 
				QObject::tr("EDB will now close. If you were successful in specifying the location of the debugger core plugin, please run EDB again.")
				);

			// TODO: detect if they corrected the issue and try again
			return -1;
		} else {
	    	return qApp->exec();
		}
	}
}


//------------------------------------------------------------------------------
// Name: main(int argc, char *argv[])
// Desc: entry point
//------------------------------------------------------------------------------
int main(int argc, char *argv[]) {
	
	QT_REQUIRE_VERSION(argc, argv, "4.5.0")

	QApplication app(argc, argv);
	QApplication::setWindowIcon(QIcon(":/debugger/images/edb48-logo.png"));
	
	std::srand(std::time(0));
	
	// setup organization info so settings go in right place
	QApplication::setOrganizationName("codef00.com");
	QApplication::setOrganizationDomain("codef00.com");
	QApplication::setApplicationName("EDB");
	
	// load some translations
	QTranslator qtTranslator;
	qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
	app.installTranslator(&qtTranslator);

	QTranslator myappTranslator;
	myappTranslator.load("edb_" + QLocale::system().name());
	app.installTranslator(&myappTranslator);
	
	const QStringList args = app.arguments();
	edb::pid_t attach_pid = 0;
	QStringList runArgs;
	QString runApp;

	if(args.size() > 1) {
		if(args.size() == 3 && args[1] == "--attach") {
			attach_pid = args[2].toUInt();
		} else if(args.size() >= 3 && args[1] == "--run") {
			runApp = args[2];
			
			for(int i = 3; i < args.size(); ++i) {
				runArgs.push_back(args[i]);
			}
		} else if(args.size() == 2 && args[1] == "--version") {
			std::cout << "EDB Version: " << version_string << std::endl;
			return 0;
		} else {
			std::cerr << "usage: " << qPrintable(args[0]) << " [ --attach <pid> ] [ --run <program> (args...) ] [ --version ]" << std::endl;
			return -1;
		}
	}

	return startDebugger(attach_pid, runApp, runArgs);
}
