#include <cassert>

#include <qcheckbox.h>
#include <qlineedit.h>
#include <QMainWindow>
#include <qpushbutton.h>
#include <qstatusbar.h>
#include <qtimer.h>
#include <qwidget.h>
#include <QStringList>
#include <QTime>
#include <QDebug>

#include <helpers.h>

// NPlugin
#include <iprovider.h>

// NApt
#include "iaptsearch.h"
#include "ipackagedb.h"
#include "complexscorecalculationstrategy.h"

#include "aptsearchplugin.h"

#include "aptsearchpluginshortinputwidget.h"



namespace NPlugin
{

const QString AptSearchPlugin::PLUGIN_NAME = "AptSearchPlugin";


AptSearchPlugin::AptSearchPlugin(NApt::IAptSearch* pAptSearch, NApt::IPackageDB* pPackageDb) :
	_title(tr("Apt-Search Plugin")),
	_briefDescription(tr("Performs a full text search")),
	_description(tr("This plugin can be used to search the packages for expressions.")),
	_pAptSearch(pAptSearch),
	_pPackageDb(pPackageDb)
{
	_pShortInputWidget = 0;
	_pStatusBar = 0;
	
	_pDelayTimer = new QTimer(this);
	_pDelayTimer->setObjectName("delayTimer");
	_delayTime=1000;
	connect(_pDelayTimer, SIGNAL(timeout()), SLOT(evaluateSearch()));

	_pScoreCalculationStrategy = new NApt::ComplexScoreCalculationStrategy(_pPackageDb);
}

AptSearchPlugin::~AptSearchPlugin()
{
	delete _pShortInputWidget;
	delete _pDelayTimer;
	delete _pScoreCalculationStrategy;
}


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

void AptSearchPlugin::init(IProvider* pProvider)
{
	_pProvider = pProvider;
	QMainWindow* pWindow = _pProvider->mainWindow();
	_pShortInputWidget = new AptSearchPluginShortInputWidget(pWindow, "AptSearchShortInputWIdget");
	_pShortInputWidget->setClearButton(
		pProvider->createClearButton(_pShortInputWidget, "AptClearButton"), 0);
	_pShortInputWidget->show();
	_pStatusBar = pWindow->statusBar();
	connect( _pShortInputWidget->_pClearButton, SIGNAL(clicked()), SLOT (onClearSearch()) );
	connect(
		_pShortInputWidget->_pAptSearchTextInput, 
		SIGNAL(textChanged(const QString&)),
		SLOT(onInputTextChanged(const QString&))
	);
	connect(
		_pShortInputWidget->_pAptSearchTextInput, 
		SIGNAL(returnPressed()),
		SLOT(evaluateSearch())
	);
	connect(
		_pShortInputWidget->_pSearchDescriptionsCheck,
		SIGNAL(toggled(bool)), 
		SLOT(evaluateSearch())
	);
}

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

QWidget* AptSearchPlugin::shortInputAndFeedbackWidget() const
{
	return _pShortInputWidget;
}

void AptSearchPlugin::clearSearch()
{
	_pShortInputWidget->_pAptSearchTextInput->clear();
	_pDelayTimer->stop();
	evaluateSearch();
}


bool AptSearchPlugin::isInactive() const
{
	return _includePatterns.isEmpty() && _excludePatterns.isEmpty();
}

/////////////////////////////////////////////////////
// ScorePlugin Interface
/////////////////////////////////////////////////////


map<string, float> AptSearchPlugin::getScore(const set<string>& packages) const
{
	assert(!_includePatterns.empty());
	_pScoreCalculationStrategy->clear();
	_pScoreCalculationStrategy->setIncludePatterns(_includePatterns);
	_pScoreCalculationStrategy->calculateScore(packages);
	return _pScoreCalculationStrategy->getScore();
}


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


void AptSearchPlugin::onInputTextChanged(const QString&)
{
	_pStatusBar->showMessage(tr("Delayed evaluation - waiting for further input"), _delayTime );
	_pDelayTimer->setSingleShot(true);
	_pDelayTimer->start(_delayTime);
}

void AptSearchPlugin::evaluateSearch()
{
	// stop the delay timer in case that this evaluateSearch() was triggered by
	// another event
	_pDelayTimer->stop();
 	_pProvider->reportBusy(this, tr("Performing full text search on package database"));
	_searchResult.clear();
	parseSearchExpression(_pShortInputWidget->_pAptSearchTextInput->text());
	if ( !isInactive() )	// if the search is not empty
	{
		QStringList patterns = this->searchPatterns();
		try {
			_pAptSearch->search(
				_searchResult, 
				_includePatterns, 
				_excludePatterns,  
				_pShortInputWidget->_pSearchDescriptionsCheck->isChecked()
			);
		} catch (...) {
			qWarning("Search threw an exception -> reloading apt-database");
			_pProvider->reloadAptFrontCache();
			try {	// retrying search
				_pAptSearch->search(
					_searchResult, 
					_includePatterns, 
					_excludePatterns,  
					_pShortInputWidget->_pSearchDescriptionsCheck->isChecked()
				);
			} catch (...) {
				qCritical("apt-search threw an exception -> expect corrupted results");
			}
		}
	}
	_pProvider->reportReady(this);
	emit searchChanged(this);
}

QStringList AptSearchPlugin::searchPatterns()
{
	return _includePatterns;
}

void AptSearchPlugin::parseSearchExpression(const QString& expression)
{
	_includePatterns.clear();
	_excludePatterns.clear();
	if (expression.isEmpty())
		return;
	QStringList patterns = expression.split('"', QString::KeepEmptyParts);
	
	// status flags
	bool minus = false;
	bool inGroup = (expression == QString("\""));
	for (QStringList::iterator it = patterns.begin(); it != patterns.end(); ++it )
	{
		if (!inGroup)
		{
			QStringList newPatterns = it->split(' ', QString::KeepEmptyParts);
			for (QStringList::iterator jt = newPatterns.begin(); jt != newPatterns.end(); ++jt )
			{
				QString p = (*jt);
				if (p.isEmpty())
					continue;
				if (p[0] == '+')
				{
					minus = false;
					p = p.mid(1);
				}
				else if (p[0] == '-')
				{
					minus = true;
					p = p.mid(1);
				}
				if (p.isEmpty())
					continue;
				if (!minus)
				{
					_includePatterns.push_back(p);
				}
				else
				{
					_excludePatterns.push_back(p);
					minus = false;
				}
			}
		}
		else
		{
			if ((*it).isEmpty())
				continue;
			if (!minus)
			{
				_includePatterns.push_back(*it);
			}
			else
			{
				_excludePatterns.push_back(*it);
				minus = false;
			}
		}
		inGroup = !inGroup;
	}
}



}	// namespace NPlugin
