/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2012  Georg Rudoy
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 **********************************************************************/

#include "packagesdelegate.h"
#include <QPainter>
#include <QApplication>
#include <QTreeView>
#include <QAction>
#include <QToolButton>
#include <QTimer>
#include <QScrollBar>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QMainWindow>
#include <QAbstractProxyModel>
#include <QtDebug>
#include <interfaces/core/icoreproxy.h>
#include "packagesmodel.h"
#include "core.h"
#include "pendingmanager.h"
#include "delegatebuttongroup.h"

namespace LeechCraft
{
namespace LackMan
{
	const int PackagesDelegate::CPadding = 5;
	const int PackagesDelegate::CIconSize = 32;
	const int PackagesDelegate::CActionsSize = 32;
	const int PackagesDelegate::CTitleSizeDelta = 2;

	PackagesDelegate::PackagesDelegate (QTreeView *parent)
	: QStyledItemDelegate (parent)
	, Viewport_ (parent->viewport ())
	, Model_ (parent->model ())
	{
		connect (parent->verticalScrollBar (),
				SIGNAL (valueChanged (int)),
				this,
				SLOT (invalidateWidgetPositions ()),
				Qt::QueuedConnection);
		connect (Model_,
				SIGNAL (rowsRemoved (const QModelIndex&, int, int)),
				this,
				SLOT (hideOverflousActions (const QModelIndex&, int, int)),
				Qt::QueuedConnection);
		connect (&Core::Instance (),
				SIGNAL (packageRowActionFinished (int)),
				this,
				SLOT (handleRowActionFinished (int)),
				Qt::QueuedConnection);

		connect (Core::Instance ().GetPendingManager (),
				SIGNAL (packageUpdateToggled (int, bool)),
				this,
				SLOT (handlePackageUpdateToggled (int, bool)));
	}

	void PackagesDelegate::paint (QPainter *painter,
			const QStyleOptionViewItem& option, const QModelIndex& index) const
	{
		QStyleOptionViewItemV4 opt (option);
		QStyle *style = opt.widget ?
				opt.widget->style () :
				QApplication::style ();
		const auto& fontMetrics = opt.widget ?
				opt.widget->fontMetrics () :
				QApplication::fontMetrics ();

		const QRect& r = option.rect;
		const bool ltr = (painter->layoutDirection () == Qt::LeftToRight);
		const bool selected = option.state & QStyle::State_Selected;

		const auto& title = index.data (Qt::DisplayRole).toString ();
		const auto& shortDescr = index.data (PackagesModel::PMRShortDescription).toString ();
		const auto& version = index.data (PackagesModel::PMRVersion).toString ();
		const auto& fgColor = selected ?
				option.palette.color (QPalette::HighlightedText) :
				option.palette.color (QPalette::Text);

		const auto& icon = index.data (Qt::DecorationRole).value<QIcon> ();

		QStyleOptionViewItem titleOption (option);
		titleOption.font.setBold (true);

		QPixmap pixmap (option.rect.size ());
		pixmap.fill (Qt::transparent);
		QPainter p (&pixmap);
		p.translate (-option.rect.topLeft ());

		if (selected ||
				(option.state & QStyle::State_MouseOver))
			style->drawPrimitive (QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);

		int textShift = 2 * CPadding + CIconSize;
		int leftPos = r.left () + (ltr ? textShift : 0);
		int textWidth = r.width () - textShift - CPadding;
		int shiftFromTop = r.top () + CPadding;
		int textHeight = TextHeight (option);

		p.setPen (fgColor);
		p.setFont (titleOption.font);
		p.drawText (leftPos, shiftFromTop,
				textWidth, TitleHeight (option),
				Qt::AlignBottom | Qt::AlignLeft, title);
		int titleWidth = QFontMetrics (titleOption.font).width (title) + CPadding;

		QFont tagsFont = option.font;
		tagsFont.setItalic (true);

		p.setFont (tagsFont);
		p.drawText (leftPos + titleWidth, shiftFromTop,
				textWidth - titleWidth, textHeight,
				Qt::AlignBottom | Qt::AlignLeft, version);
		titleWidth += QFontMetrics (titleOption.font).width (version) + CPadding;

		shiftFromTop += TitleHeight (option);

		p.setFont (option.font);
		p.drawText (leftPos, shiftFromTop,
				textWidth, textHeight,
				Qt::AlignTop | Qt::AlignLeft,
				fontMetrics.elidedText (shortDescr,
						option.textElideMode, textWidth));

		shiftFromTop += textHeight;

		style->drawPrimitive (QStyle::PE_FrameGroupBox, &option, &p);

		p.end ();

		painter->drawPixmap (option.rect, pixmap);

		icon.paint (painter,
				ltr ?
						r.left () + CPadding :
						r.left () + r.width () - CPadding - CIconSize,
				r.top () + CPadding,
				CIconSize, CIconSize,
				Qt::AlignCenter, QIcon::Normal);

		QWidget *layoutWidget = GetLayout (index);
		QPoint actionPos (r.width () - layoutWidget->width () - CPadding,
				r.top () + CPadding);
		if (layoutWidget->pos () != actionPos)
			layoutWidget->move (actionPos);
		if (!layoutWidget->isVisible ())
			layoutWidget->show ();
	}

