/***************************************************************************
 *	Copyright (C) 2010 by cazou88											*
 *	cazou88@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 <klocalizedstring.h>  // for i18n
#include <qglobal.h>           // for qSwap, foreach
#include <QList>
#include <QStringList>

#include <utility>

#include "common.h"            // for KurooDBSingleton, PortageSingleton
#include "dependatom.h"        // for PortageAtom
#include "packagebase.h"       // for PackageBase
#include "packagelistitem.h"
#include "packageversion.h"    // for PackageVersion
#include "portage.h"           // for Portage
#include "portagedb.h"         // for KurooDB
#include "portagelistview.h"   // for PortageListView
#include "queuelistview.h"     // for QueueListView
#include "settings.h"          // for KurooConfig

PackageListItem::PackageListItem(QObject *parent)
 : QObject(parent), 
   m_id(),
   m_description(),
   m_update(),
   m_status(0),
   m_isInitialized(false)
{}

PackageListItem::PackageListItem(const QString& name, QString  id, const QString& category, QString  description, const int status, QString  update, QObject *parent)
 : QObject(parent), PackageBase(name, category),
   m_id(std::move(id)),
   m_description(std::move(description)),
   m_update(std::move(update)),
   m_status(status),
   m_isInitialized(false)
{
	m_isInWorld = PortageSingleton::Instance()->isInWorld(m_category + u'/' + m_name);
	
	connect(PortageSingleton::Instance(), &Portage::signalWorldChanged, this, &PackageListItem::worldChanged);
}

/**
 * Initialize the package with all its versions and info. Executed when PortageItem get focus first time.
 */
void PackageListItem::initVersions()
{
	if ( !m_isInitialized ) {
		while (!m_versions.isEmpty())
			delete m_versions.takeFirst();
		m_versions.clear();
		m_versionMap.clear();

		// Get list of accepted keywords, eg if package is "untesting"
		QString acceptedKeywords = KurooDBSingleton::Instance()->packageKeywordsAtom( id() );
		const QStringList versionsList = KurooDBSingleton::Instance()->packageVersionsInfo( id() );
		for (auto it = versionsList.constBegin(); it != versionsList.constEnd(); ++it ) {
			QString versionString = *it++;
			QString description = *it++;
			QString homepage = *it++;
			QString status = *it++;
			QString licenses = *it++;
			QString useFlags = *it++;
			QString slot = *it++;
			QString keywords = *it++;
			QString size = *it;

			// WARN: ASAN reports this as a memory leak, reproducible by selecting an item
			// in the package list such that it displays the versions in the summary pane
			// writing an actual destructor which 'delete's each item in m_versions and
			// deleting them at the top of this method before 'clear()' has fixed that
			auto* version = new PackageVersion( versionString );
			version->setDescription( description );
			version->setHomepage( homepage );
			version->setLicenses( licenses.split(u' ') );
			version->setUseflags( useFlags.split(u' ') );
			version->setSlot( slot );
			version->setKeywords( keywords.split(u' ') );
			version->setAcceptedKeywords( acceptedKeywords.split(u' ') );
			version->setSize( size );

			if ( status == PACKAGE_INSTALLED_STRING )
				version->setInstalled( true );

			m_versions.append( version );
			m_versionMap.insert( versionString, version );
		}

		// Now that we have all available versions, sort out masked ones and leaving unmasked.

		// Check if any of this package versions are hardmasked
        atom = new PortageAtom( this );
		const QStringList atomHardMaskedList = KurooDBSingleton::Instance()->packageHardMaskAtom( id() );
	// 	qDebug() << "atomHardMaskedList=" << atomHardMaskedList;
		for( const QString& mask : atomHardMaskedList ) {

			// Test the atom string on validness, and fill the internal variables with the extracted atom parts,
			// and get the matching versions
			if ( atom->parse( mask ) ) {
				QList<PackageVersion*> versions = atom->matchingVersions();
				QList<PackageVersion*>::iterator versionIterator;
				for( versionIterator = versions.begin(); versionIterator != versions.end(); ++versionIterator )
					( *versionIterator )->setHardMasked( true );
			}
		}
		delete atom;

		// Check if any of this package versions are user-masked
        atom = new PortageAtom( this );
		const QStringList atomUserMaskedList = KurooDBSingleton::Instance()->packageUserMaskAtom( id() );
	// 	qDebug() << "atomUserMaskedList=" << atomUserMaskedList;
		for( const QString& mask : atomUserMaskedList ) {

			// Test the atom string on validness, and fill the internal variables with the extracted atom parts,
			// and get the matching versions
			if ( atom->parse( mask ) ) {
				QList<PackageVersion*> versions = atom->matchingVersions();
				QList<PackageVersion*>::iterator versionIterator;
				for( versionIterator = versions.begin(); versionIterator != versions.end(); ++versionIterator )
					( *versionIterator )->setUserMasked( true );
			}
		}
		delete atom;

		// Check if any of this package versions are unmasked
        atom = new PortageAtom( this );
		const QStringList atomUnmaskedList = KurooDBSingleton::Instance()->packageUnMaskAtom( id() );
	// 	qDebug() << "atomUnmaskedList=" << atomUnmaskedList;
		for( const QString& mask : atomUnmaskedList ) {

			// Test the atom string on validness, and fill the internal variables with the extracted atom parts,
			// and get the matching versions
			if ( atom->parse( mask ) ) {
				QList<PackageVersion*> versions = atom->matchingVersions();
				QList<PackageVersion*>::iterator versionIterator;
				for( versionIterator = versions.begin(); versionIterator != versions.end(); ++versionIterator )
					( *versionIterator )->setUnMasked( true );
			}
		}
		delete atom;

		// This package has collected all it's data
		m_isInitialized = true;
	}
}


