//
// C++ Implementation: packagesearchimpl
//
// Description: 
//
//
// Author: Benjamin Mesing <bensmail@gmx.net>, (C) 2004
//
// Copyright: See COPYING file that comes with this distribution
//
//

#include "packagesearchimpl.h"

#include <iostream>
#include <algorithm>
#include <functional>
#include <cassert>


#include <qaction.h>
#include <QApplication>
#include <qclipboard.h>
#include <QCloseEvent>
#include <qcursor.h>
#include <QDesktopServices>
#include <QDir>
#include <qicon.h>
#include <QList>
#include <qmessagebox.h>
#include <QNetworkAccessManager>
#include <qpixmap.h>
#include <qstatusbar.h>
#include <QStringList>
#include <qtabwidget.h>
#include <QTime>
#include <QTimer>
#include <QToolBar>
#include <QWhatsThis>
#include <QtDebug>



// NApplication
#include "applicationfactory.h"
#include "runcommand.h"
#include "runcommandforoutput.h"
#include "gainroot.h"

// namespace NPlugin 
#include "plugin.h"
#include "searchplugin.h"
#include "actionplugin.h"
#include "informationplugin.h"
#include "shortinformationplugin.h"
#include "plugincontainer.h"
#include "pluginmanager.h"
#include "packagenotfoundexception.h"
#include "plugincompare.h"
#include "settingsdlg.h"
#include "packagenameplugin.h"
#include "scoredisplayplugin.h"

// NXml
#include "xmldata.h"

// NException
#include "exception.h"

#include "extalgorithm.h"


// NPackageSearch
#include "packagesearchaboutdlg.h"
#include "packagedisplaywidget.h"

// NUtil
#include "helpers.h"

#undef slots
#include <xapian.h>

#include <ept/apt/apt.h>
#include <ept/debtags/debtags.h>
#include <ept/debtags/vocabulary.h>
#include <ept/axi/axi.h>


using namespace std;

template <typename PluginType> 
bool LesserPriority<PluginType>::operator()(PluginType const * const p1, PluginType const * const p2) const
{
	return (*p1) < (*p2);
}

template class LesserPriority<NPlugin::SearchPlugin>;
template class LesserPriority<NPlugin::InformationPlugin>;
template class LesserPriority<NPlugin::ShortInformationPlugin>;


PackageSearchImpl::PackageSearchImpl( QWidget* parent, const char* name, Qt::WindowFlags fl )
: QMainWindow(parent, fl), _DETAILS_INFORMATION_PRIORITY(2), _network(NUtil::NetworkSettings::SYSTEM_PROXY)
{
	setObjectName(name);
	setupUi(this);

	_pApt = 0;
	_pVocabulary = 0;
	_pXapianDatabase = 0;
	// set up what's this actions
	_pWhatsThisAction = QWhatsThis::createAction(this);
	_pWhatsThisAction->setShortcut(QKeySequence("Shift+F1"));
	
	initDirectories();
	// don't change order, because initMenus relies on an initialized toolbar
	initToolbars();
	initMenus();

	// delete placeholder ListViewWidget and replace by a custom class
	delete _pPackageView;
	_pPackageView = new NPackageSearch::PackageDisplayWidget(_pPackageViewFrame);
	_pPackageView->setWhatsThis(tr("Displays the packages matching the search entered above."));
 	_pPackageViewFrame->layout()->addWidget(_pPackageView);
	
	
	_pPackageNamePlugin = 0;
	_pScoreDisplayPlugin = 0;
	_settingsFilename = QDir::homePath()+"/.packagesearch";
	_pInformationDetailsPage = _pInformationContainer->widget(0);

    _pNetworkAccessManager = new QNetworkAccessManager(this);

	// setting up window geometry (must happen before loading settings
	// since otherwise stored geometry would be overridden)
    uint width = 1000;
    uint height = 800;
	QList<int> sizes;
	sizes.push_back(width/2);
	sizes.push_back((width+1)/2);
	resize(width, height);
	_pUpperVSplitter->setSizes(sizes);
	_pLowerVSplitter->setSizes(sizes);


	vector<string> pluginDirectories;
	pluginDirectories.push_back("plugins/");
    pluginDirectories.push_back("/usr/lib/packagesearch/");
	_pPluginManager = new NPlugin::PluginManager(pluginDirectories, this);
	_pPluginManager->addPluginUser(this);
	_pPluginManager->addPluginUser(packageView());
	loadSettings();	// must be called after setting _pPluginManager but before calling loadPlugins
	
	QPixmap appIcon(_iconDir+"packagesearch.png");
	setWindowIcon(appIcon);

	connect(_pClearSearchButton, SIGNAL(clicked()), SLOT(onClearSearch()));
	connect(_pBackButton, SIGNAL(clicked()), SLOT(onBack()));
	connect(_pForwardButton, SIGNAL(clicked()), SLOT(onForward()));
	connect(_pControlPluginsAction, SIGNAL(triggered()), SLOT(onControlPlugins()));
	connect(_pPreferencesAction, SIGNAL(triggered()), SLOT(onPreferences()));
	connect(_pExitAction, SIGNAL(triggered()), SLOT(close()));
	connect(_pHelpAboutAction, SIGNAL(triggered()), SLOT(onHelpAboutAction()));
	connect(_pHelpContentsAction, SIGNAL(triggered()), SLOT(onHelpContentsAction()));
    
	connect(_pPackageView, SIGNAL(packageSelected(QString)), SLOT(onPackageSelectionChanged(QString)));
    connect(_pPackageSelection, SIGNAL(currentTextChanged(const QString&)), SLOT(selectNewPackage(QString)));
	connect(_pDetailsView, SIGNAL(anchorClicked(const QUrl&)), SLOT(onLinkClicked(const QUrl&)));
	connect(_pInformationContainer, SIGNAL(currentChanged(int)), SLOT(onInformationPageChanged()));
	// query aptDB for updates every 2s
	connect(&timer, SIGNAL(timeout()), SLOT(checkAptDbForUpdates()));
	timer.start(2000);
}