	QSize PackagesDelegate::sizeHint (const QStyleOptionViewItem& option,
			const QModelIndex& index) const
	{
		QSize result = index.data (Qt::SizeHintRole).toSize ();

		// One padding from the top, one from the bottom, and
		// one between webview and install/remove actions (if
		// selected).
		result.rheight () = std::max (TitleHeight (option) + TextHeight (option), CActionsSize) +
				CPadding * 2;

		return result;
	}

	int PackagesDelegate::TitleHeight (const QStyleOptionViewItem& option) const
	{
		QFont boldFont = option.font;
		boldFont.setBold (true);
		return QFontInfo (boldFont).pixelSize () + CPadding;
	}

	int PackagesDelegate::TextHeight (const QStyleOptionViewItem& option) const
	{
		return QFontInfo (option.font).pixelSize () + CPadding;
	}

	QToolButton* PackagesDelegate::GetInstallRemove (const QModelIndex& index) const
	{
		int row = index.row ();
		if (!Row2InstallRemove_.contains (row))
		{
			QAction *action = new QAction (Viewport_);
			action->setCheckable (true);
			action->setProperty ("Role", "InstallRemove");
			connect (action,
					SIGNAL (triggered ()),
					this,
					SLOT (handleAction ()));

			QToolButton *toolButton = new QToolButton ();
			toolButton->resize (CActionsSize, CActionsSize);
			toolButton->setDefaultAction (action);
			Row2InstallRemove_ [row] = toolButton;
		}

		QToolButton *button = Row2InstallRemove_ [row];

		bool installed = index.data (PackagesModel::PMRInstalled).toBool ();
		QString label;
		QString iconName;
		if (installed)
		{
			label = tr ("Remove");
			iconName = "list-remove";
		}
		else
		{
			label = tr ("Install");
			iconName = "list-add";
		}

		QAction *action = button->defaultAction ();
		WasInstalled_ [index] = installed;

		action->setText (label);
		action->setIcon (Core::Instance ().GetProxy ()->GetIcon (iconName));
		action->setData (index.data (PackagesModel::PMRPackageID));
		action->setProperty ("Installed", index.data (PackagesModel::PMRInstalled));

		return button;
	}

	QToolButton* PackagesDelegate::GetUpdate (const QModelIndex& index) const
	{
		int row = index.row ();
		if (!Row2Update_.contains (row))
		{
			QAction *action = new QAction (Core::Instance ()
						.GetProxy ()->GetIcon ("system-software-update"),
					tr ("Update"),
					Viewport_);
			action->setCheckable (true);
			action->setProperty ("Role", "Update");
			connect (action,
					SIGNAL (triggered ()),
					this,
					SLOT (handleAction ()));

			QToolButton *toolButton = new QToolButton ();
			toolButton->resize (CActionsSize, CActionsSize);
			toolButton->setDefaultAction (action);
			Row2Update_ [row] = toolButton;
		}

		QToolButton *button = Row2Update_ [row];
		QAction *action = button->defaultAction ();

		bool upgradable = index.data (PackagesModel::PMRUpgradable).toBool ();
		action->setEnabled (upgradable);
		action->setData (index.data (PackagesModel::PMRPackageID));

		WasUpgradable_ [index] = upgradable;

		return button;
	}