/**
 * Return a list of PackageVersion objects sorted by their version numbers,
 * with the oldest version at the beginning and the latest version at the end
 * of the list.
 * @return sortedVersions
 */
auto PackageListItem::sortedVersionList() -> QList<PackageVersion*>
{
	QList<PackageVersion*> sortedVersions;
	QList<PackageVersion*>::iterator sortedVersionIterator;

	for( auto versionIterator = m_versions.begin(); versionIterator != m_versions.end(); ++versionIterator) {
		if ( versionIterator == m_versions.begin() ) {
			sortedVersions.append( *versionIterator );
			continue; // if there is only one version, it can't be compared
		}

		// reverse iteration through the sorted version list
		sortedVersionIterator = sortedVersions.end();
		while ( true ) {
			if ( sortedVersionIterator == sortedVersions.begin() ) {
				sortedVersions.prepend( *versionIterator );
				break;
			}

			--sortedVersionIterator;
			if ( (*versionIterator)->isNewerThan( (*sortedVersionIterator)->version() ) ) {
				++sortedVersionIterator; // insert after the compared one, not before
				sortedVersions.insert( sortedVersionIterator, *versionIterator );
				break;
			}
		}
	}
	return sortedVersions;
}

/**
 * Parse sorted list of versions for stability, installed, emerge versions ...
 */
