//
// C++ Implementation: %{MODULE}
//
// Description:
//
//
// Author: %{AUTHOR} <%{EMAIL}>, (C) %{YEAR}
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "dumpavailpackagedb.h"

#include <iostream>
#include <stdio.h>

#include <qdatetime.h>
#include <QtDebug>
#include <qfile.h>
#include <qstringlist.h>

// NUtil
#include <helpers.h>

// NApplication
#include <runcommandforoutput.h>

// NPlugin
#include <iprovider.h>
#include <iprogressobserver.h>
#include <packagenotfoundexception.h>


namespace NApt
{

DumpAvailPackageDB::DumpAvailPackageDB(NUtil::IProgressObserver* pObserver, uint estimatedPackageNum)
{
	_estimatedPackageNum = estimatedPackageNum;
	reloadPackageInformation(pObserver);
}


DumpAvailPackageDB::~DumpAvailPackageDB()
{
	qDebug("Deleting DumpAvailPackageDB");
}

/////////////////////////////////////////////////////
// IPackageDB Interface
/////////////////////////////////////////////////////


const Package& DumpAvailPackageDB::getPackageRecord(const QString& pkg) const
{
	string package = toString(pkg);
	PackageMap::const_iterator it = _packages.find(package);
	if (it == _packages.end())
		throw NPlugin::PackageNotFoundException(package);
	return it->second;
}


const Package& DumpAvailPackageDB::getPackageRecord(const string& package) const
{
	PackageMap::const_iterator it = _packages.find(package);
	if (it == _packages.end())
		throw NPlugin::PackageNotFoundException(package);
	return it->second;
}


const QString DumpAvailPackageDB::getShortDescription(const string& package) const
{
	PackageMap::const_iterator it = _packages.find(package);
	if (it == _packages.end())
		throw NPlugin::PackageNotFoundException(package);
	return it->second.shortDescription();
}

Package::InstalledState DumpAvailPackageDB::getState(const string& package) const
{
	PackageMap::const_iterator it = _packages.find(package);
	if (it == _packages.end())
		throw NPlugin::PackageNotFoundException(package);
	return it->second.installedState();
}

void DumpAvailPackageDB::reloadPackageInformation(NUtil::IProgressObserver* pObserver)
{
	_packages.clear();
	{
		if (pObserver)
			pObserver->setText("reading package information");
		
		const char* command = "apt-cache dumpavail";
		#ifdef __DEBUG
		qDebug() << "running " << command;
		QTime t;
		t.start();
		#endif
		
		// setup the data for progress infromation
		double increment;
		if ( _estimatedPackageNum == 0 )
		 	increment = 0;
		else
			increment = 80.0 / _estimatedPackageNum;	// update the progress every 1%
		double count = 0;
		int dbgCount = 0;
		int progress = 0;
		
		FILE* pipe = popen(command, "r");
		// current maximum line length of apt-cache dumpavail is around 7800 so 50000 should offer enough safety
		int maximumLineLength = 50000;
		char* chars = new char[maximumLineLength];
		QStringList packageLines;
		while (fgets(chars, maximumLineLength, pipe) != 0)
		{
			QString line(chars);
			line.truncate(line.length()-1);	// remove the trailing newline
			if (!line.isEmpty()) 
			{
				packageLines.append(line);
			} 
			else 
			{
				Package p(packageLines);
				_packages[toString(p.name())] = p;
				packageLines.clear();
				if (pObserver)
				{
					count += increment;
					++dbgCount;
					if (count >= 1)
					{
						++progress;
						pObserver->setProgress(progress);
						count -= 1;
					}
				}
			}
		}
		qDebug("size: %d, debug-count: %d", _estimatedPackageNum, dbgCount);
		delete[] chars;
		pclose(pipe);
		#ifdef __DEBUG
		qDebug( "Time elapsed: %d ms", t.elapsed() );	
		#endif
	}
	// 80% done
	{	
		// reading the status file
		#ifdef __DEBUG
		QTime t;
		t.start();
		#endif
		
		if (pObserver)
		{
 			pObserver->setProgress(80);
			pObserver->setText("parsing status information");
		}
		
		// setup the data for progress infromation
		double increment;
		if ( _estimatedPackageNum == 0 )
		 	increment = 0;
		else
			increment = 20.0 / _estimatedPackageNum;	// update the progress every 1%
		double count = 0;
		int dbgCount = 0;
		int progress = 80;

		FILE* statusFile = fopen("/var/lib/dpkg/status", "r");
		// current maximum line length of apt-cache dumpavail is around 6700 so 50000 should offer enough safety
		int maximumLineLength = 50000;
		char* chars = new char[maximumLineLength];
		QStringList packageLines;
		Package* pP = 0;
		while (fgets(chars, maximumLineLength, statusFile) != 0)
		{
			QString line(chars);
			line.truncate(line.length()-1);	// remove the trailing newline
			if ( line.isEmpty() )	// package ended
			{
				if (pObserver)
				{
					count += increment;
					++dbgCount;
					if (count >= 1)
					{
						++progress;
						pObserver->setProgress(progress);
						count -= 1;
					}
				}
				// if we gathered some information
				if (!packageLines.empty())
				{
					pP->parseInformation(packageLines);
				}
				packageLines.clear();
			}
			else if ( line.startsWith("Package: ") )
			{
				QString pkg = (line.mid(9));
				PackageMap::iterator jt = _packages.find(toString(pkg));
				if (jt == _packages.end())
					// insert a new package and return the iterator to it
 					jt = _packages.insert(make_pair(toString(pkg), Package(pkg))).first;
				pP = &((*jt).second);
			}
			else if (line.startsWith("Status: "))
			{
				// QString status = (line.mid(8));
				if (pP != 0)
				{
					if (line.endsWith(" installed"))
						pP->_installedState = Package::INSTALLED;
				}
			}
			else if (line.startsWith("Version: "))
			{
				if (pP != 0)
				{
					QString installedVersion = (line.mid(9));
					pP->_installedVersion = installedVersion;
				}
			}
			else
				packageLines.push_back(line);
		}
		qDebug("size: %d, debug-count: %d", _estimatedPackageNum, dbgCount);
		delete[] chars;
		fclose(statusFile);
	
		// collect the information needed by the package here
		#ifdef __DEBUG
		qDebug( "Time elapsed for /var/lib/dpkg/status: %d ms", t.elapsed() );	
		#endif
	}
	if (pObserver)
		pObserver->setProgress(100);
	_estimatedPackageNum = _packages.size();
}

int DumpAvailPackageDB::getDescriptionCount(const string& package, const QString& pattern, bool caseSensitive) const
{
	PackageMap::const_iterator it = _packages.find(package);
	// if the package can't be found
	if (it == _packages.end())
		throw NPlugin::PackageNotFoundException(package);
	const Package& packageStruct = it->second;
	Qt::CaseSensitivity qtCaseSensitive = (caseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
	return packageStruct.description().count(pattern, qtCaseSensitive);
}

bool DumpAvailPackageDB::matchesName(const string& package, const QString& pattern, bool caseSensitive) const
{
	PackageMap::const_iterator it = _packages.find(package);
	// if the package can't be found
	if (it == _packages.end())
		throw NPlugin::PackageNotFoundException(package);
	const Package& packageStruct = it->second;
	Qt::CaseSensitivity qtCaseSensitive = (caseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
	return packageStruct.name().contains(pattern, qtCaseSensitive);
}


/////////////////////////////////////////////////////
// IAptSearch Interface
/////////////////////////////////////////////////////

// bool DumpAvailPackageDB::search(Tagcoll::OpSet<string>& result,
// 		const QString& pattern, bool searchDescr, bool caseSensitive) const
// {
// 	for (PackageMap::const_iterator it = _packages.begin(); it!=_packages.end(); ++it)
// 	{
// 		const Package& package(it->second);
// 		if ( package.name().contains(pattern, caseSensitive) || 
// 				(searchDescr && package.description().contains(pattern, caseSensitive))
// 		)
// 			result.insert(it->first);
// 	}
// 	return result.empty();
// }
// 
// 
// bool DumpAvailPackageDB::search(Tagcoll::OpSet<string>& result, const QStringList& includePatterns, 
// 	const QStringList& excludePatterns, bool searchDescr, bool caseSensitive) const
// {
// 	for (PackageMap::const_iterator it = _packages.begin(); it!=_packages.end(); ++it)
// 	{
// 		const Package& package(it->second);
// 		// holds if the search matches the package
// 		bool included = true;
// 		// check if each included pattern occurs in the package
// 		for (PackageContainer::const_iterator jt = includePatterns.begin(); jt != includePatterns.end(); ++jt)
// 		{
// 			if ( !  ( package.name().contains(*jt, caseSensitive) ||
// 					    (searchDescr && package.description().contains(*jt, caseSensitive))
// 					  ) 
// 				)
// 			{
// 				included = false;
// 				break;
// 			}
// 		}
// 		if (!included)
// 			continue;
// 		// check if each excluded pattern does not occur in the package
// 		for (PackageContainer::const_iterator jt = excludePatterns.begin(); jt != excludePatterns.end(); ++jt)
// 		{
// 			if ( package.name().contains(*jt, caseSensitive) ||
// 				  (searchDescr && package.description().contains(*jt, caseSensitive))
// 				)
// 			{
// 				included = false;
// 				break;
// 			}
// 		}
// 		if (included)
// 			result.insert(it->first);
// 	}
// 	return result.empty();
// }


}	// namespace NApt