PackageSearchImpl::~PackageSearchImpl()
{
	delete _pPackageNamePlugin;
	delete _pScoreDisplayPlugin;
	delete _pApt;
	delete _pVocabulary;
	delete _pXapianDatabase;
}


/////////////////////////////////////////////////////
// Initialisation
/////////////////////////////////////////////////////

void PackageSearchImpl::initDirectories()
{
	// search for doc path
	if  ( QFile("../doc/content.html").exists() )
	{
		QDir docDir = QDir::current();
		docDir.cd("../doc/");
		_docDir = docDir.absolutePath()+"/";
	}
	else if ( QFile("doc/content.html").exists() )
	{
		QDir docDir = QDir::current();
		docDir.cd("doc/");
		_docDir = docDir.absolutePath()+"/";
	}
	else if ( QFile("/usr/share/doc/packagesearch/content.html").exists() )
		_docDir = "/usr/share/doc/packagesearch/";
	else
		cerr << "Could not find doc directory. It must be located at "
		"/usr/share/doc/packagesearch/, ../doc/ or doc/."<<endl;
	// search for icon path
	if ( QFile("../icons/packagesearch.png").exists() )
		_iconDir = "../icons/";
	else if ( QFile("icons/packagesearch.png").exists() )
		_iconDir = "icons/";
	else if ( QFile("/usr/share/pixmaps/packagesearch/packagesearch.png").exists() )
		_iconDir = "/usr/share/pixmaps/packagesearch/";
	else
		cerr << "Could not find icon directory. It must located at "
			"/usr/share/pixmaps/packagesearch, ../icons/ or icons/."<<endl;
}


void PackageSearchImpl::initToolbars()
{
	_pMainToolBar = addToolBar(tr("Main Toolbar"));
	_pMainToolBar->setIconSize(QSize(16,16));
	_pMainToolBar->addAction(_pWhatsThisAction);
	_nameToToolBar["Main"] = _pMainToolBar;

	// setting up brows toolbar
	QIcon backIcon(_iconDir + "back.png");
	QIcon forwardIcon(_iconDir + "forward.png");
	_pBackButton->setIcon(backIcon);
	_pForwardButton->setIcon(forwardIcon);
	updateBrowseToolbar();

}

void PackageSearchImpl::initMenus()
{
	_pViewMenu->addAction(_pMainToolBar->toggleViewAction());
	_pHelpMenu->insertAction(_pHelpContentsAction, _pWhatsThisAction);

	_nameToMenu["Packagesearch"] = _pPackagesearchMenu;
	_nameToMenu["View"] = _pViewMenu;
	_nameToMenu["Packages"] = _pPackagesMenu;
	_nameToMenu["System"] = _pSystemMenu;
	_nameToMenu["Help"] = _pHelpMenu;
}

