/***************************************************************************
 *	Copyright (C) 2005 by Karye												*
 *	karye@users.sourceforge.net												*
 *																			*
 *	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.				*
 ***************************************************************************/

#include <QDebug>       // for QDebug
#include <QDir>         // for QDir, operator|, QDir::Name, QDir::NoSymLinks
#include <QtGlobal>      // for qWarning, foreach, qCritical
#include <QList>        // for QList<>::iterator, QList<>::Iterator, QList
#include <QMap>         // for QMap, QMap<>::iterator
#include <qobjectdefs.h>  // for Q_EMIT
#include <QString>      // for QString, operator+, operator<
#include <QStringList>  // for QStringList

#include "cacheportagejob.h"
#include "common.h"       // for KurooDBSingleton, SignalistSingleton, DEBUG...
#include "global.h"       // for parsePackage
#include "portagedb.h"    // for KurooDB, DbConnection
#include "progressjob.h"  // for ProgressJob
#include "settings.h"     // for KurooConfig
#include "signalist.h"    // for Signalist

namespace ThreadWeaver {
class Thread;
}  // namespace ThreadWeaver

/**
 * @class CachePortageJob
 * @short Thread to cache package information from the Portage directory to speed up portage refreshing.
 *
 * Portage cache is scanned for package sizes, and stored in portage cache map and in the database.
 */
CachePortageJob::CachePortageJob()
    : m_db( KurooDBSingleton::Instance()->getStaticDbConnection() )
{
}

CachePortageJob::~CachePortageJob()
{
	KurooDBSingleton::Instance()->returnStaticDbConnection( m_db );
}

/**
 * Scan for package size found in digest files.
 */