	QWidget* PackagesDelegate::GetLayout (const QModelIndex& index) const
	{
		if (!Row2Layout_.contains (index.row ()))
		{
			QToolButton *instRem = GetInstallRemove (index);
			QToolButton *update = GetUpdate (index);

			DelegateButtonGroup *group = new DelegateButtonGroup (Viewport_);
			group->AddButton (instRem);
			group->AddButton (update);

			QWidget *result = new QWidget (Viewport_);

			QHBoxLayout *layout = new QHBoxLayout (result);
			layout->addWidget (instRem);
			layout->addWidget (update);

			result->setLayout (layout);

			Row2Layout_ [index.row ()] = result;
		}
		else
		{
			GetInstallRemove (index);
			GetUpdate (index);
		}

		return Row2Layout_ [index.row ()];
	}

	void PackagesDelegate::invalidateWidgetPositions ()
	{
		QTreeView *view = qobject_cast<QTreeView*> (parent ());
		QAbstractItemModel *model = view->model ();
		for (int i = 0, rows = model->rowCount ();
				i < rows; ++i)
			emit sizeHintChanged (model->index (i, 0));
	}

	void PackagesDelegate::hideOverflousActions (const QModelIndex&,
			int, int)
	{
		int rowCount = Model_->rowCount ();
		int thisCount = Row2Layout_.size ();
		for (int i = rowCount; i < thisCount; ++i)
			Row2Layout_ [i]->hide ();
	}

	void PackagesDelegate::handleAction ()
	{
		QAction *sAction = qobject_cast<QAction*> (sender ());
		if (!sAction)
		{
			qWarning () << Q_FUNC_INFO
					<< "sender is not an action"
					<< sender ();
			return;
		}

		int packageID = sAction->data ().toInt ();
		QString role = sAction->property ("Role").toString ();
		bool checked = sAction->isChecked ();

		try
		{
			if (role == "InstallRemove")
			{
				bool installed = sAction->property ("Installed").toBool ();
				Core::Instance ().GetPendingManager ()->ToggleInstallRemove (packageID, checked, installed);
			}
			else if (role == "Update")
				Core::Instance ().GetPendingManager ()->ToggleUpdate (packageID, checked);
			else
				qWarning () << Q_FUNC_INFO
						<< "unknown role"
						<< role;
		}
		catch (const std::exception& e)
		{
			qWarning () << Q_FUNC_INFO
					<< e.what ();
			QMessageBox::critical (Core::Instance ().GetProxy ()->GetMainWindow (),
					"LeechCraft",
					tr ("Unable to mark package, reverting.") + "<br />" + QString::fromUtf8 (e.what ()));

			sAction->setChecked (false);
		}
	}

	namespace
	{
		QAbstractItemModel* GetRealModel (QAbstractItemModel *source,
					QList<QAbstractProxyModel*> *proxyChain = 0)
		{
			QAbstractProxyModel *proxy = 0;
			while ((proxy = qobject_cast<QAbstractProxyModel*> (source)))
			{
				if (proxyChain)
					proxyChain->push_front (proxy);
				source = proxy->sourceModel ();
			}
			return source;
		}
	}

	void PackagesDelegate::handleRowActionFinished (int row)
	{
		// Front — deepest proxy, back — outermost.
		QList<QAbstractProxyModel*> proxyChain;
		QAbstractItemModel *realModel = GetRealModel (Model_, &proxyChain);

		QModelIndex index = realModel->index (row, 0);

		Q_FOREACH (QAbstractProxyModel *pm, proxyChain)
			index = pm->mapFromSource (index);

		row = index.row ();

		if (Row2InstallRemove_.contains (row))
		{
			Row2InstallRemove_ [row]->setChecked (false);
			Row2InstallRemove_ [row]->defaultAction ()->setChecked (false);
			Row2InstallRemove_ [row]->setChecked (false);
		}
		if (Row2Update_.contains (row))
			Row2Update_ [row]->defaultAction ()->setChecked (false);
	}

	void PackagesDelegate::handlePackageUpdateToggled (int id, bool enabled)
	{
		PackagesModel *realModel =
				qobject_cast<PackagesModel*> (GetRealModel (Model_));
		int row = realModel->GetRow (id);
		if (Row2Update_.contains (row))
			Row2Update_ [row]->defaultAction ()->setChecked (enabled);
	}
}
}
