#include <fstream>

#include <assert.h>

#include <qcheckbox.h>
#include <QDir>
#include <qfileinfo.h>
#include <qlineedit.h>
#include <QMainWindow>
#include <qpushbutton.h>
#include <qstatusbar.h>
#include <qtimer.h>

#include <iprovider.h>

// NUtil
#include <helpers.h>

#include <extalgorithm.h>

#include "filenameplugin.h"

#include "filenameview.h"
#include "filenamesearchinput.h"
#include "filenamefeedbackwidget.h"

// NApplication
#include <runcommandforoutput.h>


using namespace std;

namespace NPlugin
{

const QString FilenamePlugin::PLUGIN_NAME = "FilenamePlugin";


FilenamePlugin::FilenamePlugin()
{
	_pProcess = 0;
	_pFilenameFeedbackWidget = 0;
	_pFileView = 0;
	_pProvider = 0;
	_pInputWidget = 0;
	_pDelayTimer = new QTimer(this);
	_pDelayTimer->setSingleShot(true);
	_delayTime=2000;
	connect(_pDelayTimer, SIGNAL(timeout()), SLOT(evaluateSearch()));
}

FilenamePlugin::~FilenamePlugin()
{
	delete _pFileView;
	delete _pInputWidget;
	delete _pFilenameFeedbackWidget;
	delete _pDelayTimer;
	delete _pProcess;
}

/////////////////////////////////////////////////////
// Plugin Interface
/////////////////////////////////////////////////////

QString FilenamePlugin::title() const
{ 
	return tr("Filename Plugin"); 
}

QString FilenamePlugin::briefDescription() const
{
	return tr("Plugin to search by files and diplay files in packages.");
}

QString FilenamePlugin::description() const
{
	return tr("This plugin allows to search the packages for files, and to display the files included "
		"in the packages\n"
		"For information about not installed packages, it needs the <tt>apt-file</tt> tool.");
}

void FilenamePlugin::init(IProvider* pProvider)
{
	_pProvider = pProvider;
	QMainWindow* pWindow = pProvider->mainWindow();
	_pFileView = new FilenameView(pWindow, "FileView", pProvider);
	_pInputWidget = new FilenameSearchInput(pWindow);
	_pFilenameFeedbackWidget = new FilenameFeedbackWidget(pWindow, "filenameFeedbackWidget");
	
	// replace the clear button
	QPushButton* pClearButton = pProvider->createClearButton(_pFilenameFeedbackWidget, "ClearButton");
	_pFilenameFeedbackWidget->setClearButton(pClearButton, 0);
	connect(pClearButton, SIGNAL(clicked()), SLOT(onClearSearch()));
	
	connect( _pInputWidget->_pFilenamePatternInput, SIGNAL(textChanged(const QString&)), 
		SLOT(onInputTextChanged(const QString&)) );
	connect( _pInputWidget->_pFilenamePatternInput, SIGNAL(returnPressed()),
		SLOT(evaluateSearch()) );
	connect( _pInputWidget->_pSearchInstalledOnlyCheck, SIGNAL(toggled(bool)), 
		SLOT(evaluateSearch()) );
	connect(_pFileView, SIGNAL(showRequested()), SLOT(onShowRequested()));
}

/////////////////////////////////////////////////////
// Information Plugin Interface
/////////////////////////////////////////////////////


QWidget* FilenamePlugin::informationWidget() const	
{ 
	return _pFileView; 
}

QString FilenamePlugin::informationWidgetTitle() const 
{
	return tr("Files"); 
}

void FilenamePlugin::updateInformationWidget(const string& package)
{
	if ( _currentPackage == toQString(package) ) return;
	_currentPackage = toQString(package);
	_pFileView->clear();
	// prevent showing of information if we have only slow access to the file list (i.e. 
	// apt-file
	if (isInstalled(package))	
	{
	 	onShowRequested();
	 	_pFileView->setShowButtonEnabled(false);
	}
	else
	{
		if (aptFileAvailable())
		{
			_pFileView->setErrorMessage
			(
				tr("<font color=#606060>For packages <b>not installed</b>, the file list is not shown "
				"by default. This is because retreiving the file list will take some time.<br>"
				"Please click the <b>&quot;Show&quot;</b> button to show the filelist for the "
				"selected package.</font>")
			);
		 	_pFileView->setShowButtonEnabled(true);
		}
		else
		{
			_pFileView->setErrorMessage( aptFileMissingErrorMsg(toQString(package)) );
		 	_pFileView->setShowButtonEnabled(false);
		}
	}
}

void FilenamePlugin::clearInformationWidget()
{
	_pFileView->clear();
}

QString FilenamePlugin::informationText(const string&)
{
	return _emptyString;
}


/////////////////////////////////////////////////////
// Search Plugin Interface
/////////////////////////////////////////////////////

QWidget* FilenamePlugin::shortInputAndFeedbackWidget() const
{
	return _pFilenameFeedbackWidget;
}

QWidget* FilenamePlugin::inputWidget() const
{
	return _pInputWidget;
}

QString FilenamePlugin::inputWidgetTitle() const 
{ 
	return tr("Filenames"); 
};


void FilenamePlugin::clearSearch()
{
	_pInputWidget->_pFilenamePatternInput->clear();
	_pDelayTimer->stop();
	evaluateSearch();
}


bool FilenamePlugin::isInactive() const
{
	return _pInputWidget->_pFilenamePatternInput->text().isEmpty();
}

/////////////////////////////////////////////////////
// Information Helper Methods
/////////////////////////////////////////////////////

QFileInfo FilenamePlugin::getFileListFileName(const string& packageName)
{
	QFileInfo packageList("/var/lib/dpkg/info/" + toQString(packageName) + ".list");
	if (packageList.isReadable()) 
		return packageList;
	QDir pkgInfoDir("/var/lib/dpkg/info/");
	QStringList filter;
	filter.append(toQString(packageName) + "*.list");
	pkgInfoDir.setNameFilters(filter);
	QFileInfoList entries = pkgInfoDir.entryInfoList();
	if (entries.isEmpty())
		return QFileInfo();
	return entries.first();
}

bool FilenamePlugin::isInstalled(const string& packageName)
{
	return getFileListFileName(packageName).isReadable();	// if the package is installed
}


QStringList FilenamePlugin::filesForPackage(const string& packageName) throw (NoInformationException)
{
	QStringList files;	// the files in the given package
	if (isInstalled(packageName))	// if the package is installed
	{
		QFileInfo fileInfo = getFileListFileName(packageName);
		string filename = toString(fileInfo.absoluteFilePath());
		ifstream file(filename.c_str());
		while (file)
		{
			string currentLine;
			file >> currentLine;
			if ( !currentLine.empty() )
				files.push_back(toQString(currentLine));
		}
		file.close();
	}
	else
	{
		if (aptFileAvailable())
		{
			if (!_processMutex.tryLock())
			{
				qDebug("The mutex was locked\n");
				return QStringList();
			}
			_pProvider->reportBusy(this, tr("Querying database for file list"));
			_pProvider->setEnabled(false);
			_pProcess = new NApplication::RunCommandForOutput("apt-file");
 			connect(_pProcess, SIGNAL(processExited(RunCommandForOutput*)), SLOT(onFilelistProcessExited()));
			QStringList arguments;
			_pProcess->addArgument("list");
			_pProcess->addArgument("--fixed-string");
			_pProcess->addArgument(toQString(packageName));
			_pProcess->start();
		}
		else
		{
			throw NoInformationException( aptFileMissingErrorMsg(toQString(packageName)) );
		}
	}
	return files;	// return the list of files, or an empty list if a process was launched
}

bool FilenamePlugin::fixEntry(QString& entry, const QString& packageName)
{
	if (!entry.startsWith(packageName+":"))
		return false;
	entry.remove(packageName+": ");
	return true;
}

void FilenamePlugin::onFilelistProcessExited()
{
	qDebug("onFilelistProcessExited()");
	QStringList output = _pProcess->getOutput();
	for (QStringList::const_iterator it = output.begin(); it != output.end(); ++it)
	{
		QString line = *it;
		line.chop(1); // remove trailing \n
		if (fixEntry(line, _currentPackage))
			_pFileView->addEntry(line);
	}
	
	if (!_pFileView->hasEntry())
	{
		qDebug("No file information available");
		_pFileView->setErrorMessage(tr("There is no file information for the current package available."));
	}
	_pProvider->reportReady(this);
	_pProcess->deleteLater();
	_pProcess = 0;
	_processMutex.unlock();
	_pProvider->setEnabled(true);
}

void FilenamePlugin::onShowRequested()
{
	_pFileView->clear();
	try
	{
		QStringList files = filesForPackage(toString(_currentPackage));
		NExtStd::for_each(files.begin(), files.end(), &FilenameView::addEntry, _pFileView);
	}
	catch (NoInformationException e)
	{
		_pFileView->setErrorMessage(e._errorMessage);
	}
}

/////////////////////////////////////////////////////
// Search Helper Methods
/////////////////////////////////////////////////////


void FilenamePlugin::onInputTextChanged(const QString&)
{
	_pProvider->mainWindow()->statusBar()->showMessage( tr("delayed evaluation - waiting for further input"), _delayTime );
	_pDelayTimer->start(_delayTime);
}


void FilenamePlugin::evaluateSearch()
{
	// stop the delay timer in case that this evaluateSearch() was triggered by
	// another event
	_pDelayTimer->stop();
	// reportReady will be called on applications exit
	_searchResult.clear();
	QString searchFilename = _pInputWidget->_pFilenamePatternInput->text();
	if ( !searchFilename.isEmpty() )	// if the search is not empty
	{
		if (!aptFileAvailable() && !_pInputWidget->_pSearchInstalledOnlyCheck->isChecked())
		{
			_pProvider->reportError(
				tr("Apt file search not availabe"),
				tr("You need the <tt>apt-file</tt> utility to search files "
				"in packages not installed.<br>"
				"To get apt-file fetch it via <tt>apt-get install apt-file</tt> and run "
				"<tt>apt-file update</tt> afterwards.")
			);
			return;
		}
		if (!_processMutex.tryLock())
		{
			qDebug("The mutex was locked\n");
			return;
		}
		assert(_pProcess==0);
		_pProvider->reportBusy(this, tr("Performing search for filenames, this might take a while"));
		_pProvider->setEnabled(false);
		if (_pInputWidget->_pSearchInstalledOnlyCheck->isChecked())
		{
			_pProcess = new NApplication::RunCommandForOutput("dpkg");
			connect(_pProcess,  SIGNAL(processExited(RunCommandForOutput*)), SLOT(onSearchProcessExited()));
			_pProcess->addArgument("-S");
			_pProcess->addArgument("*"+searchFilename+"*");
			_pProcess->start();
		}
		else
		{
			_pProcess = new NApplication::RunCommandForOutput("apt-file");
			connect(_pProcess, SIGNAL(processExited(RunCommandForOutput*)), SLOT(onSearchProcessExited()));
			_pProcess->addArgument("search");
			_pProcess->addArgument("-l");	// list only package names
			_pProcess->addArgument(searchFilename);
			_pProcess->start();
		}
	}
	else
	{
		// do not show the feedback widget if the search is empty
		_pFilenameFeedbackWidget->setShown(false);	
		emit searchChanged(this);
	}
}


void FilenamePlugin::onSearchProcessExited()
{
	QStringList output = _pProcess->getOutput();
	for (QStringList::const_iterator it = output.begin(); it != output.end(); ++it)
	{
		QString line = *it;
		line.chop(1);	// remove trailing \n
		if (_pInputWidget->_pSearchInstalledOnlyCheck->isChecked())
		{	/// @todo intercept not found message here - not neccessary?
			/// @todo fix diversion bin entries
			line = line.left(line.indexOf(':'));	// the packages are listed before the colon
			QStringList packages = line.split(", ");
			for(QStringList::iterator it = packages.begin(); it != packages.end(); ++it)
				_searchResult.insert( toString(*it) );
		}
		// if it was an apt-file search
		else
		{
			_searchResult.insert( toString(line) );
		}
	}
	
	
	_pProvider->reportReady(this);
	emit searchChanged(this);
	
	_pFilenameFeedbackWidget->setShown(true);
	_pFilenameFeedbackWidget->_pFilenameDisplay->setText(_pInputWidget->_pFilenamePatternInput->text());
	
	_pProcess->deleteLater();
	_pProcess = 0;
	_processMutex.unlock();
	_pProvider->setEnabled(true);
}

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

bool FilenamePlugin::aptFileAvailable()
{
	QFileInfo aptFile("/usr/bin/apt-file");
	return aptFile.isExecutable();
}


QString FilenamePlugin::aptFileMissingErrorMsg(QString packageName)
{
	return tr("<font color=#606060><p>File list for <b>") + packageName + tr("</b> not available.</p>"
	"<p>For displaying the file list for not-installed packages, the \"apt-file\" "
	"package is required. Please install \"apt-file\" and "
	"retrieve the latest file database by running  <tt>apt-file update</tt>.</p></font>");
}


}	// namespace NPlugin