void PackageSearchImpl::initialize()
{

	_pApt = new ept::apt::Apt;
	_pVocabulary = new ept::debtags::Vocabulary;
	// TODO: check if the database is up-to-date (though 
	// apt-xapian-index by default updates the index after installation and weekly
	bool success = false;
	// try running until Xapian::Database can be constructed
	do 
	{
		try {
			_pXapianDatabase = new Xapian::Database("/var/lib/apt-xapian-index/index");
			success = true;
		} catch (Xapian::RuntimeError& e) {
			if ( QMessageBox::critical(this, tr("Error trying to open Xapian database"), 
					tr("<p>An error occurred trying to open the xapian database at "
						"/var/lib/apt-xapian-index/index please make sure the file is "
						"readable and valid. You may try to run update-apt-xapian-index "
						"to fix this problem. The orginal error message was:<br>\"") +
					toQString(e.get_msg()) + "\"</p>" + 
					tr("Would you like to run update-apt-xapian-index now? Otherwise "
						"packagesearch will be terminated."), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No )
				exit(2);
			updateXapianDatabase();
		}
	}
	while (!success);

    makeHandleVisible(_pHSplitter->handle(1));
    makeHandleVisible(_pUpperVSplitter->handle(1));
    makeHandleVisible(_pLowerVSplitter->handle(1));

	// create the hard coded plugins
	_pPackageNamePlugin = new NPlugin::PackageNamePlugin();
	this->addPlugin(_pPackageNamePlugin);
	packageView()->addPlugin(_pPackageNamePlugin);
	
	_pScoreDisplayPlugin = new NPlugin::ScoreDisplayPlugin();
	_pPluginManager->addPluginUser(_pScoreDisplayPlugin);
	this->addPlugin(_pScoreDisplayPlugin);
	packageView()->addPlugin(_pScoreDisplayPlugin);
	
	_pPluginManager->loadPlugins();
	
	packageView()->initialize();

	_pInformationContainer->setCurrentIndex(0);
	const set<string>& packages_ = packages();
	for (set<string>::const_iterator it = packages_.begin(); it != packages_.end(); ++it)
	{
		_pPackageSelection->addItem(toQString(*it));
	}
	_pPackageSelection->setMinimumWidth(100);
	setPackageActionsEnabled(false);
}


/////////////////////////////////////////////////////
// IXmlStorable Interface
/////////////////////////////////////////////////////

void PackageSearchImpl::saveSettings()
{
	NXml::XmlData xmlData("packagesearch");
	QDomElement root = xmlData.root();
	xmlData.addAttribute(root, QString("3"), "settingsVersion");
	// changes:
	// settings version 2: added "system" node
	// settings version 3: added "network" node
	
	xmlData.addAttribute(root, _pMainToolBar->toggleViewAction()->isChecked(), "showMainToolbar");
	
	QDomElement window = xmlData.addElement(xmlData.root(), "geometry");
	xmlData.addAttribute(window, width(), "width");
	xmlData.addAttribute(window, height(), "height");
	QList<int> sizes = _pHSplitter->sizes();
	xmlData.addAttribute(window, sizes[0], "hSplitterSize1");
	xmlData.addAttribute(window, sizes[1], "hSplitterSize2");

	sizes = _pUpperVSplitter->sizes();
	xmlData.addAttribute(window, sizes[0], "upperVSplitterSize1");
	xmlData.addAttribute(window, sizes[1], "upperVSplitterSize2");

	sizes = _pLowerVSplitter->sizes();
	xmlData.addAttribute(window, sizes[0], "lowerVSplitterSize1");
	xmlData.addAttribute(window, sizes[1], "lowerVSplitterSize2");

 	packageView()->saveSettings(xmlData, root);
	
	QDomElement system = xmlData.addElement(xmlData.root(), "system");
	xmlData.addAttribute(system, NApplication::RunCommand::gainRootCommand(), "gainRootCommand");

	_network.saveSettings(xmlData, root);

	_pPluginManager->saveSettings(xmlData, root);
	if ( !xmlData.writeFile(_settingsFilename) )
		reportError(tr("Unable to write settings"), tr("Unable to write settings to") +
			_settingsFilename + "\nPersonal settings were not saved.");
}

void PackageSearchImpl::loadSettings()
{
	NXml::XmlData xmlData;
	if (!xmlData.loadFile(_settingsFilename))
	{
		// sort by the first column by default
// 		_pPackageView->setSorting(1, false);
		return;
	}
	QDomElement root = xmlData.root();
	
	QString settingsVersion;
	NXml::getAttribute(root, settingsVersion, "settingsVersion", "");
	QDomElement element = NXml::getFirstElement(xmlData.root().firstChild());
	
	bool showMainToolbar;
	NXml::getAttribute(root, showMainToolbar, "showMainToolbar", true);
	// TODO this does not work, even though showMainToobar is read correctly
	_pMainToolBar->toggleViewAction()->setChecked(showMainToolbar);

	if (element.tagName() == "geometry")
	{
		uint width;
		uint height;
		NXml::getAttribute(element, width, "width", 600);
		NXml::getAttribute(element, height, "height", 600);
		
		QList<int> sizes;
		int size;
		NXml::getAttribute(element, size, "hSplitterSize1", height/2);
		sizes.push_back(size);
		NXml::getAttribute(element, size, "hSplitterSize2", height/2);
		sizes.push_back(size);
		_pHSplitter->setSizes(sizes);
		
		sizes.clear();
		NXml::getAttribute(element, size, "upperVSplitterSize1", width/2);
		sizes.push_back(size);
		NXml::getAttribute(element, size, "upperVSplitterSize2", width/2);
		sizes.push_back(size);
		_pUpperVSplitter->setSizes(sizes);
		
		sizes.clear();
		NXml::getAttribute(element, size, "lowerVSplitterSize1", width/2);
		sizes.push_back(size);
		NXml::getAttribute(element, size, "lowerVSplitterSize2", width/2);
		sizes.push_back(size);
		_pLowerVSplitter->setSizes(sizes);
		
		element = NXml::getNextElement(element);
		
		resize(width, height);
	}
	// <customListView> node since version 0.3
	if (settingsVersion >= QString("0.3"))
	{
		element = packageView()->loadSettings(element);
	}
	// dismiss old setting version
	else
	{
		if (element.tagName() == "shownShortInformation")
		{
			//_shownShortInformation = NXml::getTextList(element);
			element = NXml::getNextElement(element);
		}
		if (element.tagName() == "hiddenShortInformation")
		{
			//_hiddenShortInformation = NXml::getTextList(element);
			element = NXml::getNextElement(element);	
		}
	}	
	// if preferences contain a "system" node
	if (element.tagName() == "system")
	{
		int gainRootCommand;
		NXml::getAttribute(element, gainRootCommand, "gainRootCommand", 0);
		NApplication::RunCommand::setGainRootCommand((NApplication::GainRoot) gainRootCommand);
		element = NXml::getNextElement(element);
	}
	element = _network.loadSettings(element);
	_pPluginManager->loadSettings(element);
}

/////////////////////////////////////////////////////
// IProvider Interface
/////////////////////////////////////////////////////

void PackageSearchImpl::setEnabled(bool enabled)
{
	QMainWindow::setEnabled(enabled);
}

QPushButton* PackageSearchImpl::createClearButton(QWidget* pParent, const char* name) const
{
	QPushButton* pButton = new QPushButton(pParent);
	pButton->setObjectName(name);
	pButton->setFixedSize(20, 20);
	QPixmap clearIcon(_iconDir+"clear.png");
	pButton->setIcon(clearIcon);
	return pButton;
}

void PackageSearchImpl::reportError(const QString& title, const QString& message)
{
    QMessageBox::critical(this, title, message, QMessageBox::StandardButton::Ok);
}

void PackageSearchImpl::reportWarning(const QString& title, const QString& message)
{
    QMessageBox::warning(this, title, message, QMessageBox::StandardButton::Ok);
}

void PackageSearchImpl::reportBusy(NPlugin::Plugin*, const QString& message)
{
	// order is important here, since setOverrideCursor() triggers an update of the UI,
	// and thus immediately updates the statusbar
	statusBar()->showMessage(message);
	qApp->setOverrideCursor(QCursor(Qt::WaitCursor));
}

void PackageSearchImpl::reportReady(NPlugin::Plugin* )
{
	// order is important here, since setOverrideCursor() triggers an update of the UI,
	// and thus immediately updates the statusbar
	statusBar()->clearMessage();
	qApp->restoreOverrideCursor();
}


NUtil::IProgressObserver* PackageSearchImpl::progressObserver() const
{
	return _pPluginManager->progressObserver();
}

const set<string>& PackageSearchImpl::packages() const
{
	static bool initialized = false;
	if (!initialized)
	{	
		typedef ept::apt::Apt Apt;
		initialized = true;
		for (Apt::Iterator it = _pApt->begin(); it != _pApt->end(); ++it)
		{
			_packages.insert(*it);
		}
	}
	return _packages;
}

void PackageSearchImpl::reloadAptFrontCache()
{
	delete _pApt;
	_pApt = new ept::apt::Apt;
}

QNetworkAccessManager* PackageSearchImpl::network() 
{
    return _pNetworkAccessManager;
}

/////////////////////////////////////////////////////
// IPluginUser Interface
/////////////////////////////////////////////////////

void PackageSearchImpl::addPlugin(NPlugin::Plugin* pPlugin)
{
	using namespace NPlugin;

	SearchPlugin* pSearchPlugin = dynamic_cast<SearchPlugin*>(pPlugin);
	if (pSearchPlugin != 0)
	{
		_searchPlugins.insert(pSearchPlugin);
		updateSearchPluginGui();
		connect(pSearchPlugin, SIGNAL(searchChanged(NPlugin::SearchPlugin*)), 
			SLOT(onSearchChanged(NPlugin::SearchPlugin*)));
		/// @todo connect selectionChanged signal
	}
	InformationPlugin* pInformationPlugin = dynamic_cast<InformationPlugin*>(pPlugin);
	if (pInformationPlugin != 0)
	{
		_informationPlugins.insert(pInformationPlugin);
		updateInformationPluginGui();
	}
	ShortInformationPlugin* pShortInformationPlugin = dynamic_cast<ShortInformationPlugin*>(pPlugin);
	if (pShortInformationPlugin != 0)
	{
		addShortInformationPlugin(dynamic_cast<ShortInformationPlugin*>(pPlugin));
	}
	if (dynamic_cast<NPlugin::ActionPlugin*>(pPlugin))
	{
		addActionPlugin(dynamic_cast<NPlugin::ActionPlugin*>(pPlugin));
	}
}


void PackageSearchImpl::addPlugin(NPlugin::PluginContainer* pPlugin)
{
	assert(pPlugin!= 0);
	// add all plugins offered by this container
	vector<string> offeredPlugins = pPlugin->offeredPlugins();
	for ( vector<string>::iterator it = offeredPlugins.begin(); it != offeredPlugins.end(); ++it)
		addPlugin(pPlugin->requestPlugin(*it));
}

void PackageSearchImpl::removePlugin(NPlugin::Plugin* pPlugin)
{
	qDebug() << "Removing plugin " << pPlugin->name();
	using namespace NPlugin;
	SearchPlugin* pSPlugin = dynamic_cast<SearchPlugin*>(pPlugin);
	if (pSPlugin)
	{
		_searchPlugins.erase(pSPlugin);
		updateSearchPluginGui();
	}
	InformationPlugin* pIPlugin = dynamic_cast<InformationPlugin*>(pPlugin);
	if (pIPlugin)
	{
		_informationPlugins.erase(pIPlugin);
		updateInformationPluginGui();
	}
	ShortInformationPlugin* pSIPlugin = dynamic_cast<ShortInformationPlugin*>(pPlugin);
	if (pSIPlugin)
	{
		_shortInformationPlugins.erase(pSIPlugin);
// 		_pPackageListViewControl->removeColumn(pSIPlugin->shortInformationCaption());
	}
	ActionPlugin* pAPlugin = dynamic_cast<ActionPlugin*>(pPlugin);
	if (pAPlugin)
	{
		vector<NPlugin::Action*> actions = pAPlugin->actions();
		for (vector<NPlugin::Action*>::const_iterator it = actions.begin(); it != actions.end(); ++it)
		{
			const NPlugin::Action* pAction = *it;
			if (pAction->packageAction())
				_packageActions.erase( std::find(_packageActions.begin(), _packageActions.end(), pAction->action()) );
		}
	}
}

/////////////////////////////////////////////////////
// IPluginUser Helper Methods ///////////////////////
/////////////////////////////////////////////////////

void PackageSearchImpl::addShortInformationPlugin(NPlugin::ShortInformationPlugin* pPlugin)
{
	_shortInformationPlugins.insert(pPlugin);
}

void PackageSearchImpl::addActionPlugin(NPlugin::ActionPlugin* pPlugin)
{
	vector<NPlugin::Action*> actions = pPlugin->actions();
	for (vector<NPlugin::Action*>::const_iterator it = actions.begin(); it != actions.end(); ++it)
	{
		const NPlugin::Action* pAction = *it;
		if (!pAction->menu().isEmpty())
		{
			map<QString, QMenu*>::const_iterator jt = _nameToMenu.find(pAction->menu());
			QMenu* pMenu;
			if (jt != _nameToMenu.end())
			{
				pMenu = jt->second;
			}
			else
			{
				qWarning() << "Requested to add to an unknown menu \"" <<  pAction->menu() 
					<< "\" in plugin " << pPlugin->name() << ".\n"
					<< "Added to system menu instead.";
				pMenu = _pSystemMenu;
			}
			pMenu->addAction(pAction->action());
		}
		if (!pAction->toolBar().isEmpty())
		{
			map<QString, QToolBar*>::const_iterator jt = _nameToToolBar.find(pAction->toolBar());
			QToolBar* pToolBar;
			if (jt != _nameToToolBar.end())
			{
				pToolBar = jt->second;
			}
			else
			{
				qWarning() << "Requested to add to an unknown toolbar \""
					<< pAction->toolBar() << "\" in plugin " << pPlugin->name() << "\n"
					<< "Added to main toolbar instead.";
				pToolBar = _pMainToolBar;
			}
			pToolBar->addAction(pAction->action());
		}
		if (pAction->packageAction())
		{
			_packageActions.push_back(pAction->action());
		}
	}
}


/////////////////////////////////////////////////////
// Helper Methods
/////////////////////////////////////////////////////

NPackageSearch::PackageDisplayWidget* PackageSearchImpl::packageView()
{
	return static_cast<NPackageSearch::PackageDisplayWidget*>(_pPackageView);
}

void PackageSearchImpl::updateSearchPluginGui()
{
	// remove all pages
	_pInputWidgetsContainer->clear();
	
	// the next lines create a new layout for the _pShortSearchFrame so
	// to have an empty frame so adding the information widgets will result in the
	// correct order
	delete _pShortSearchFrame->layout();
	QVBoxLayout* pLayout = new QVBoxLayout(_pShortSearchFrame);
    pLayout->setContentsMargins(5, 5, 5, 5);
	pLayout->setSpacing(10);
	NExtStd::for_each(_searchPlugins.begin(), _searchPlugins.end(), 
		&PackageSearchImpl::addSearchPluginToGui, this);
	if (_pInputWidgetsContainer->count()>0)
		_pInputWidgetsContainer->setCurrentIndex(0);
}

void PackageSearchImpl::addSearchPluginToGui(NPlugin::SearchPlugin* pPlugin)
{
	assert(pPlugin);
	// add the input widget
	if (pPlugin->inputWidget() != 0)
	{
		_pInputWidgetsContainer->addTab(pPlugin->inputWidget(), pPlugin->inputWidgetTitle());
	}
	// add the short input widget
	if (pPlugin->shortInputAndFeedbackWidget()!=0)
	{
		QWidget* pWidget = pPlugin->shortInputAndFeedbackWidget();
		// store if the widget was shown to as reparenting destroys this state
 		bool hidden = pWidget->isHidden();
		// reparenting was the only way which seemed to work to get the widgets where I wanted them :-(
		pWidget->setParent(_pShortSearchFrame);
		_pShortSearchFrame->layout()->addWidget(pWidget);
		pWidget->setHidden(hidden);
	}
}

void PackageSearchImpl::updateInformationPluginGui()
{
	_pInformationContainer->clear();
	bool detailPageAdded = false;
	// add all information plugins in order of their priorities, inserting the details
	// information page between
	for (InformationPluginContainer::const_iterator it = _informationPlugins.begin();
		it != _informationPlugins.end(); ++it)
	{
		NPlugin::InformationPlugin* pPlugin = *it;
		if ( !detailPageAdded && 
			(pPlugin->informationPriority() >= _DETAILS_INFORMATION_PRIORITY) )
		{
			_pInformationContainer->addTab(_pDetailsView, tr("Details"));
			detailPageAdded = true;
		}
		if (pPlugin->informationWidget() != 0)	// if the plugin offers an information widget
		{
			_pInformationContainer->addTab(
				pPlugin->informationWidget(), 
				pPlugin->informationWidgetTitle()
			);
		}
	}
	if (!detailPageAdded)
		_pInformationContainer->addTab(_pDetailsView, tr("Details"));
	_pInformationContainer->setCurrentIndex(0);
	updatePackageInformation(_currentPackage);
}

NPlugin::ShortInformationPlugin* PackageSearchImpl::getShortInformationPluginWithCaption(
	const QString caption) const
{
	NPlugin::ShortInformationCaptionEquals predicat(caption);
	ShortInformationPluginContainer::const_iterator it = 
		find_if(_shortInformationPlugins.begin(), _shortInformationPlugins.end(), predicat);
	if (it == _shortInformationPlugins.end())
		return 0;
	else 
		return *it;
}

void PackageSearchImpl::onClearSearch()
{
	for_each(_searchPlugins.begin(), _searchPlugins.end(),
			mem_fun(&NPlugin::SearchPlugin::clearSearch));
}


bool PackageSearchImpl::FilterPackages::operator()(const string& package)
{
	return _pPlugin->filterPackage(package);
}


void PackageSearchImpl::onSearchChanged(NPlugin::SearchPlugin*)
{
	reportBusy(0, tr("Evaluating searches"));
	using namespace NPlugin;
	std::set<string> result;
	bool first = true;	// keeps track if this is the first search which produces results
	
	// evaluate plugins which do not uses filter technique
	for (SearchPluginContainer::iterator it = _searchPlugins.begin(); it != _searchPlugins.end(); ++it)
	{
		SearchPlugin* pPlugin = *it;
		if (!pPlugin->isInactive())
		{
			if (!pPlugin->usesFilterTechnique())
			{
				if (first)
				{
					result = pPlugin->searchResult();
					first = false;
				}
                else {
                    std::set<string> intersection;
                    auto& searchResult = pPlugin->searchResult();
                    std::set_intersection(
                                result.begin(), result.end(),
                                searchResult.begin(), searchResult.end(),
                                std::inserter(intersection, intersection.begin())
                    );
                    result.swap(intersection);
                }
            }
		}
	}
	// filter the resulting packages through the filter plugins
	for (SearchPluginContainer::iterator it = _searchPlugins.begin(); it != _searchPlugins.end(); ++it)
	{
		SearchPlugin* pPlugin = *it;
		if (!pPlugin->isInactive())
		{
			// if we had no search which returned a result set
			if (first) 
			{
				// the result set contains all packages available
				for (set<string>::const_iterator it = _packages.begin(); it != _packages.end(); ++it)
					result.insert(*it);
				first = false;
			}
			if (pPlugin->usesFilterTechnique())
			{
				std::set<string> newResult;
				FilterPackages fp(pPlugin);
				// copy the packages which are matched by the package filter to newResult
				NExtStd::copy_if( result.begin(), result.end(), inserter(newResult, newResult.begin()), fp );
				swap(newResult, result);
			}
		}
	}

	// if at least one search was active
	if (!first)
	{
		setPackagesFound(result.size());
		if (packageView()->scoreColumnVisible())
			_pScoreDisplayPlugin->updateScores(result);
	}
	else
	{
		setPackagesFound(-1);
		result.clear();
	}
	packageView()->setPackages(result);
	reportReady(0);
}

void PackageSearchImpl::updatePackageInformation(const QString & package)
{
	qDebug() << "updatePackageInformation(" << package << ")";
	_pDetailsView->clear();
	
	QString detailedText;	// information text for "Details" tab (in HTML)
	for ( InformationPluginContainer::iterator it = _informationPlugins.begin();
		it != _informationPlugins.end(); ++it )
	{
		NPlugin::InformationPlugin* pPlugin = (*it);
		if (package.isEmpty())
		{
			pPlugin->clearInformationWidget();
		}
		else
		{
			if ( pPlugin->offersInformationText())
			{
				try 
				{
					detailedText += pPlugin->informationText(toString(package));
				}
				// simply ignore it if the package was not available for this plugin
				catch (NPlugin::PackageNotFoundException& e) {}
				catch (std::exception& e) {
					qWarning() << "Plugin " << (*it)->name() << " failed to provide detailed information\n"
							<< "e.what(): " << e.what();
				}
				catch (...) {
					qWarning() << "Plugin " << (*it)->name() << " failed to provide detailed information (unknown exception thrown)";
				}
			}
			if (_pInformationContainer->currentWidget() == pPlugin->informationWidget())
			{
				// update the tab only if it is currenly active
				try  {
					pPlugin->updateInformationWidget(toString(package));
				}
				catch (std::exception& e) {
					qWarning() << "Plugin " << (*it)->name() << " failed to update information\n"
							<< "e.what(): " << e.what();
				}
				catch (...) {
					qWarning() << "Plugin " << (*it)->name() << " failed to update information (unknown exception thrown)";
				}
			}
		}
	}
	_pDetailsView->setHtml("<p>" + detailedText + "<p>");
}

void PackageSearchImpl::onInformationPageChanged()
{
	if (_currentPackage.isEmpty())	// if no package was selected
		return;
	
	for (
		InformationPluginContainer::iterator it = _informationPlugins.begin();
		it != _informationPlugins.end();
		++it
	)
	{
		if (_pInformationContainer->currentWidget() == (*it)->informationWidget())
		// show the page only if it is currenly active
		{
			try  {
				(*it)->updateInformationWidget(toString(_currentPackage));
			}
			catch (std::exception& e) {
				qWarning() << "Plugin " << (*it)->name() << " failed to update information\n"
						<< "e.what(): " << e.what();
			}
			catch (...) {
				qWarning() << "Plugin " << (*it)->name() << " failed to update information (unknown exception thrown)";
			}
		}
	}
}

void PackageSearchImpl::onControlPlugins()
{
	_pPluginManager->showControlDialog(this);
}

void PackageSearchImpl::onPreferences()
{
	NPackageSearch::SettingsDlg dlg(_network, this);
	// TODO grey out SUDO button if sudo is not available
	switch (NApplication::RunCommand::gainRootCommand())
	{
		case NApplication::SU:
			dlg._pSuButton->setChecked(true);
			break;
		case NApplication::SUDO:
			dlg._pSudoButton->setChecked(true);
			break;
	}
	// if sudo is not executable
	if (! (QFile::permissions(NApplication::GainRootPaths::sudo) & QFile::ExeUser) )
		dlg._pSudoButton->setEnabled(false);
	vector<NPlugin::PluginContainer*> plugins = _pPluginManager->getLoadedPlugins();
	for_each(plugins.begin(), plugins.end(), 
			bind1st( mem_fun(&NPackageSearch::SettingsDlg::addPlugin), &dlg) );
	if ( dlg.exec() == QDialog::Accepted )
	{
		if (dlg._pSuButton->isChecked())
			NApplication::RunCommand::setGainRootCommand(NApplication::SU);
		else if (dlg._pSudoButton->isChecked())
			NApplication::RunCommand::setGainRootCommand(NApplication::SUDO);
		_network = dlg.networkSettings();		
		for_each( plugins.begin(), plugins.end(),
				mem_fun(&NPlugin::PluginContainer::applySettings) );
	}
}


void PackageSearchImpl::closeEvent(QCloseEvent* pE)
{
	saveSettings();
	pE->accept();
}


void PackageSearchImpl::onLinkClicked( const QUrl& link)
{
	if ( link.scheme() == QString("package") ) 
	{
		selectNewPackage(link.path());
	} 
	else
	{
		QDesktopServices::openUrl(link);
	}
}

void PackageSearchImpl::onForward()
{
	setCurrentPackage(_viewHistory.forward());
	updateBrowseToolbar();
}

void PackageSearchImpl::onBack()
{
	setCurrentPackage(_viewHistory.back());
	updateBrowseToolbar();
}

void PackageSearchImpl::selectNewPackage(const QString& package)
{
	qDebug() << "selectNewPackage(" << package << ") called";
	if (package.isEmpty()) 
	{
		setPackageActionsEnabled(false);
		_pDetailsView->clear();
	}
	else
	{
		// reselect the package even if already selected to reflect changes due to a changed search
		setCurrentPackage(package);
		if ( _viewHistory.empty() || _viewHistory.current() != package)
		{
			_viewHistory.append(package);
			updateBrowseToolbar();
		}
	}
}

void PackageSearchImpl::onPackageSelectionChanged(QString package)
{
	if (!package.isEmpty()) 
	{
		selectNewPackage(package);
}

}

void PackageSearchImpl::updateBrowseToolbar()
{
	_pForwardButton->setEnabled(_viewHistory.forwardPossible());
	_pBackButton->setEnabled(_viewHistory.backPossible());
}

void PackageSearchImpl::onHelpContentsAction()
{
	static QDialog* pDlg = new QDialog(this);
	static QVBoxLayout* pMainLayout = new QVBoxLayout(pDlg);
	static QTextBrowser *pHelpWindow = new QTextBrowser(pDlg); 
	pHelpWindow->setWindowTitle(tr("Package Search Help Page"));
	pHelpWindow->setSource(_docDir+"content.html");
	pMainLayout->addWidget(pHelpWindow);
	pDlg->resize(520,560);
	pDlg->show();
}

void PackageSearchImpl::onHelpAboutAction()
{
	NPackageSearch::PackageSearchAboutDlg dlg;
	dlg.exec();
}

void PackageSearchImpl::setPackagesFound(int number)
{
	QString output("<font size=\"-1\">");
	if (number == -1)
		output += "No Search Active";
	else
		output += QString().setNum(number)+" Packages Found";
	output += ("</font>");
	_pPackagesFoundDisplay->setText(output);
}

void PackageSearchImpl::setCurrentPackage( const QString & package )
{
	setPackageActionsEnabled(true);
	// if the package to be shown is not the one selected in the package view, deselect the 
	// one in  the package view
	if (packageView()->selectedPackage() != package)
		packageView()->clearSelection();
	if (package != _currentPackage)
	{
		_currentPackage = package;
		// select the package in the package selection combo box
		// this triggers a selectNewPackage() call  but it returns immidiately because 
		// package == _currentPackage
		int i = _pPackageSelection->findText(package);
		// if we have a package which is in the package list, select it from there
		if (i != -1)
			_pPackageSelection->setCurrentIndex(i);
		// if the package is not in the package list, change the text to reflect
		// the current package
		else
			_pPackageSelection->setEditText(package);
	}
	// update the package information even if the current package did not change
	// to reflect changes due to a changed search
	updatePackageInformation(package);
}


void PackageSearchImpl::setPackageActionsEnabled(bool enabled)
{
	for (list<QAction*>::iterator it = _packageActions.begin(); it != _packageActions.end(); ++it)
		(*it)->setEnabled(enabled);
}

void PackageSearchImpl::checkAptDbForUpdates()
{
	_pApt->checkCacheUpdates();
}


void PackageSearchImpl::setTranslationDir(const QDir& dir) 
{
	_pPluginManager->setTranslationDir(dir);
}


void PackageSearchImpl::updateXapianDatabase() 
{
	setEnabled(false);
	NApplication::RunCommand* pCommand = NApplication::ApplicationFactory::getInstance()->getRunCommand("");
	pCommand->addArgument("/usr/sbin/update-apt-xapian-index");
	pCommand->addArgument("-f");
	pCommand->startAsRoot();
	while (!pCommand->finished()) 
	{
		pCommand->waitForFinished(500);
 		qApp->processEvents();
	}
	// reload new xapian database
	delete _pXapianDatabase;
	_pXapianDatabase = new Xapian::Database(ept::axi::path_db());
	setEnabled(true);
}

void PackageSearchImpl::makeHandleVisible(QSplitterHandle* pSplitterHandle) {
    QVBoxLayout *layout = new QVBoxLayout(pSplitterHandle);
    layout->setSpacing(0);
    layout->setContentsMargins(0, 0, 0, 0);

    QFrame *line = new QFrame(pSplitterHandle);
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Sunken);
    layout->addWidget(line);
}