void CachePortageJob::run( ThreadWeaver::JobPointer  /*self*/, ThreadWeaver::Thread*  /*thread*/)
{
	DEBUG_LINE_INFO;
	if ( !m_db->isConnected() ) {
		qCritical() << "Creating cache. Can not connect to database";
		return;// false;
	}

	int count( 0 );
	QMap <QString, uint> packageNameToSizeInBytes;
	QDir dCategory, dPackage;
	dCategory.setFilter( QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot );
	QStringList nonCategories = { QStringLiteral("distfiles"), QStringLiteral("eclass"), QStringLiteral("licenses"), QStringLiteral("metadata"), QStringLiteral("packages"), QStringLiteral("profiles"), QStringLiteral("scripts") };	// for filtering out false errors
	dPackage.setFilter( QDir::NoDotAndDotDot );
	//dCategory.setSorting( QDir::Name );

	// Get a count of total packages for proper progress
	int packageCount = KurooDBSingleton::Instance()->singleQuery( QStringLiteral("SELECT data FROM dbInfo WHERE meta = 'packageCount' LIMIT 1;"), m_db ).toInt();
	setTotalSteps( packageCount == 0 ? 35000 : packageCount );
	//TODO: setStatus( "CachePortage", i18n("Collecting package information...") );

	//TODO: kuroo was already built to handle overlays?
	// Scan Portage cache
	for (auto & itPath : KurooConfig::repoLocations()) {
		if ( !dCategory.cd( itPath ) ) {
			qWarning() << "Creating cache. Can not access " << itPath;
			continue;
		}

		for ( auto & itCategory : dCategory.entryList() ) {
			//It's probably fewer cycles to just allow special non-categories error and filter out false-negative errors later

            // Abort the scan
			if ( isAborted() ) {
				qWarning() << "Creating cache. Aborted!";
				//TODO: setStatus( "CachePortage", i18n("Caching aborted.") );
				//Q_EMIT failed( self );
				setStatus( ThreadWeaver::JobInterface::Status::Status_Failed );
				SignalistSingleton::Instance()->cachePortageComplete();
				return;
            }

			// Get list of package-versions in this category
			QString currentManifest;
			QHash<QString, uint> manifestFilesToSize;
			dPackage.setFilter( QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot );
			dPackage.setSorting( QDir::Name );
			if ( dPackage.cd( itPath + QStringLiteral("/metadata/md5-cache/") + itCategory ) ) {
				for ( auto & itPackage : dPackage.entryList() ) {
					if ( itPackage == QStringLiteral("Manifest.gz") )
						continue;

					// Abort the scan
					if ( isAborted() ) {
						qWarning() << "Creating cache. Aborted!";
						//TODO: setStatus( "CachePortage", i18n("Caching aborted.") );
						setStatus( ThreadWeaver::JobInterface::Status::Status_Failed );
						//Q_EMIT failed( self );
						SignalistSingleton::Instance()->cachePortageComplete();
						return;
					}
					//Includes version
					QString package = itCategory + u'/' + itPackage;

					QStringList parts = parsePackage( itPackage );
					if ( parts.isEmpty() ) {
						qWarning() << __PRETTY_FUNCTION__ << "Can't parse" << itPackage;
						continue;
					}

					QString packageName = parts[1];
					// itPath here is still the repo dir, so this will read /usr/portage/app-portage/kuroo/Manifest
					QString newManifest( itPath + u'/' + itCategory + u'/' + packageName + QStringLiteral("/Manifest") ); 
					if ( currentManifest != newManifest ) {
						manifestFilesToSize.clear();
						currentManifest = newManifest;
						QFile manifestFile( currentManifest );
						if ( manifestFile.open( QIODevice::ReadOnly ) ) {
							QTextStream lines( &manifestFile );
							while ( !lines.atEnd() ) {
								auto tokens = lines.readLine().split(u' ');	//No way to limit the number of tokens?
								//each line is <type> <name> <size> (<checksum> <checksumtype>)+ per https://www.gentoo.org/glep/glep-0044.html
								manifestFilesToSize[tokens[1]] = tokens[2].toUInt();	//Returns 0 if conversion failed, would be nice to qWarn
							}
						}
						else {
							qWarning() << __PRETTY_FUNCTION__ << "Can't open" << currentManifest;
						}
					}

					//Read distfile size out of Manifest2
					//metadata/md5-cache/<category>/<package>-<version>
					QString path = dPackage.absolutePath() + u'/' + itPackage;
					QFile metadataCacheFile( path );
					if ( metadataCacheFile.open( QIODevice::ReadOnly ) ) {
						//This is the md5-cache file with all vars listed in dependency specification syntax per https://projects.gentoo.org/pms/7/pms.html#x1-68002r2
						uint runningSize = 0;
						QTextStream lines( &metadataCacheFile );
						while ( !lines.atEnd() ) {
							auto line = lines.readLine();
							if ( line.startsWith( QStringLiteral("SRC_URI=") ) ) {
								//Chomp off 'SRC_URI=' and tokenize by spaces
								auto sources = line.mid( 8 ).split(u' ');
								// For any construct like "<URL> -> <filename>" the URL is just renamed to filename and manifested as filename, so ignore '->' and 'URL'
								int i = -1;
								while ( (i = sources.lastIndexOf(QStringLiteral("->"), i)) ) {
									if ( i == -1 )
										break;
									sources.removeAt(i);
									sources.removeAt(--i);
									i = sources.lastIndexOf(QStringLiteral("->"), i);
								}
								//Gather all required distfiles, ignoring anything inside parens
								int conditionalStack = 0;
								for ( auto token : sources ) {
									if ( token == u'(' )
										conditionalStack++;
									else if ( token == u')' )
										conditionalStack--;
									else if ( 0 == conditionalStack) {
										if ( token.contains( u'/' ) )
											token = token.mid( token.lastIndexOf( u'/' ) + 1 );

										if ( manifestFilesToSize.contains( token ) )
											runningSize += manifestFilesToSize.value( token );
// 										else if ( token == QStringLiteral("download") )
// 											qWarning() << __PRETTY_FUNCTION__ << "download came out of" << line;
										else if ( !token.endsWith( u'?' ) )
											qWarning() << __PRETTY_FUNCTION__ << "Couldn't find" << token;
									}
								}
								break;	//no other lines can be SRC_URI= so we can stop now
							}
						}
						packageNameToSizeInBytes.insert( package, runningSize );
					}
					else {
						qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "couldn't read" << path;
					}

					// Post scan count progress
					if ( (++count % 100) == 0 )
						setProgress( count );
				}
			}
			else if ( !nonCategories.contains( itCategory ) )
				qWarning() << __PRETTY_FUNCTION__ << "Can't access " << itPath << "/metadata/cache/" << itCategory;

		}
	}
	KurooDBSingleton::Instance()->query( QStringLiteral("UPDATE dbInfo SET data = '%1' WHERE meta = 'packageCount';")
	                                     .arg( count ), m_db );

	// Store cache in DB
	KurooDBSingleton::Instance()->query( QStringLiteral("DELETE FROM cache;"), m_db );
	KurooDBSingleton::Instance()->query( QStringLiteral("BEGIN TRANSACTION;"), m_db );
	qDebug() << __PRETTY_FUNCTION__ << "inserting" << packageNameToSizeInBytes.size() << "packages into cache";
	QMap<QString, uint>::const_iterator package;
	for ( package = packageNameToSizeInBytes.constBegin(); package != packageNameToSizeInBytes.constEnd(); ++package )
		KurooDBSingleton::Instance()->insert( QStringLiteral("INSERT INTO cache (package, size) VALUES ('%1', %2);").
		                                      arg( package.key(), QString::number( package.value() ) ), m_db );

	KurooDBSingleton::Instance()->query(QStringLiteral("COMMIT TRANSACTION;"), m_db );

	//TODO: setStatus( "CachePortage", i18n("Done.") );
	setProgress( 0 );
	DEBUG_LINE_INFO;
// 	return true;
// }
//
// void CachePortageJob::completeJob()
// {
	SignalistSingleton::Instance()->cachePortageComplete();
}

