/**************************************************************************
* Otter Browser: Web browser controlled by the user, not vice-versa.
* Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
*
* 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 "TransfersContentsWidget.h"
#include "../../../core/ThemesManager.h"
#include "../../../core/TransfersManager.h"
#include "../../../core/Utils.h"
#include "../../../ui/Menu.h"

#include "ui_TransfersContentsWidget.h"

#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QtMath>
#include <QtGui/QClipboard>
#include <QtGui/QKeyEvent>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressBar>

namespace Otter
{

ProgressBarDelegate::ProgressBarDelegate(QObject *parent) : ItemDelegate(parent)
{
}

void ProgressBarDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
	QProgressBar *progressBar(qobject_cast<QProgressBar*>(editor));

	if (progressBar)
	{
		const Transfer::TransferState state(static_cast<Transfer::TransferState>(index.data(TransfersContentsWidget::StateRole).toInt()));
		const bool isIndeterminate(index.data(TransfersContentsWidget::BytesTotalRole).toLongLong() <= 0);
		const bool hasError(state == Transfer::UnknownState || state == Transfer::ErrorState);

		progressBar->setRange(0, ((isIndeterminate && !hasError) ? 0 : 100));
		progressBar->setValue(isIndeterminate ? (hasError ? 0 : -1) : index.data(TransfersContentsWidget::ProgressRole).toInt());
		progressBar->setFormat(isIndeterminate ? tr("Unknown") : QLatin1String("%p%"));
	}
}

QWidget* ProgressBarDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
	Q_UNUSED(option)

	QProgressBar *editor(new QProgressBar(parent));
	editor->setAlignment(Qt::AlignCenter);

	setEditorData(editor, index);

	return editor;
}

TransfersContentsWidget::TransfersContentsWidget(const QVariantMap &parameters, Window *window, QWidget *parent) : ContentsWidget(parameters, window, parent),
	m_model(new QStandardItemModel(this)),
	m_isLoading(false),
	m_ui(new Ui::TransfersContentsWidget)
{
	m_ui->setupUi(this);

	m_model->setHorizontalHeaderLabels({tr("Status"), tr("Filename"), tr("Size"), tr("Progress"), tr("Time"), tr("Speed"), tr("Started"), tr("Finished")});
	m_model->setHeaderData(0, Qt::Horizontal, QSize(28, 0), Qt::SizeHintRole);
	m_model->setHeaderData(1, Qt::Horizontal, QSize(500, 0), Qt::SizeHintRole);
	m_model->setHeaderData(2, Qt::Horizontal, QSize(150, 0), Qt::SizeHintRole);
	m_model->setHeaderData(3, Qt::Horizontal, QSize(250, 0), Qt::SizeHintRole);
	m_model->setHeaderData(4, Qt::Horizontal, QSize(150, 0), Qt::SizeHintRole);
	m_model->setHeaderData(5, Qt::Horizontal, QSize(150, 0), Qt::SizeHintRole);

	m_ui->transfersViewWidget->setModel(m_model);
	m_ui->transfersViewWidget->setItemDelegateForColumn(3, new ProgressBarDelegate(this));
	m_ui->transfersViewWidget->setSortRoleMapping({{0, StateRole}, {2, BytesTotalRole}, {3, ProgressRole}, {6, TimeStartedRole}, {7, TimeFinishedRole}});
	m_ui->transfersViewWidget->installEventFilter(this);
	m_ui->stopResumeButton->setIcon(ThemesManager::createIcon(QLatin1String("task-reject")));
	m_ui->redownloadButton->setIcon(ThemesManager::createIcon(QLatin1String("view-refresh")));
	m_ui->downloadLineEditWidget->installEventFilter(this);

	const QVector<Transfer*> transfers(TransfersManager::getTransfers());

	for (int i = 0; i < transfers.count(); ++i)
	{
		handleTransferAdded(transfers.at(i));
	}

	if (isSidebarPanel())
	{
		m_ui->detailsWidget->hide();
	}

	connect(TransfersManager::getInstance(), &TransfersManager::transferStarted, this, &TransfersContentsWidget::handleTransferAdded);
	connect(TransfersManager::getInstance(), &TransfersManager::transferRemoved, this, &TransfersContentsWidget::handleTransferRemoved);
	connect(TransfersManager::getInstance(), &TransfersManager::transferChanged, this, &TransfersContentsWidget::handleTransferChanged);
	connect(m_model, &QStandardItemModel::modelReset, this, &TransfersContentsWidget::updateActions);
	connect(m_ui->transfersViewWidget, &ItemViewWidget::doubleClicked, this, &TransfersContentsWidget::openTransfer);
	connect(m_ui->transfersViewWidget, &ItemViewWidget::customContextMenuRequested, this, &TransfersContentsWidget::showContextMenu);
	connect(m_ui->transfersViewWidget, &ItemViewWidget::needsActionsUpdate, this, &TransfersContentsWidget::updateActions);
	connect(m_ui->downloadLineEditWidget, &LineEditWidget::returnPressed, this, &TransfersContentsWidget::startQuickTransfer);
	connect(m_ui->stopResumeButton, &QPushButton::clicked, this, &TransfersContentsWidget::stopResumeTransfer);
	connect(m_ui->redownloadButton, &QPushButton::clicked, this, &TransfersContentsWidget::redownloadTransfer);
}

TransfersContentsWidget::~TransfersContentsWidget()
{
	delete m_ui;
}

void TransfersContentsWidget::changeEvent(QEvent *event)
{
	ContentsWidget::changeEvent(event);

	if (event->type() == QEvent::LanguageChange)
	{
		m_ui->retranslateUi(this);

		m_model->setHorizontalHeaderLabels({tr("Status"), tr("Filename"), tr("Size"), tr("Progress"), tr("Time"), tr("Speed"), tr("Started"), tr("Finished")});

		updateActions();
	}
}

void TransfersContentsWidget::removeTransfer()
{
	Transfer *transfer(getTransfer(m_ui->transfersViewWidget->currentIndex()));

	if (transfer)
	{
		if (transfer->getState() == Transfer::RunningState && QMessageBox::warning(this, tr("Warning"), tr("This file is still being downloaded.\nDo you really want to remove it?"), (QMessageBox::Yes | QMessageBox::Cancel)) == QMessageBox::Cancel)
		{
			return;
		}

		m_model->removeRow(m_ui->transfersViewWidget->currentIndex().row());

		TransfersManager::removeTransfer(transfer);
	}
}

void TransfersContentsWidget::openTransfer()
{
	const Transfer *transfer(getTransfer(m_ui->transfersViewWidget->currentIndex()));

	if (transfer)
	{
		transfer->openTarget();
	}
}

void TransfersContentsWidget::openTransferFolder()
{
	const Transfer *transfer(getTransfer(m_ui->transfersViewWidget->currentIndex()));

	if (transfer)
	{
		Utils::runApplication({}, QUrl::fromLocalFile(QFileInfo(transfer->getTarget()).dir().canonicalPath()));
	}
}

void TransfersContentsWidget::copyTransferInformation()
{
	const QModelIndex index(m_ui->transfersViewWidget->currentIndex());

	if (index.isValid() && index.data(Qt::ToolTipRole).isValid())
	{
		QApplication::clipboard()->setText(index.data(Qt::ToolTipRole).toString().remove(QRegularExpression(QLatin1String("<[^>]*>"))));
	}
}

void TransfersContentsWidget::stopResumeTransfer()
{
	Transfer *transfer(getTransfer(m_ui->transfersViewWidget->getCurrentIndex()));

	if (transfer)
	{
		if (transfer->getState() == Transfer::RunningState)
		{
			transfer->stop();
		}
		else if (transfer->getState() == Transfer::ErrorState)
		{
			transfer->resume();
		}

		updateActions();
	}
}

void TransfersContentsWidget::redownloadTransfer()
{
	Transfer *transfer(getTransfer(m_ui->transfersViewWidget->getCurrentIndex()));

	if (transfer)
	{
		transfer->restart();
	}
}

void TransfersContentsWidget::startQuickTransfer()
{
	TransfersManager::startTransfer(m_ui->downloadLineEditWidget->text(), {}, (Transfer::CanNotifyOption | Transfer::IsQuickTransferOption | (SessionsManager::isPrivate() ? Transfer::IsPrivateOption : Transfer::NoOption)));

	m_ui->downloadLineEditWidget->clear();
}

void TransfersContentsWidget::clearFinishedTransfers()
{
	TransfersManager::clearTransfers();
}

void TransfersContentsWidget::handleTransferAdded(Transfer *transfer)
{
	QList<QStandardItem*> items({new QStandardItem(), new QStandardItem(QFileInfo(transfer->getTarget()).fileName())});
	items[0]->setData(qVariantFromValue(static_cast<void*>(transfer)), InstanceRole);
	items[0]->setFlags(items[0]->flags() | Qt::ItemNeverHasChildren);
	items[1]->setFlags(items[1]->flags() | Qt::ItemNeverHasChildren);

	for (int i = 2; i < m_model->columnCount(); ++i)
	{
		QStandardItem *item(new QStandardItem());
		item->setFlags(item->flags() | Qt::ItemNeverHasChildren);

		items.append(item);
	}

	m_model->appendRow(items);

	m_ui->transfersViewWidget->openPersistentEditor(items[3]->index());

	handleTransferChanged(transfer);
}

void TransfersContentsWidget::handleTransferChanged(Transfer *transfer)
{
	const int row(findTransfer(transfer));

	if (row < 0)
	{
		return;
	}

	QIcon icon;
	const bool isIndeterminate(transfer->getBytesTotal() <= 0);

	switch (transfer->getState())
	{
		case Transfer::RunningState:
			icon = ThemesManager::createIcon(QLatin1String("task-ongoing"));

			break;
		case Transfer::FinishedState:
			icon = ThemesManager::createIcon(QLatin1String("task-complete"));

			break;
		default:
			icon = ThemesManager::createIcon(QLatin1String("task-reject"));

			break;
	}

	const QString toolTip(tr("<div style=\"white-space:pre;\">Source: %1\nTarget: %2\nSize: %3\nDownloaded: %4\nProgress: %5</div>").arg(transfer->getSource().toDisplayString().toHtmlEscaped()).arg(transfer->getTarget().toHtmlEscaped()).arg(isIndeterminate ? tr("Unknown") : Utils::formatUnit(transfer->getBytesTotal(), false, 1, true)).arg(Utils::formatUnit(transfer->getBytesReceived(), false, 1, true)).arg(isIndeterminate ? tr("Unknown") : QStringLiteral("%1%").arg(Utils::calculatePercent(transfer->getBytesReceived(), transfer->getBytesTotal()), 0, 'f', 1)));

	for (int i = 0; i < m_model->columnCount(); ++i)
	{
		const QModelIndex &index(m_model->index(row, i));

		m_model->setData(index, toolTip, Qt::ToolTipRole);

		switch (i)
		{
			case 0:
				m_model->setData(index, icon, Qt::DecorationRole);
				m_model->setData(index, transfer->getState(), StateRole);

				break;
			case 1:
				m_model->setData(index, QFileInfo(transfer->getTarget()).fileName(), Qt::DisplayRole);

				break;
			case 2:
				m_model->setData(index, Utils::formatUnit(transfer->getBytesTotal(), false, 1), Qt::DisplayRole);
				m_model->setData(index, transfer->getBytesTotal(), BytesTotalRole);

				break;
			case 3:
				m_model->setData(index, transfer->getBytesReceived(), BytesReceivedRole);
				m_model->setData(index, transfer->getBytesTotal(), BytesTotalRole);
				m_model->setData(index, ((transfer->getBytesTotal() > 0) ? qFloor(Utils::calculatePercent(transfer->getBytesReceived(), transfer->getBytesTotal())) : -1), ProgressRole);
				m_model->setData(index, transfer->getState(), StateRole);

				break;
			case 4:
				m_model->setData(index, ((isIndeterminate || transfer->getRemainingTime() <= 0) ? QString() : Utils::formatElapsedTime(transfer->getRemainingTime())), Qt::DisplayRole);

				break;
			case 5:
				m_model->setData(index, ((transfer->getState() == Transfer::RunningState) ? Utils::formatUnit(transfer->getSpeed(), true, 1) : QString()), Qt::DisplayRole);

				break;
			case 6:
				m_model->setData(index, Utils::formatDateTime(transfer->getTimeStarted()), Qt::DisplayRole);
				m_model->setData(index, transfer->getTimeStarted(), TimeStartedRole);

				break;
			case 7:
				m_model->setData(index, Utils::formatDateTime(transfer->getTimeFinished()), Qt::DisplayRole);
				m_model->setData(index, transfer->getTimeFinished(), TimeFinishedRole);

				break;
			default:
				break;
		}
	}

	if (m_ui->transfersViewWidget->selectionModel()->hasSelection())
	{
		updateActions();
	}

	const bool isRunning(transfer && transfer->getState() == Transfer::RunningState);

	if (isRunning != m_isLoading)
	{
		if (isRunning)
		{
			m_isLoading = true;

			emit loadingStateChanged(WebWidget::OngoingLoadingState);
		}
		else
		{
			const QVector<Transfer*> transfers(TransfersManager::getTransfers());
			bool hasRunning(false);

			for (int i = 0; i < transfers.count(); ++i)
			{
				if (transfers.at(i) && transfers.at(i)->getState() == Transfer::RunningState)
				{
					hasRunning = true;

					break;
				}
			}

			if (!hasRunning)
			{
				m_isLoading = false;

				emit loadingStateChanged(WebWidget::FinishedLoadingState);
			}
		}
	}
}

void TransfersContentsWidget::handleTransferRemoved(Transfer *transfer)
{
	const int row(findTransfer(transfer));

	if (row >= 0)
	{
		m_model->removeRow(row);
	}
}

void TransfersContentsWidget::showContextMenu(const QPoint &position)
{
	const Transfer *transfer(getTransfer(m_ui->transfersViewWidget->indexAt(position)));
	QMenu menu(this);

	if (transfer)
	{
		const bool canOpen(QFile::exists(transfer->getTarget()));
		QAction *openAction(menu.addAction(tr("Open")));
		openAction->setEnabled(canOpen);

		Menu *openWithMenu(new Menu(Menu::OpenInApplicationMenuRole, this));
		openWithMenu->setEnabled(canOpen);
		openWithMenu->setExecutor(ActionExecutor::Object(this, this));
		openWithMenu->setActionParameters({{QLatin1String("url"), transfer->getTarget()}});
		openWithMenu->setMenuOptions({{QLatin1String("mimeType"), transfer->getMimeType().name()}});

		menu.addMenu(openWithMenu);

		QAction *openFolderAction(menu.addAction(tr("Open Folder")));
		openFolderAction->setEnabled(canOpen || QFileInfo(transfer->getTarget()).dir().exists());

		menu.addSeparator();

		QAction *stopResumeAction(menu.addAction((transfer->getState() == Transfer::ErrorState) ? tr("Resume") : tr("Stop")));
		stopResumeAction->setEnabled(transfer->getState() == Transfer::RunningState || transfer->getState() == Transfer::ErrorState);

		connect(menu.addAction(tr("Redownload")), &QAction::triggered, this, &TransfersContentsWidget::redownloadTransfer);

		menu.addSeparator();

		connect(menu.addAction(tr("Copy Transfer Information")), &QAction::triggered, this, &TransfersContentsWidget::copyTransferInformation);

		menu.addSeparator();

		connect(menu.addAction(tr("Remove")), &QAction::triggered, this, &TransfersContentsWidget::removeTransfer);
		connect(openAction, &QAction::triggered, this, &TransfersContentsWidget::openTransfer);
		connect(openFolderAction, &QAction::triggered, this, &TransfersContentsWidget::openTransferFolder);
		connect(stopResumeAction, &QAction::triggered, this, &TransfersContentsWidget::stopResumeTransfer);
	}

	const QVector<Transfer*> transfers(TransfersManager::getTransfers());
	int finishedTransfers(0);

	for (int i = 0; i < transfers.count(); ++i)
	{
		if (transfers.at(i)->getState() == Transfer::FinishedState)
		{
			++finishedTransfers;
		}
	}

	QAction *clearFinishedAction(menu.addAction(tr("Clear Finished Transfers")));
	clearFinishedAction->setEnabled(finishedTransfers > 0);

	connect(clearFinishedAction, &QAction::triggered, this, &TransfersContentsWidget::clearFinishedTransfers);

	menu.exec(m_ui->transfersViewWidget->mapToGlobal(position));
}

void TransfersContentsWidget::updateActions()
{
	const Transfer *transfer(getTransfer(m_ui->transfersViewWidget->getCurrentIndex()));

	if (transfer && transfer->getState() == Transfer::ErrorState)
	{
		m_ui->stopResumeButton->setText(tr("Resume"));
		m_ui->stopResumeButton->setIcon(ThemesManager::createIcon(QLatin1String("task-ongoing")));
	}
	else
	{
		m_ui->stopResumeButton->setText(tr("Stop"));
		m_ui->stopResumeButton->setIcon(ThemesManager::createIcon(QLatin1String("task-reject")));
	}

	m_ui->stopResumeButton->setEnabled(transfer && (transfer->getState() == Transfer::RunningState || transfer->getState() == Transfer::ErrorState));
	m_ui->redownloadButton->setEnabled(transfer);

	if (transfer)
	{
		const bool isIndeterminate(transfer->getBytesTotal() <= 0);

		m_ui->sourceLabelWidget->setText(transfer->getSource().toDisplayString());
		m_ui->sourceLabelWidget->setUrl(transfer->getSource());
		m_ui->targetLabelWidget->setText(transfer->getTarget());
		m_ui->targetLabelWidget->setUrl(QUrl(transfer->getTarget()));
		m_ui->sizeLabelWidget->setText(isIndeterminate ? tr("Unknown") : Utils::formatUnit(transfer->getBytesTotal(), false, 1, true));
		m_ui->downloadedLabelWidget->setText(Utils::formatUnit(transfer->getBytesReceived(), false, 1, true));
		m_ui->progressLabelWidget->setText(isIndeterminate ? tr("Unknown") : QStringLiteral("%1%").arg(Utils::calculatePercent(transfer->getBytesReceived(), transfer->getBytesTotal()), 0, 'f', 1));
	}
	else
	{
		m_ui->sourceLabelWidget->clear();
		m_ui->targetLabelWidget->clear();
		m_ui->sizeLabelWidget->clear();
		m_ui->downloadedLabelWidget->clear();
		m_ui->progressLabelWidget->clear();
	}

	emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::EditingCategory});
}

void TransfersContentsWidget::print(QPrinter *printer)
{
	m_ui->transfersViewWidget->render(printer);
}

void TransfersContentsWidget::triggerAction(int identifier, const QVariantMap &parameters)
{
	switch (identifier)
	{
		case ActionsManager::CopyAction:
			if (m_ui->transfersViewWidget->hasFocus() && m_ui->transfersViewWidget->currentIndex().isValid())
			{
				copyTransferInformation();
			}
			else
			{
				const QWidget *widget(focusWidget());

				if (widget->metaObject()->className() == QLatin1String("Otter::TextLabelWidget"))
				{
					const TextLabelWidget *label(qobject_cast<const TextLabelWidget*>(widget));

					if (label)
					{
						label->copy();
					}
				}
			}

			break;
		case ActionsManager::DeleteAction:
			removeTransfer();

			break;
		case ActionsManager::FindAction:
		case ActionsManager::QuickFindAction:
			m_ui->downloadLineEditWidget->setFocus();
			m_ui->downloadLineEditWidget->selectAll();

			break;
		case ActionsManager::ActivateContentAction:
			m_ui->transfersViewWidget->setFocus();

			break;
		default:
			ContentsWidget::triggerAction(identifier, parameters);

			break;
	}
}

Transfer* TransfersContentsWidget::getTransfer(const QModelIndex &index) const
{
	if (index.isValid())
	{
		return static_cast<Transfer*>(index.sibling(index.row(), 0).data(InstanceRole).value<void*>());
	}

	return nullptr;
}

QString TransfersContentsWidget::getTitle() const
{
	return tr("Downloads");
}

QLatin1String TransfersContentsWidget::getType() const
{
	return QLatin1String("transfers");
}

QUrl TransfersContentsWidget::getUrl() const
{
	return QUrl(QLatin1String("about:transfers"));
}

QIcon TransfersContentsWidget::getIcon() const
{
	return ThemesManager::createIcon(QLatin1String("transfers"), false);
}

ActionsManager::ActionDefinition::State TransfersContentsWidget::getActionState(int identifier, const QVariantMap &parameters) const
{
	switch (identifier)
	{
		case ActionsManager::CopyAction:
		case ActionsManager::DeleteAction:
			{
				ActionsManager::ActionDefinition::State state(ActionsManager::getActionDefinition(identifier).getDefaultState());
				state.isEnabled = (getTransfer(m_ui->transfersViewWidget->getCurrentIndex()) != nullptr);

				return state;
			}

			break;
		default:
			break;
	}

	return ContentsWidget::getActionState(identifier, parameters);
}

WebWidget::LoadingState TransfersContentsWidget::getLoadingState() const
{
	return (m_isLoading ? WebWidget::OngoingLoadingState : WebWidget::FinishedLoadingState);
}

int TransfersContentsWidget::findTransfer(Transfer *transfer) const
{
	if (!transfer)
	{
		return -1;
	}

	for (int i = 0; i < m_model->rowCount(); ++i)
	{
		if (transfer == getTransfer(m_model->index(i, 0)))
		{
			return i;
		}
	}

	return -1;
}

bool TransfersContentsWidget::eventFilter(QObject *object, QEvent *event)
{
	if (object == m_ui->transfersViewWidget && event->type() == QEvent::KeyPress)
	{
		const QKeyEvent *keyEvent(static_cast<QKeyEvent*>(event));

		if (keyEvent)
		{
			switch (keyEvent->key())
			{
				case Qt::Key_Delete:
					removeTransfer();

					return true;
				case Qt::Key_Enter:
				case Qt::Key_Return:
					openTransfer();

					return true;
				default:
					break;
			}
		}
	}
	else if (object == m_ui->downloadLineEditWidget && event->type() == QEvent::KeyPress)
	{
		const QKeyEvent *keyEvent(static_cast<QKeyEvent*>(event));

		if (keyEvent->key() == Qt::Key_Escape)
		{
			m_ui->downloadLineEditWidget->clear();
		}
	}

	return ContentsWidget::eventFilter(object, event);
}

}
