/***************************************************************************
 *	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 <algorithm>              // for max
#include <cstddef>               // for NULL
#include <klocalizedstring.h>     // for i18n
#include <qabstractitemmodel.h>   // for QModelIndex, QAbstractItemModel
#include <qabstractitemview.h>    // for QAbstractItemView, QAbstractItemVie...
#include <qdebug.h>               // for QDebug
#include <qevent.h>               // for QMouseEvent
#include <qglobal.h>              // for foreach, qSwap
#include <qheaderview.h>          // for QHeaderView, QHeaderView::ResizeToC...
#include <qitemselectionmodel.h>  // for QItemSelection
#include <qlocale.h>              // for QLocale
#include <qnamespace.h>           // for NoModifier
#include <qvariant.h>             // for QVariant, QVariant::String

#include "common.h"               // for KurooDBSingleton, QueueSingleton
#include "history.h"              // for History
#include "portagedb.h"            // for KurooDB
#include "queue.h"                // for Queue
#include "queuelistdelegate.h"    // for QueueListDelegate
#include "queuelistitem.h"        // for QueueListItem
#include "queuelistmodel.h"       // for QueueListModel
#include "queuelistview.h"
#include "signalist.h"            // for Signalist

class QWidget;

const int diffTime = 10;
//const QRegularExpression QueueListView::m_rxNonDigit = QRegularExpression("\\D");

QueueListView::QueueListView(QWidget *parent)
 : QTreeView(parent)
{
	auto *m = new QueueListModel(this);
	setModel(m);
	setItemDelegateForColumn(5, new QueueListDelegate(this));
	QHeaderView *hh = header();
	hh->setStretchLastSection(true);
	hh->setSectionResizeMode(QHeaderView::ResizeToContents);
	hh->setSectionResizeMode(5, QHeaderView::Stretch);

	setSelectionBehavior(QAbstractItemView::SelectRows);
	setSelectionMode(QAbstractItemView::ExtendedSelection);
	setAlternatingRowColors(true);
}

QueueListView::~QueueListView()
= default;

void QueueListView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{
	
	// declaring a local const container prevents detaching and the attendant clazy warning
	// In C++20 the range for can do this in the loop declaration https://www.kdab.com/blog-qasconst-and-stdas_const/
	const QModelIndexList deselectedindexes = deselected.indexes();
	for(const QModelIndex index : deselectedindexes)
	{
		if (index.column() == 0 && index.data().canConvert<QString>())
		{
			for(int j = 0; j < m_selectedPackages.count(); j++)
				if (m_selectedPackages[j]->name() == index.data().toString())
					m_selectedPackages.removeAt(j);
		}
	}
	// declaring a local const container prevents detaching and the attendant clazy warning
	// In C++20 the range for can do this in the loop declaration https://www.kdab.com/blog-qasconst-and-stdas_const/
	const QModelIndexList selectedindexes = selected.indexes();
	for(const QModelIndex index : selectedindexes)
	{
		if (index.column() == 0 && index.data().canConvert<QString>())
		{
			auto *item = static_cast<QueueListItem*>(index.internalPointer());
			m_selectedPackages << item;
		}
	}

	Q_EMIT selectionChangedSignal();
}

auto QueueListView::selectedPackages() const -> QList<QueueListItem*>
{
	return m_selectedPackages;
}

auto QueueListView::currentPackage() -> QueueListItem*
{
	return static_cast<QueueListItem*>(currentIndex().internalPointer());
}

auto QueueListView::packageItemById(const QString& id) -> QueueListItem*
{
	//QMap will insert a default-constructed value if it doesn't already exist
	if (m_packageIndex.contains(id)) {
		return m_packageIndex[id];
	}
	return nullptr;
}

auto QueueListView::selectedPackagesByIds() -> QStringList
{
	QStringList ret;
	for(const QueueListItem *item : std::as_const(m_selectedPackages))
		ret << item->id();

	return ret;
}

/**
 * Populate queue with packages from db.
 * @param bool hasCheckedQueue: whether packageList is the result of emerge pretend or just added by user manually.
 */
void QueueListView::insertPackageList( bool hasCheckedQueue )
{
	DEBUG_LINE_INFO;
	QueueListItem* item = nullptr;
	QList<QueueListItem*> items;
	QList<QueueListItem*> orphans;
	m_sumSize = 0;

	reset();//FIXME: I don't know why this is labeled fixme
	m_selectedPackages.clear();
	m_packageIndex.clear();

	// Get list of update packages with info
	const QStringList packageList = KurooDBSingleton::Instance()->allQueuePackages();
	//int packageCount = packageList.size() / 7;
	QListIterator<QString> it( packageList );
	while( it.hasNext() ) {
		QString id = it.next();
		QString category = it.next();
		QString name = it.next();
		QString status = it.next();
		QString idDepend = it.next();
		QString size = it.next();
		QString version = it.next();

		// Get package emerge duration from statistics
		int duration = HistorySingleton::Instance()->packageTime( category + u'/' + name ).toInt() + diffTime;

		// If version get size
		if ( size.isEmpty() || size == u'0' )
			size = KurooDBSingleton::Instance()->versionSize( id, version );
		else
			size = formatSize( size );

		item = new QueueListItem(name, id, category, status.toInt(), duration, this);

		if ( !idDepend.isEmpty() && idDepend != u'0' ) {
			item->setParentId(idDepend);

			if (m_packageIndex.contains(idDepend))
				m_packageIndex[idDepend]->appendChild(item);
			else
				orphans << item;
		}

		for(int i = 0; i < orphans.count(); i++)
		{
			QueueListItem *orphan = orphans.at(i);
			if (orphan->parentId() == item->id())
				item->appendChild(orphans.takeAt(i));
		}

		// Add version to be emerged
		if ( version.isEmpty() )
			item->setVersion(i18n("na") );
		else
			item->setVersion(version);

		// Add emerge duration
		if ( duration == diffTime )
			item->setDuration(-1);
		else
			item->setDuration(duration);

		// Add download size = tarball size
		if ( size.isEmpty() )
			item->setSize(i18n("na"));
		else {
			item->setSize(size);
			addSize(size);
		}

		item->setPretended( hasCheckedQueue );
		item->setIsComplete(QueueSingleton::Instance()->hasCompleted(id));

		indexPackage( id, item );

		// Inform all other listviews that this package is in queue
		QueueSingleton::Instance()->insertInCache( id );

		//Fix bug #3163827 order of queue items
		items.prepend( item );
	}

	// Cannot have current changed for only one package so emit manually
	/*if ( packageCount == 1 )
		emit currentChanged( 0 );*/

	dynamic_cast<QueueListModel*>(model())->setPackages(items);
	SignalistSingleton::Instance()->packageQueueChanged();
}

/**
 * Get total emerge duration in format hh:mm:ss.
 @return totalDuration
 */
auto QueueListView::totalDuration() const -> long
{
	long totalSeconds = 0;
	const QList<QueueListItem*> packages = dynamic_cast<QueueListModel*>(model())->packages();
	for(const QueueListItem *item : std::as_const(packages))
	{
		// declaring a local const container prevents detaching and the attendant clazy warning
		// In C++20 the range for can do this in the loop declaration https://www.kdab.com/blog-qasconst-and-stdas_const/
		// Trying to use std::as_const here says "call to deleted function"
		const QList<QueueListItem*> children = item->children();
		for(const QueueListItem *child : children)
		{
			if (!child->isComplete()) {
				totalSeconds += child->remainingDuration();
			}
		}
		if (!item->isComplete())
		{
			totalSeconds += item->remainingDuration();
		}
	}
	return std::max( totalSeconds, 0L );
}

/**
 * All packages in listview by name - no children
 * @return packageList
 */
auto QueueListView::allPackagesNoChildren() -> const QStringList
{
	QStringList packageList;
	QList<QueueListItem*> packages = dynamic_cast<QueueListModel*>(model())->packages();
	for(const QueueListItem *item : std::as_const(packages))
	{
		if (item->parentItem() == nullptr)
		{
			packageList << item->category() + u'/' + item->name();
		}
	}

	return packageList;
}

auto QueueListView::allId() const -> const QStringList
{
	return m_packageIndex.keys();
}

auto QueueListView::allPackages() const -> QList<QueueListItem*>
{
	return m_packageIndex.values();
}

void QueueListView::nextPackage( bool isPrevious )
{
	if ( isVisible() ) {
		QModelIndex item;
		if ( isPrevious )
			item = moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier);
		else
			item = moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier);

		if (item.isValid()) {
			scrollTo(item);
			setCurrentIndex(item);
		}
	}
}

/**
 * Format package size nicely
 * @param size
 * @return total		as "xxx kB"
 */
auto QueueListView::formatSize( const QString& sizeString ) -> const QString
{
	QString total;
	QString tmp = sizeString;
	long long size = tmp.remove(u',').toLongLong();

	if ( size == 0 )
		total = QStringLiteral("0 kB ");
	else
		total = QLocale::system().formattedDataSize( size * 1024 );

	return total;
}

/**
 * Add this package size to total.
 * @param size
 */
void QueueListView::addSize( const QString& size )
{
	QString packageSize( size );
	packageSize = packageSize.remove( m_rxNonDigit );
	m_sumSize += packageSize.toInt() * 1024;
}

/**
 * Register package in index and check if in the queue.
 * @param id
 * @param item
 */
void QueueListView::indexPackage( const QString& id, QueueListItem *item )
{
	if ( !id.isEmpty() ) {
		m_packageIndex.insert( id, item );
		m_packageIndex[id]->setPackageIndex( m_packageIndex.count() );
	}
}

void QueueListView::mouseDoubleClickEvent(QMouseEvent *event)
{
	QModelIndex index = indexAt(event->pos());
	if (!index.isValid())
		return;

	auto *item = static_cast<QueueListItem*>(index.internalPointer());
	if (!item)
		return;

	Q_EMIT itemDoubleClicked(item);
}

void QueueListView::hasStarted(const QString& id)
{
	slotPackageStart(id);
}

void QueueListView::slotPackageStart(const QString& id)
{
	m_currentEmergingId = id;
	if (m_packageIndex.contains(id))
		m_packageIndex[id]->setHasStarted(true);
}

void QueueListView::slotPackageProgress()
{
	if (!m_currentEmergingId.isEmpty() && m_packageIndex.contains(m_currentEmergingId))
		m_packageIndex[m_currentEmergingId]->oneStep();
}

void QueueListView::slotPackageComplete(const QString& id)
{
	if (id == m_currentEmergingId)
	{
		if (m_packageIndex.contains(id))
			m_packageIndex[id]->setIsComplete(true);
		m_currentEmergingId.clear();
	}
}