void PackageListItem::parsePackageVersions()
{
	if ( !m_isInitialized )
		initVersions();

	m_versionsDataList.clear();
	m_linesAvailable = QString();
	m_linesEmerge = QString();
	m_linesInstalled = QString();

	// Iterate sorted versions list
	QString version;
	QList<PackageVersion*> sortedVersions = sortedVersionList();
	QList<PackageVersion*>::iterator sortedVersionIterator;
	for ( sortedVersionIterator = sortedVersions.begin(); sortedVersionIterator != sortedVersions.end(); ++sortedVersionIterator ) {

		version = (*sortedVersionIterator)->version();

		// Mark official version stability for version listview
		QString stability;
		if ( (*sortedVersionIterator)->isNotArch() ) {
			stability = i18n( "Not on %1", KurooConfig::arch() );
		}
		else {
			if ( (*sortedVersionIterator)->isOriginalHardMasked() ) {
				stability = i18n( "Hardmasked" );
				version = QStringLiteral("<font color=darkRed><i>") + version + QStringLiteral("</i></font>");
			}
			else {
				if ( (*sortedVersionIterator)->isOriginalTesting() ) {
					stability = i18n( "Testing" );
					version = QStringLiteral("<i>") + version + QStringLiteral("</i>");
				}
				else {
					if ( (*sortedVersionIterator)->isAvailable() ) {
						stability = i18n( "Stable" );
					}
					else {
						stability = i18n( "Not available" );
					}
				}
			}
		}

// 		qDebug() << "version="<< (*sortedVersionIterator)->version() << " isInstalled=" << (*sortedVersionIterator)->isInstalled() <<
// 			" stability=" << stability;

		// Versions data for use by Inspector in vewrsion view
		m_versionsDataList << (*sortedVersionIterator)->version() << stability << (*sortedVersionIterator)->size();

		// Create nice summary showing installed packages
		if ( (*sortedVersionIterator)->isInstalled() ) {
			m_versionsDataList << QStringLiteral("1");
			version = QStringLiteral("<b>") + version + QStringLiteral("</b>");
			m_linesInstalled.prepend( version + QStringLiteral(" (") + stability + QStringLiteral("), ") );
		}
		else {
			m_versionsDataList << QStringLiteral("0");
		}

		// Collect all available packages except those not in users arch
		if ( (*sortedVersionIterator)->isAvailable() ) {
			m_emergeVersion = (*sortedVersionIterator)->version();
			m_linesEmerge = version + QStringLiteral(" (") + stability + QStringLiteral(")");
			m_linesAvailable.prepend( version + QStringLiteral(", ") );
		}
		else {
			if ( (*sortedVersionIterator)->isNotArch() ) {
				m_isInArch = false;
			}
			else {
				m_linesAvailable.prepend( version + QStringLiteral(", ") );
			}
		}

		// Get description and homepage from most recent version = assuming most correct
		m_description = (*sortedVersionIterator)->description();
		m_homepage = (*sortedVersionIterator)->homepage();
	}

	// Remove trailing commas
	m_linesInstalled.truncate( m_linesInstalled.length() - 2 );
	m_linesAvailable.truncate( m_linesAvailable.length() - 2 );

}

/**
 * Is this package installed.
 * @return true if yes
 */
auto PackageListItem::isInstalled() const -> bool
{
	return ( m_status & ( PACKAGE_INSTALLED | PACKAGE_UPDATES | PACKAGE_OLD ) );
}

/**
 * Is this package available in Portage tree?
 * @return true if yes
 */
auto PackageListItem::isInPortage() const -> bool
{
	return ( m_status & ( PACKAGE_AVAILABLE | PACKAGE_INSTALLED | PACKAGE_UPDATES ) );
}

void PackageListItem::resetDetailedInfo()
{
	m_isInitialized = false;
}

void PackageListItem::setPackageIndex(const int idx)
{
	m_index = idx;
}

auto PackageListItem::isFirstPackage() const -> bool
{
	auto* plv = dynamic_cast<PortageListView*>(parent());
	auto* qlv = dynamic_cast<QueueListView*>(parent());

	if (plv)
		return (m_index == plv->packages().count());

	if (qlv)
		return (m_index == qlv->allPackages().count());

	return true;
}

void PackageListItem::worldChanged()
{
	if (PortageSingleton::Instance()->isInWorld(m_category + u'/' + m_name) != m_isInWorld)
		m_isInWorld = PortageSingleton::Instance()->isInWorld(m_category + u'/' + m_name);
}
