/*
 * debtags - Implement package tags support for Debian
 *
 * Copyright (C) 2007  Enrico Zini <enrico@debian.org>
 * Copyright (C) 2007, 2008  Miriam Ruiz <little_miry@yahoo.es>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#define APPNAME PACKAGE_NAME
#else
#warning No config.h found: using fallback values
#define APPNAME __FILE__
#define PACKAGE_VERSION "unknown"
#endif

#include "Environment.h"
#include "GamesOptions.h"
#include "Engine.h"

#include "common.h"
#include "ui.h"
#include "filter.h"

#include <ept/apt/packagerecord.h>
#include <wibble/regexp.h>
#include <wibble/string.h>
#include <xapian.h>

#include <iostream>
#include <fstream>
#include <cmath>

#ifdef USE_GETTEXT
#include <libintl.h>
#include <locale.h>
#else
// Work-around until goplay is actually gettextised
#define gettext(a) (a)
#endif

#include <FL/Fl.H>
#include <FL/Fl_PNG_Image.H>
#include <FL/fl_ask.H>

#include <string.h>

#include "libxdgutils/XDGDesktopEntry.h"

namespace std {

template<typename TAG, typename _Traits>
basic_ostream<char, _Traits>& operator<<(basic_ostream<char, _Traits>& out, const std::set<TAG>& tags)
{
	for (typename std::set<TAG>::const_iterator i = tags.begin();
			i != tags.end(); i++)
		if (i == tags.begin())
			out << i->fullname();
		else
			out << ", " << i->fullname();
	return out;
}

template<typename TAG, typename _Traits>
basic_ostream<char, _Traits>& operator<<(basic_ostream<char, _Traits>& out, const wibble::Singleton<TAG>& tags)
{
	out << *tags.begin();
	return out;
}

template<typename TAG, typename _Traits>
basic_ostream<char, _Traits>& operator<<(basic_ostream<char, _Traits>& out, const wibble::Empty<TAG>&)
{
	return out;
}

}

using namespace std;
using namespace ept;
using namespace ept::debtags;
using namespace ept::apt;

static char* tagString(const std::string& tag)
{
	static map<string, char*> table;
	map<string, char*>::iterator i = table.find(tag);
	if (i == table.end())
	{
		pair< map<string, char*>::iterator, bool > tmp =
			table.insert(make_pair(tag, strdup(tag.c_str())));
		i = tmp.first;
	}
	return i->second;
}
static char* pkgString(const std::string& name)
{
	static map<string, char*> table;
	map<string, char*>::iterator i = table.find(name);
	if (i == table.end())
	{
		pair< map<string, char*>::iterator, bool > tmp =
			table.insert(make_pair(name, strdup(name.c_str())));
		i = tmp.first;
	}
	return i->second;
}

static void printResults(Engine& engine)
{
	const vector<Result>& packages = engine.results();
	for (vector<Result>::const_iterator i = packages.begin();
			i != packages.end(); ++i)
	{
		PackageRecord pkg = engine.apt().rawRecord(i->name);
		cerr << "PKG " << pkg.package() << " - " << pkg.shortDescription() << endl;
	}

	const set<string>& ttags = engine.types();
	for (set<string>::const_iterator i = ttags.begin();
			i != ttags.end(); ++i)
	{
		cerr << "TTAG " << *i << endl;
	}

	const set<string>& ftags = engine.interfaces();
	for (set<string>::const_iterator i = ftags.begin();
			i != ftags.end(); ++i)
	{
		cerr << "ITAG " << *i << endl;
	}
}

static const char* ReadFlChoice(Fl_Choice& c)
{
	const Fl_Menu_Item* cur = c.mvalue();
	if (cur->user_data_)
		return (const char*)cur->user_data_;
	else
		return "";
}

static void UpdateUILists(GamesUI& ui)
{
	// Defining this here, non-const, because fltk's interface want non-const
	// strings and I did not have time to check whether it would attempt to
	// modify the parameter or not.  Likely not, and thus a
	// const_cast<char*>(VoidString) would probably have been better.
	// --Enrico
	char VoidString[1] = "";
	Engine& engine = *ui.engine;
	const char* oldType = ReadFlChoice(*ui.TypeSelection);
	const char* oldIface = ReadFlChoice(*ui.InterfaceSelection);
	ui.TypeSelection->clear();
	ui.TypeSelection->add(_("Any type"), 0, NULL, VoidString, FL_NORMAL_LABEL);
	ui.InterfaceSelection->clear();
	ui.InterfaceSelection->add(_("Any interface"), 0, NULL, VoidString, FL_NORMAL_LABEL);

	static int widths_with_popcon[] = { 100, 300, 0 }; // widths for each column
	static int widths_without_popcon[] = { 100, 0 };
	ui.ResultsBrowser->clear();
	if (engine.hasPopcon())
	{
		char empty_string[1] = "";
		ui.ResultsBrowser->column_widths(widths_with_popcon);
		// tab delimited columns with colors
		ui.ResultsBrowser->add(_("@B12@C7@b@.PACKAGE\t@B12@C7@b@.DESCRIPTION\t@B12@C7@b@.POPCON"), empty_string);
	}
	else
	{
		char empty_string[1] = "";
		ui.ResultsBrowser->column_widths(widths_without_popcon);
		// tab delimited columns with colors
		ui.ResultsBrowser->add(_("@B12@C7@b@.PACKAGE\t@B12@C7@b@.DESCRIPTION"), empty_string);
	}

	// FIXME: there are better ways to remember the previous item
	
	const set<string> types = engine.types();
	int newIdx = 0;
	for (set<string>::const_iterator i = types.begin();
			i != types.end(); ++i)
	{
		const voc::TagData* td = engine.voc().tagData(*i);
		if (!td) continue;
		int idx = ui.TypeSelection->add(gettext(td->shortDescription().c_str()),
							0, NULL, tagString(*i), FL_NORMAL_LABEL);
		if (*i == oldType)
			newIdx = idx;
	}
	ui.TypeSelection->value(newIdx);
	
	const set<std::string> ifaces = engine.interfaces();
	newIdx = 0;
	for (set<std::string>::const_iterator i = ifaces.begin();
			i != ifaces.end(); ++i)
	{
		const voc::TagData* td = engine.voc().tagData(*i);
		if (!td) continue;
		int idx = ui.InterfaceSelection->add(gettext(td->shortDescription().c_str()),
							0, NULL, tagString(*i), FL_NORMAL_LABEL);
		if (*i == oldIface)
			newIdx = idx;
	}
	ui.InterfaceSelection->value(newIdx);

	const std::vector<Result> res = engine.results();
	for (vector<Result>::const_iterator i = res.begin();
			i != res.end(); ++i)
	{
		PackageRecord rec(engine.apt().rawRecord(i->name));
		char* userData = pkgString(rec.package());

		// Available Colors: FL_BLACK, FL_BLUE, FL_CYAN, FL_DARK_BLUE,
		// FL_DARK_CYAN, FL_DARK_GREEN FL_DARK_MAGENTA, FL_DARK_RED,
		// FL_DARK_YELLOW, FL_GREEN, FL_MAGENTA, FL_RED, FL_WHITE, FL_YELLOW

		Fl_Color bk(FL_WHITE);
		Fl_Color fr(FL_BLACK);
		set<std::string> tags = ui.engine->debtags().getTagsOfItem((const char *)rec.package().c_str());
		switch (pkgfilter.TagsValue(tags))
		{
			case PackageFilter::Green:
				fr = FL_BLACK; bk = FL_GREEN; break;
			case PackageFilter::Yellow:
				fr = FL_BLACK; bk = FL_YELLOW; break;
			case PackageFilter::Red:
				fr = FL_WHITE; bk = FL_RED; break;
			case PackageFilter::Black:
				fr = FL_WHITE; bk = FL_BLACK; break;
			default:
				fr = FL_BLACK; bk = FL_WHITE; break;
		}

		char fmtstr[16];
		snprintf(fmtstr, sizeof(fmtstr), "@B%d@C%d@.", bk, fr);

		string desc = string(fmtstr) + rec.package() + "\t" + 
			string(fmtstr) + rec.shortDescription();
		if (engine.hasPopcon() && i->popcon)
		{
			desc += "\t" + string(fmtstr);
			char stars[16];
			snprintf(stars, sizeof(stars), "%%%d/8/1;",
				(int)rintf(log(i->popcon) * 100 / log(engine.popconLocalMax())));
			desc += stars;
			//printf ("%s (%s): POPCON=%f\n", rec.package().c_str(), rec.shortDescription().c_str(), i->popcon);
		}
		ui.ResultsBrowser->add(desc.c_str(), userData);

		// Relevance is 0 to 100
		// Popcon is a weird floating point number (to be improved)
		//FIXMEaddToResults(rec.package() + " - " + rec.shortDescription(), i->relevance, i->popcon);
	}
}

static void CallBackTypeSelection(Fl_Choice* choice, void *data)
{
	//printf("CallBackTypeSelection\n");
	//fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	ui.engine->setTypeFilter(ReadFlChoice(*choice));
	UpdateUILists(ui);
}

static void CallBackInterfaceSelection(Fl_Choice* choice, void *data)
{
	//printf("CallBackInterfaceSelection\n");
	//fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	ui.engine->setInterfaceFilter(ReadFlChoice(*choice));
	UpdateUILists(ui);
}

static void CallBackSearchInput(Fl_Input* input, void *data)
{
	//printf("CallBackSearchInput\n"); fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	ui.engine->setKeywordFilter(input->value());
	UpdateUILists(ui);
}

static void CallBackAlreadyInstalled(Fl_Round_Button*, void *data)
{
	//printf("CallBackInstalled\n"); fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	ui.AlreadyInstalled->value(1); ui.AlreadyInstalled->redraw();
	ui.ToBeInstalled->value(0); ui.ToBeInstalled->redraw();
	ui.InstalledOrNot->value(0); ui.InstalledOrNot->redraw();
	ui.engine->setInstalledFilter(Engine::INSTALLED);
	UpdateUILists(ui);
}

static void CallBackToBeInstalled(Fl_Round_Button*, void *data)
{
	//printf("CallBackToBeInstalled\n"); fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	ui.AlreadyInstalled->value(0); ui.AlreadyInstalled->redraw();
	ui.ToBeInstalled->value(1); ui.ToBeInstalled->redraw();
	ui.InstalledOrNot->value(0); ui.InstalledOrNot->redraw();
	ui.engine->setInstalledFilter(Engine::NOTINSTALLED);
	UpdateUILists(ui);
}

static void CallBackInstalledOrNot(Fl_Round_Button*, void *data)
{
	//printf("CallBackToBeInstalled\n"); fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	ui.AlreadyInstalled->value(0); ui.AlreadyInstalled->redraw();
	ui.ToBeInstalled->value(0); ui.ToBeInstalled->redraw();
	ui.InstalledOrNot->value(1); ui.InstalledOrNot->redraw();
	ui.engine->setInstalledFilter(Engine::ANY);
	UpdateUILists(ui);
}

static void StartPackage(const std::string pkgname)
{
	// find desktop file, fork, exec
  
	std::vector<std::string> desktoplist;

	std::string pkglistfile =
	  std::string("/var/lib/dpkg/info/") + pkgname + ".list";
	std::ifstream pkglist(pkglistfile.c_str());
	if (pkglist.is_open())
	{
		while ( pkglist.good() )
		{
			std::string line;
			std::getline (pkglist,line);
			if (0 == line.find("/usr/share/applications/")
			    && line.npos != line.find(".desktop")) {
				std::cout << line << endl;
				desktoplist.push_back(line);
			}
		}
		pkglist.close();
	}
	else
		std::cout << "Unable to open file" << std::endl; 
	if (1 == desktoplist.size()) {
		std::string desktopfile(desktoplist.back());
		std::cout << "Running " << desktopfile << endl;
		XDGDesktopEntry entry(desktopfile.c_str());
		entry.run("");
	} else {
		std::cout << "Not only one desktop file" << std::endl; 
	}
}

static void CallBackScreenshotClick(Fl_Round_Button*, void *data)
{
	printf("CallBackScreenshotClick\n"); fflush(stdout);
	GamesUI& ui = *static_cast<GamesUI*>(data);
	PackageBrowser *ResultsBrowser = ui.ResultsBrowser;
	std::string pkgname = ResultsBrowser->pkg_selected();
	PackageRecord rec(ui.engine->apt().rawRecord(pkgname));

	bool isinstalled = true; // FIXME find way to figure this out
	if (isinstalled) {
		StartPackage(pkgname);
	}
}

#if 0
static void OnResultsBrowserClick(Fl_Browser*, void *data)
{
	printf("OnResultsBrowserClick\n"); fflush(stdout);
//	GamesUI& ui = *static_cast<GamesUI*>(data);
}
#endif

#ifndef UNIT_TEST
int main(int argc, const char* argv[])
{
#ifdef USE_GETTEXT
	setlocale (LC_MESSAGES, "");
	setlocale (LC_CTYPE, "");
	setlocale (LC_COLLATE, "");
	textdomain ("gamesui");
	bindtextdomain ("gamesui", NULL);
#endif

	wibble::commandline::GamesOptions opts;

	try {
		// Install the handler for unexpected exceptions
		wibble::exception::InstallUnexpected installUnexpected;

		if (opts.parse(argc, argv))
			return 0;

		if (opts.out_verbose->boolValue())
			::Environment::get().verbose(true);

		if (opts.out_debug->boolValue())
			::Environment::get().debug(true);

		Engine engine;

		if (wibble::str::endsWith(argv[0], "learn") || opts.gowhere->stringValue() == "learn")
		{
			engine.mainFacet = "field";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_AND,
									Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program")),
									Xapian::Query("XTuse::learning"));
		} else if (wibble::str::endsWith(argv[0], "admin") || opts.gowhere->stringValue() == "admin") {
			engine.mainFacet = "admin";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program"));
		} else if (wibble::str::endsWith(argv[0], "net") || opts.gowhere->stringValue() == "net") {
			engine.mainFacet = "network";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program"));
		} else if (wibble::str::endsWith(argv[0], "office") || opts.gowhere->stringValue() == "office") {
			engine.mainFacet = "office";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program"));
		} else if (wibble::str::endsWith(argv[0], "safe") || opts.gowhere->stringValue() == "safe") {
			engine.mainFacet = "security";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program"));
		} else if (wibble::str::endsWith(argv[0], "web") || opts.gowhere->stringValue() == "web") {
			engine.mainFacet = "web";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program"));
		} else if (wibble::str::endsWith(argv[0], "science") || opts.gowhere->stringValue() == "science") {
			engine.mainFacet = "field";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query(Xapian::Query::OP_OR,
										Xapian::Query("XTrole::documentation"),
										Xapian::Query("XTrole::program"));
		} else {
			engine.mainFacet = "game";
			engine.secondaryFacet = "interface";
			engine.globalFilter = Xapian::Query("XTrole::program");
		}

		if (opts.mainFacet->isSet())
			engine.mainFacet = opts.mainFacet->stringValue();

		if (opts.secondaryFacet->isSet())
			engine.secondaryFacet = opts.secondaryFacet->stringValue();

		if (opts.ftags->isSet())
		{
			Xapian::Query fquery;
			wibble::Splitter tags(", *", REG_EXTENDED);
			bool first = true;
			for (wibble::Splitter::const_iterator i = tags.begin(opts.ftags->stringValue());
					i != tags.end(); ++i)
			{
				if (first)
				{
					fquery = Xapian::Query("XT"+*i);
					first = false;
				}
				else
					fquery = Xapian::Query(Xapian::Query::OP_AND, fquery, Xapian::Query("XT"+*i));
			}
			engine.globalFilter = fquery;
		}

		/*
		cerr << " *** Initial:" << endl;
		printResults(engine);

		engine.setTypeFilter(engine.voc().tagByName("game::arcade"));
		cerr << " *** Arcades:" << endl;
		printResults(engine);

		engine.setInterfaceFilter(engine.voc().tagByName("interface::x11"));
		cerr << " *** X11 Arcades:" << endl;
		printResults(engine);

		engine.setInstalledFilter(Engine::INSTALLED);
		cerr << " *** Installed X11 Arcades:" << endl;
		printResults(engine);
		*/

		GamesUI mainui;
		mainui.engine = &engine;
		Fl_Double_Window *window = mainui.CreateWindows();
		mainui.TypeSelection->callback((Fl_Callback*)CallBackTypeSelection, &mainui);
		mainui.TypeSelection->when(FL_WHEN_CHANGED);
		mainui.InterfaceSelection->callback((Fl_Callback*)CallBackInterfaceSelection, &mainui);
		mainui.InterfaceSelection->when(FL_WHEN_CHANGED);
		mainui.SearchInput->callback((Fl_Callback*)CallBackSearchInput, &mainui);
		mainui.SearchInput->when(FL_WHEN_CHANGED);
		mainui.AlreadyInstalled->callback((Fl_Callback*)CallBackAlreadyInstalled, &mainui);
		mainui.AlreadyInstalled->when(FL_WHEN_CHANGED);
		mainui.ToBeInstalled->callback((Fl_Callback*)CallBackToBeInstalled, &mainui);
		mainui.ToBeInstalled->when(FL_WHEN_CHANGED);
		mainui.InstalledOrNot->callback((Fl_Callback*)CallBackInstalledOrNot, &mainui);
		mainui.InstalledOrNot->when(FL_WHEN_CHANGED);

		mainui.AlreadyInstalled->value(0); mainui.AlreadyInstalled->redraw();
		mainui.ToBeInstalled->value(0); mainui.ToBeInstalled->redraw();
		mainui.InstalledOrNot->value(1); mainui.InstalledOrNot->redraw();
		mainui.engine->setInstalledFilter(Engine::ANY);

		mainui.ScreenshotWidget->callback((Fl_Callback*)CallBackScreenshotClick, &mainui);
		mainui.Screenshot(new Fl_PNG_Image(FILE_NO_SCREENSHOT));
		mainui.AboutView->load(HTMLDIR "/about.en.html");

		UpdateUILists(mainui);

		window->show(argc, (char**)argv);
		while (Fl::wait());
		return 0;
	} catch (wibble::exception::BadOption& e) {
		cerr << e.desc() << endl;
		opts.outputHelp(cerr);
		return 1;
	} catch (std::exception& e) {
		cerr << e.what() << endl;
		return 1;
	} catch (Xapian::InvalidArgumentError& e) {
		cerr << "Xapian " << e.get_type() << ": " << e.get_msg();
		if (!e.get_context().empty())
			cerr << ". Context: " << e.get_context();
		cerr << endl;
		cerr << endl;
		cerr << "It could be that your Apt Xapian index is missing: you can create it by running ept-cache reindex as root." << endl;
		fl_alert("Error: It could be that your Apt Xapian index is missing: you can create it by running ept-cache reindex as root.");
	} catch (Xapian::DatabaseVersionError& e) {
		cerr << "Xapian " << e.get_type() << ": " << e.get_msg();
		if (!e.get_context().empty())
			cerr << ". Context: " << e.get_context();
		cerr << endl;
		cerr << endl;
		cerr << "Please recreate the database by removing /var/lib/apt-xapian and running ept-cache reindex as root." << endl;
		fl_alert("Error: Please recreate the database by removing /var/lib/apt-xapian and running ept-cache reindex as root.");
	} catch (Xapian::Error& e) {
		cerr << "Xapian " << e.get_type() << ": " << e.get_msg();
		if (!e.get_context().empty())
			cerr << ". Context: " << e.get_context();
		cerr << endl;
		return 1;
	}

}
#endif

#include <ept/debtags/debtags.tcc>

// vim:set ts=4 sw=4:
