/***************************************************************************
 *   Copyright (C) 2007 by Joris Guisson and Ivan Vasic                    *
 *   joris.guisson@gmail.com                                               *
 *   ivasic@gmail.com                                                      *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/
#include <QColor>
#include <QMimeData>
#include <KIcon>
#include <KLocale>
#include <util/log.h>
#include <util/functions.h>
#include <torrent/queuemanager.h>
#include <interfaces/torrentinterface.h>
#include "queuemanagermodel.h"
#include "settings.h"
#include <QApplication>


using namespace bt;

namespace kt
{


	QueueManagerModel::QueueManagerModel(QueueManager* qman, QObject* parent)
			: QAbstractTableModel(parent),
			qman(qman),
			show_uploads(true),
			show_downloads(true),
			show_not_queud(true)
	{
		connect(qman, SIGNAL(queueOrdered()), this, SLOT(onQueueOrdered()));
		for (QueueManager::iterator i = qman->begin();i != qman->end();i++)
		{
			bt::TorrentInterface* tc = *i;
			connect(tc, SIGNAL(statusChanged(bt::TorrentInterface*)),
			        this, SLOT(onTorrentStatusChanged(bt::TorrentInterface*)));

			if (visible(tc))
			{
				Item item = {tc, 0};
				queue.append(item);
			}
		}
		
		qSort(queue.begin(), queue.end());
		//dumpQueue();
	}


	QueueManagerModel::~QueueManagerModel()
	{}

	void QueueManagerModel::onQueueOrdered()
	{
		reset();
	}
	
	void QueueManagerModel::updateQueue()
	{
		queue.clear();
		
		for (QueueManager::iterator i = qman->begin();i != qman->end();i++)
		{
			bt::TorrentInterface* tc = *i;
			if (visible(tc))
			{
				Item item = {tc, 0};
				queue.append(item);
			}
		}
		
		reset();
	}

	void QueueManagerModel::setShowDownloads(bool on)
	{
		show_downloads = on;
		updateQueue();
	}

	void QueueManagerModel::setShowUploads(bool on)
	{
		show_uploads = on;
		updateQueue();
	}

	void QueueManagerModel::setShowNotQueued(bool on)
	{
		show_not_queud = on;
		updateQueue();
	}

	void QueueManagerModel::onTorrentAdded(bt::TorrentInterface* tc)
	{
		if (visible(tc))
		{
			Item item = {tc, 0};
			queue.append(item);
			qSort(queue.begin(), queue.end());
			reset();
		}
		connect(tc, SIGNAL(statusChanged(bt::TorrentInterface*)), this, SLOT(onTorrentStatusChanged(bt::TorrentInterface*)));
	}

	void QueueManagerModel::onTorrentRemoved(bt::TorrentInterface* tc)
	{
		disconnect(tc, SIGNAL(statusChanged(bt::TorrentInterface*)),
		           this, SLOT(onTorrentStatusChanged(bt::TorrentInterface*)));
		int r = 0;
		foreach (const Item & i, queue)
		{
			if (tc == i.tc)
			{
				queue.removeAt(r);
				break;
			}
			r++;
		}
	}

	void QueueManagerModel::onTorrentStatusChanged(bt::TorrentInterface* tc)
	{
		int r = 0;
		foreach (const Item & i, queue)
		{
			if (tc == i.tc)
			{
				if (!visible(tc))
				{
					queue.removeAt(r);
					removeRow(r);
				}
				else
				{
					QModelIndex idx = index(r, 2);
					emit dataChanged(idx, idx);
				}
				return;
			}

			r++;
		}

		if (visible(tc))
		{
			Item item = {tc, 0};
			queue.append(item);
			qSort(queue.begin(), queue.end());
			reset();
		}
	}

	int QueueManagerModel::rowCount(const QModelIndex & parent) const
	{
		if (parent.isValid())
			return 0;
		else
			return queue.count();
	}

	int QueueManagerModel::columnCount(const QModelIndex & parent) const
	{
		if (parent.isValid())
			return 0;
		else
			return 4;
	}

	QVariant QueueManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
	{
		if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
			return QVariant();

		switch (section)
		{
			case 0:
				return i18n("Order");
			case 1:
				return i18n("Name");
			case 2:
				return i18n("Status");
			case 3:
				return i18n("Time Stalled");
			case 4:
				return i18n("Priority");
			default:
				return QVariant();
		}
	}

	QVariant QueueManagerModel::data(const QModelIndex & index, int role) const
	{
		if (!index.isValid() || index.row() >= queue.count() || index.row() < 0)
			return QVariant();

		const bt::TorrentInterface* tc = queue.at(index.row()).tc;
		if (role == Qt::ForegroundRole)
		{
			if (index.column() == 2)
			{
				if (tc->getStats().running)
					return QColor(40, 205, 40); // green
				else if (tc->getStats().status == bt::QUEUED)
					return QColor(255, 174, 0); // yellow
				else
					return QVariant();
			}
			return QVariant();
		}
		else if (role == Qt::DisplayRole)
		{
			switch (index.column())
			{
				case 0:
					return index.row() + 1;
				case 1:
					return tc->getDisplayName();
				case 2:
					if (tc->getStats().running)
						return i18n("Running");
					else if (tc->getStats().status == bt::QUEUED)
						return i18n("Queued");
					else
						return i18n("Not queued");
					break;
				case 3:
				{
					if (!tc->getStats().running)
						return QVariant();

					Int64 stalled_time =  queue.at(index.row()).stalled_time;
					if (stalled_time >= 1)
						return i18n("%1", DurationToString(stalled_time));
					else
						return QVariant();
				}
				break;
				case 4:
					return tc->getPriority();
				default:
					return QVariant();
			}
		}
		else if (role == Qt::ToolTipRole && index.column() == 0)
		{
			return i18n("Order of a torrent in the queue.\n"
			            "Use drag and drop or the move up and down buttons on the right to change the order.");
		}
		else if (role == Qt::DecorationRole && index.column() == 1)
		{
			if (!tc->getStats().completed)
				return KIcon("arrow-down");
			else
				return KIcon("arrow-up");
		}
		else if (role == Qt::FontRole && !search_text.isEmpty())
		{
			QFont f = QApplication::font();
			if (tc->getDisplayName().contains(search_text, Qt::CaseInsensitive))
				f.setBold(true);

			return f;
		}

		return QVariant();
	}

	Qt::ItemFlags QueueManagerModel::flags(const QModelIndex &index) const
	{
		Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);

		if (index.isValid())
			return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
		else
			return Qt::ItemIsDropEnabled | defaultFlags;
	}

	Qt::DropActions QueueManagerModel::supportedDropActions() const
	{
		return Qt::CopyAction | Qt::MoveAction;
	}

	QStringList QueueManagerModel::mimeTypes() const
	{
		QStringList types;
		types << "application/vnd.text.list";
		return types;
	}

	QMimeData* QueueManagerModel::mimeData(const QModelIndexList &indexes) const
	{
		QMimeData *mimeData = new QMimeData();
		QByteArray encodedData;

		dragged_items.clear();

		foreach (const QModelIndex &index, indexes)
		{
			if (index.isValid() && !dragged_items.contains(index.row()))
				dragged_items.append(index.row());
		}

		mimeData->setData("application/vnd.text.list", "stuff");
		return mimeData;
	}

	bool QueueManagerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
	{
		Q_UNUSED(column);
		if (action == Qt::IgnoreAction)
			return true;

		if (!data->hasFormat("application/vnd.text.list"))
			return false;

		int begin_row = row;
		if (row != -1)
		{
			begin_row = row;
		}
		else if (parent.isValid())
		{
			begin_row = parent.row();
		}
		else
		{
			moveBottom(dragged_items.front(), dragged_items.count());
			return true;
		}
		
		int from = dragged_items.front();
		int count = dragged_items.count();
		if (from < begin_row)
		{
			while (from < begin_row)
			{
				for (int i = count - 1;i >= 0;i--)
					swapItems(from + i, from + i + 1);
				from++;
			}
		}
		else
		{
			while (from > begin_row)
			{
				for (int i = 0;i < count;i++)
					swapItems(from + i, from + i - 1);
				from--;
			}
		}
		

		updatePriorities();
		// reorder the queue
		qman->orderQueue();
		reset();
		return true;
	}

	bool QueueManagerModel::removeRows(int row, int count, const QModelIndex & parent)
	{
		Q_UNUSED(parent);
		beginInsertRows(QModelIndex(), row, row + count - 1);
		endInsertRows();
		return true;
	}

	bool QueueManagerModel::insertRows(int row, int count, const QModelIndex & parent)
	{
		Q_UNUSED(parent);
		beginInsertRows(QModelIndex(), row, row + count - 1);
		endInsertRows();
		return true;
	}

	void QueueManagerModel::moveUp(int row, int count)
	{
		if (row <= 0 || row > qman->count())
			return;

		for (int i = 0;i < count;i++)
		{
			swapItems(row + i, row + i - 1);
		}
		
		updatePriorities();
		//dumpQueue();
		// reorder the queue
		qman->orderQueue();
		reset();
	}

	void QueueManagerModel::moveDown(int row, int count)
	{
		if (row < 0 || row >= qman->count() - 1)
			return;

		for (int i = count - 1;i >= 0;i--)
		{
			swapItems(row + i, row + i + 1);
		}

		updatePriorities();
		//dumpQueue();
		// reorder the queue
		qman->orderQueue();
		reset();
	}

	void QueueManagerModel::moveTop(int row, int count)
	{
		if (row < 0 || row >= qman->count())
			return;

		while (row > 0)
		{
			for (int i = 0;i < count;i++)
			{
				swapItems(row + i, row + i - 1);
			}
			row--;
		}
		
		updatePriorities();
		//dumpQueue();
		// reorder the queue
		qman->orderQueue();
		reset();
	}

	void QueueManagerModel::moveBottom(int row, int count)
	{
		if (row < 0 || row >= qman->count())
			return;

		while (row + count < queue.count())
		{
			for (int i = count - 1;i >= 0;i--)
			{
				swapItems(row + i, row + i + 1);
			}
			row++;
		}
		
		updatePriorities();
		//dumpQueue();
		// reorder the queue
		qman->orderQueue();
		reset();
	}
	
	void QueueManagerModel::dumpQueue()
	{
		int idx = 0;
		foreach (const Item & item, queue)
		{
			Out(SYS_GEN|LOG_DEBUG) << "Item " << idx << ": " << item.tc->getDisplayName() << " " << item.tc->getPriority() << endl;
			idx++;
		}
	}
	
	void QueueManagerModel::updatePriorities()
	{
		int idx = queue.size();
		for (QList<Item>::iterator i = queue.begin(); i != queue.end(); i++)
			i->tc->setPriority(idx--);
	}

	void QueueManagerModel::update()
	{
		TimeStamp now = bt::CurrentTime();
		int r = 0;
		for (QList<Item>::iterator i = queue.begin(); i != queue.end(); i++)
		{
			bt::TorrentInterface* tc = i->tc;
			if (!tc->getStats().running)
			{
				if (i->stalled_time != -1)
				{
					i->stalled_time = -1;
					emit dataChanged(createIndex(r, 3), createIndex(r, 3));
				}
			}
			else
			{
				Int64 stalled_time = 0;
				if (tc->getStats().completed)
					stalled_time = (now - tc->getStats().last_upload_activity_time) / 1000;
				else
					stalled_time = (now - tc->getStats().last_download_activity_time) / 1000;

				if (i->stalled_time != stalled_time)
				{
					i->stalled_time = stalled_time;
					emit dataChanged(createIndex(r, 3), createIndex(r, 3));
				}
			}
			r++;
		}
	}


	QModelIndex QueueManagerModel::find(const QString& text)
	{
		search_text = text;
		if (text.isEmpty())
		{
			reset();
			return QModelIndex();
		}

		int idx = 0;
		foreach (const Item & i, queue)
		{
			bt::TorrentInterface* tc = i.tc;
			if (tc->getDisplayName().contains(text, Qt::CaseInsensitive))
			{
				reset();
				return index(idx, 0);
			}
			idx++;
		}

		reset();
		return QModelIndex();
	}

	bool QueueManagerModel::visible(const bt::TorrentInterface* tc)
	{
		if (!show_uploads && tc->getStats().completed)
			return false;

		if (!show_downloads && !tc->getStats().completed)
			return false;

		if (!show_not_queud && !tc->isAllowedToStart())
			return false;

		return true;
	}

	void QueueManagerModel::swapItems(int a, int b)
	{
		if (a < 0 || a >= queue.count() || b < 0 || b >= queue.count())
			return;
		
		queue.swap(a, b);
	}


}

#include "queuemanagermodel.moc"
