#include "tabs/downloads-tab.h"
#include <QCheckBox>
#include <QDir>
#include <QElapsedTimer>
#include <QFileDialog>
#include <QFileInfo>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#include <QSoundEffect>
#include <QTimer>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
	#include <QStorageInfo>
#endif
#include <QtMath>
#include <ui_downloads-tab.h>
#include <algorithm>
#include "batch/add-group-window.h"
#include "batch/add-unique-window.h"
#include "batch/batch-window.h"
#include "batch-download-image.h"
#include "commands/commands.h"
#include "download-group-table-model.h"
#include "download-image-table-model.h"
#include "downloader/download-query-group.h"
#include "downloader/download-query-image.h"
#include "downloader/download-query-loader.h"
#include "downloader/image-downloader.h"
#include "filename/filename.h"
#include "full-width-drop-proxy-style.h"
#include "functions.h"
#include "helpers.h"
#include "loader/pack-loader.h"
#include "logger.h"
#include "main-window.h"
#include "models/page.h"
#include "models/profile.h"
#include "monitoring/monitor-manager.h"
#include "progress-bar-delegate.h"


DownloadsTab::DownloadsTab(Profile *profile, DownloadQueue *downloadQueue, MainWindow *parent)
	: QWidget(parent), ui(new Ui::DownloadsTab), m_profile(profile), m_settings(profile->getSettings()), m_downloadQueue(downloadQueue), m_parent(parent), m_getAll(false), m_progressDialog(nullptr), m_batchAutomaticRetries(0), m_finishedSoundEffect(this)
{
	ui->setupUi(this);

	m_groupBatchsModel = new DownloadGroupTableModel(m_profile, m_groupBatchs, this);
	ui->tableBatchGroups->setModel(m_groupBatchsModel);
	ui->tableBatchGroups->setItemDelegate(new ProgressBarDelegate(m_groupBatchsModel));
	ui->tableBatchGroups->setStyle(new FullWidthDropProxyStyle(ui->tableBatchGroups->style()));
	connect(m_groupBatchsModel, &DownloadGroupTableModel::dataChanged, this, &DownloadsTab::saveLinkListLater);

	m_batchsModel = new DownloadImageTableModel(m_batchs, this);
	ui->tableBatchUniques->setModel(m_batchsModel);

	ui->tableBatchGroups->loadGeometry(m_settings, "Downloads/Groups");
	ui->tableBatchUniques->loadGeometry(m_settings, "Downloads/Uniques", QList<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });

	QStringList splitterSizes = m_settings->value("batchSplitter", "100,100").toString().split(',');
	ui->splitter->setSizes({ splitterSizes[0].toInt(), splitterSizes[1].toInt() });

	auto *actionDeleteBatchGroups = new QShortcut(QKeySequence::Delete, ui->tableBatchGroups);
	actionDeleteBatchGroups->setContext(Qt::WidgetWithChildrenShortcut);
	connect(actionDeleteBatchGroups, &QShortcut::activated, this, &DownloadsTab::batchClearSelGroups);

	auto *actionDeleteBatchUniques = new QShortcut(QKeySequence::Delete, ui->tableBatchUniques);
	actionDeleteBatchUniques->setContext(Qt::WidgetWithChildrenShortcut);
	connect(actionDeleteBatchUniques, &QShortcut::activated, this, &DownloadsTab::batchClearSelUniques);

	connect(m_profile, &Profile::siteDeleted, this, &DownloadsTab::siteDeleted);

	m_saveLinkList = new QTimer(this);
	m_saveLinkList->setInterval(100);
	m_saveLinkList->setSingleShot(true);
	connect(m_saveLinkList, &QTimer::timeout, this, &DownloadsTab::saveLinkListDefault);

	m_finishedSoundEffect.setSource(QUrl(":/sounds/finished.wav"));
}

DownloadsTab::~DownloadsTab()
{
	close();
	delete ui;
}

void DownloadsTab::changeEvent(QEvent *event)
{
	// Automatically re-translate this tab on language change
	if (event->type() == QEvent::LanguageChange) {
		ui->retranslateUi(this);
	}

	QWidget::changeEvent(event);
}

void DownloadsTab::closeEvent(QCloseEvent *event)
{
	Q_UNUSED(event)

	ui->tableBatchGroups->saveGeometry(m_settings, "Downloads/Groups");
	ui->tableBatchUniques->saveGeometry(m_settings, "Downloads/Uniques");

	// Splitter
	QList<int> splitterSizesOrig = ui->splitter->sizes();
	QStringList splitterSizes;
	splitterSizes.reserve(splitterSizesOrig.count());
	for (int size : splitterSizesOrig) {
		splitterSizes.append(QString::number(size));
	}
	m_settings->setValue("batchSplitter", splitterSizes.join(","));
}

void DownloadsTab::batchDownloadsTableContextMenu(const QPoint &pos)
{
	auto index = ui->tableBatchGroups->indexAt(pos);
	if (!index.isValid()) {
		return;
	}

	auto *menu = new QMenu(this);
	menu->addAction(QIcon(":/images/icons/save.png"), tr("Download"), this, SLOT(batchSel()));
	menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy to monitors"), [this]() { batchConvert(); });
	menu->addSeparator();
	menu->addAction(QIcon(":/images/icons/arrow-top.png"), tr("Move to top"), this, SLOT(batchMoveToTop()));
	menu->addAction(QIcon(":/images/icons/arrow-up.png"), tr("Move up"), this, SLOT(batchMoveUp()));
	menu->addAction(QIcon(":/images/icons/arrow-down.png"), tr("Move down"), this, SLOT(batchMoveDown()));
	menu->addAction(QIcon(":/images/icons/arrow-bottom.png"), tr("Move to bottom"), this, SLOT(batchMoveToBottom()));
	if (!m_getAll) {
		menu->addSeparator();
		menu->addAction(QIcon(":/images/icons/remove.png"), tr("Remove"), this, SLOT(batchClearSelGroups()));
	}
	menu->exec(QCursor::pos());
}


void DownloadsTab::siteDeleted(Site *site)
{
	QList<int> batchRows;
	for (int i = 0; i < m_groupBatchs.count(); ++i) {
		const DownloadQueryGroup &batch = m_groupBatchs[i];
		if (batch.site == site) {
			batchRows.append(i);
		}
	}
	batchRemoveGroups(batchRows);

	QList<int> uniquesRows;
	for (int i = 0; i < m_batchs.count(); ++i) {
		const DownloadQueryImage &batch = m_batchs[i];
		if (batch.site == site) {
			uniquesRows.append(i);
		}
	}
	batchRemoveUniques(uniquesRows);
}

QSet<int> DownloadsTab::selectedRows(QTableView *table) const
{
	QSet<int> rows;
	for (const QModelIndex &index : table->selectionModel()->selection().indexes()) {
		const int row = index.row();
		if (!rows.contains(row)) {
			rows.insert(row);
		}
	}
	return rows;
}

void DownloadsTab::batchClear()
{
	// Don't do anything if there's nothing to be cleared
	if (m_groupBatchsModel->rowCount() == 0 && m_batchsModel->rowCount() == 0) {
		return;
	}

	// Confirm deletion
	auto response = QMessageBox::question(this, tr("Confirmation"), tr("Are you sure you want to clear your download list?"), QMessageBox::Yes | QMessageBox::No);
	if (response != QMessageBox::Yes) {
		return;
	}

	m_batchs.clear();
	m_batchsModel->cleared();

	m_groupBatchs.clear();
	m_groupBatchsModel->cleared();

	saveLinkListLater();
	updateGroupCount();
}
void DownloadsTab::batchClearSel()
{
	batchClearSelGroups();
	batchClearSelUniques();
}
void DownloadsTab::batchClearSelGroups()
{
	batchRemoveGroups(selectedRows(ui->tableBatchGroups).values());
}
void DownloadsTab::batchClearSelUniques()
{
	batchRemoveUniques(selectedRows(ui->tableBatchUniques).values());
}
void DownloadsTab::batchRemoveGroups(QList<int> rows)
{
	if (m_getAll) {
		return;
	}

	std::sort(rows.begin(), rows.end());

	int rem = 0;
	for (int i : qAsConst(rows)) {
		int pos = i - rem;
		m_groupBatchsModel->removed(pos);
		m_groupBatchs.removeAt(pos);
		rem++;
	}

	saveLinkListLater();
	updateGroupCount();
}
void DownloadsTab::batchRemoveUniques(QList<int> rows)
{
	if (m_getAll) {
		return;
	}

	std::sort(rows.begin(), rows.end());

	int rem = 0;
	for (int i : qAsConst(rows)) {
		int pos = i - rem;
		m_batchsModel->removed(pos);
		m_batchs.removeAt(pos);
		rem++;
	}

	saveLinkListLater();
	updateGroupCount();
}

void DownloadsTab::batchMove(int diff)
{
	QList<int> rows = selectedRows(ui->tableBatchGroups).values();
	if (rows.isEmpty()) {
		return;
	}

	if (diff > 0) {
		std::sort(rows.begin(), rows.end(), std::greater<>());
	} else {
		std::sort(rows.begin(), rows.end());
	}

	for (int i = 0; i < rows.count(); ++i) {
		const int sourceRow = rows[i];

		int destRow = qMin(qMax(i, sourceRow + diff), m_groupBatchsModel->rowCount() - i - 1);
		if (destRow == sourceRow) {
			return;
		}

		auto sourceBatch = m_groupBatchs.takeAt(sourceRow);
		m_groupBatchs.insert(destRow, sourceBatch);

		for (int j = sourceRow; j != destRow; (diff < 0 ? --j : ++j)) {
			m_groupBatchsModel->changed(j);
		}
	}

	QItemSelection selection;
	for (const auto &index : ui->tableBatchGroups->selectionModel()->selection().indexes()) {
		int destRow = qMin(qMax(0, index.row() + diff), m_groupBatchsModel->rowCount() - 1);
		QModelIndex shifted = m_groupBatchsModel->index(destRow, index.column());
		selection.select(shifted, shifted);
	}

	auto *selectionModel = new QItemSelectionModel(m_groupBatchsModel, this);
	selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
	ui->tableBatchGroups->setSelectionModel(selectionModel);

	saveLinkListLater();
}
void DownloadsTab::batchMoveToTop()
{
	batchMove(-99999);
}
void DownloadsTab::batchMoveUp()
{
	batchMove(-1);
}
void DownloadsTab::batchMoveDown()
{
	batchMove(1);
}
void DownloadsTab::batchMoveToBottom()
{
	batchMove(99999);
}

void DownloadsTab::addGroup()
{
	auto *addGroupWindow = new AddGroupWindow(m_parent->getSelectedSiteOrDefault(), m_profile, this);
	connect(addGroupWindow, &AddGroupWindow::sendData, this, &DownloadsTab::batchAddGroup);
	addGroupWindow->show();
}
void DownloadsTab::addUnique()
{
	auto *addUniqueWindow = new AddUniqueWindow(m_parent->getSelectedSiteOrDefault(), m_profile, &m_batchs, this);
	connect(addUniqueWindow, SIGNAL(sendData(DownloadQueryImage)), this, SLOT(batchAddUnique(DownloadQueryImage)));
	addUniqueWindow->show();
}


void DownloadsTab::batchAddGroup(const DownloadQueryGroup &values)
{
	// Ignore downloads already present in the list
	if (m_groupBatchs.contains(values)) {
		return;
	}

	m_groupBatchs.append(values);
	m_groupBatchsModel->inserted(m_groupBatchs.count() - 1);

	saveLinkListLater();
	updateGroupCount();
}
void DownloadsTab::updateGroupCount()
{
	int groups = 0;
	for (const auto &batch : m_groupBatchs) {
		groups += batch.total;
	}
	ui->labelGroups->setText(tr("Groups (%1/%2)").arg(m_groupBatchsModel->rowCount()).arg(groups));
}
void DownloadsTab::batchAddUnique(const DownloadQueryImage &query, bool save)
{
	// Ignore downloads already present in the list
	if (m_batchs.contains(query)) {
		return;
	}

	log(QStringLiteral("Adding single file: `%1`").arg(query.image->fileUrl().toString()), Logger::Info);

	m_batchs.append(query);
	m_batchsModel->inserted(m_batchs.count() - 1);

	if (save) {
		saveLinkListLater();
	}
}

QTableWidgetItem *DownloadsTab::addTableItem(QTableWidget *table, int row, int col, const QString &text)
{
	auto *item = new QTableWidgetItem(text);
	item->setToolTip(text);

	table->setItem(row, col, item);
	return item;
}


void DownloadsTab::saveFile()
{
	QString lastDir = m_settings->value("linksLastDir", "").toString();
	QString save = QFileDialog::getSaveFileName(this, tr("Save link list"), QDir::toNativeSeparators(lastDir), tr("Imageboard-Grabber links (*.igl)"));
	if (save.isEmpty()) {
		return;
	}

	save = QDir::toNativeSeparators(save);
	m_settings->setValue("linksLastDir", save.section(QDir::separator(), 0, -2));

	if (saveLinkList(save, false)) {
		QMessageBox::information(this, tr("Save link list"), tr("Link list saved successfully!"));
	} else {
		QMessageBox::critical(this, tr("Save link list"), tr("Error opening file."));
	}
}
void DownloadsTab::saveLinkListLater()
{
	m_saveLinkList->start();
}
bool DownloadsTab::saveLinkListDefault()
{
	return saveLinkList(m_profile->getPath() + "/restore.igl");
}
bool DownloadsTab::saveLinkList(const QString &filename, bool saveProgress)
{
	return DownloadQueryLoader::save(filename, m_batchs, m_groupBatchs, saveProgress);
}

void DownloadsTab::loadFile()
{
	QStringList files = QFileDialog::getOpenFileNames(this, tr("Load link list"), QString(), tr("Imageboard-Grabber links (*.igl)"));
	if (files.isEmpty()) {
		return;
	}

	bool allOk = true;
	for (const QString &file : files) {
		allOk = allOk && loadLinkList(file);
	}

	if (allOk) {
		QMessageBox::information(this, tr("Load link list"), tr("Link list loaded successfully!"));
	} else {
		QMessageBox::critical(this, tr("Load link list"), tr("Error opening file."));
	}
}
bool DownloadsTab::loadLinkList(const QString &filename)
{
	QList<DownloadQueryImage> newBatchs;
	QList<DownloadQueryGroup> newGroupBatchs;

	if (!DownloadQueryLoader::load(filename, newBatchs, newGroupBatchs, m_profile)) {
		return false;
	}

	log(tr("Loading %n download(s)", "", newBatchs.count() + newGroupBatchs.count()), Logger::Info);

	for (const auto &queryImage : qAsConst(newBatchs)) {
		batchAddUnique(queryImage, false);
	}
	for (const auto &queryGroup : qAsConst(newGroupBatchs)) {
		m_groupBatchs.append(queryGroup);
		m_groupBatchsModel->inserted(m_groupBatchs.count() - 1);
	}
	updateGroupCount();

	return true;
}

QIcon &DownloadsTab::getIcon(const QString &path)
{
	if (!m_icons.contains(path)) {
		m_icons.insert(path, QIcon(path));
	}

	return m_icons[path];
}

bool DownloadsTab::isDownloading() const
{
	return m_getAll;
}

void DownloadsTab::batchConvert()
{
	QSet<int> rows = selectedRows(ui->tableBatchGroups);
	for (const int row : rows) {
		const DownloadQueryGroup &group = m_groupBatchs[row];
		Monitor monitor(m_settings, { group.site }, group.query, group.postFiltering);
		m_profile->monitorManager()->add(monitor);
	}
}


/* Batch download */
void DownloadsTab::batchSel()
{
	getAll(false);
}
void DownloadsTab::getAll(bool all)
{
	// Initial checks
	if (m_getAll) {
		log(QStringLiteral("Batch download start cancelled because another one is already running."), Logger::Warning);
		return;
	}
	if (m_settings->value("Save/path").toString().isEmpty()) {
		error(this, tr("You did not specify a save folder!"));
		return;
	}
	if (m_settings->value("Save/filename").toString().isEmpty()) {
		error(this, tr("You did not specify a filename!"));
		return;
	}
	log(QStringLiteral("Batch download started."), Logger::Info);

	if (m_progressDialog == nullptr) {
		m_progressDialog = new BatchWindow(m_profile->getSettings(), this);
		connect(m_progressDialog, &BatchWindow::paused, this, &DownloadsTab::getAllPause);
		connect(m_progressDialog, &BatchWindow::rejected, this, &DownloadsTab::getAllCancel);
		connect(m_progressDialog, &BatchWindow::skipped, this, &DownloadsTab::getAllSkip);
	}

	// Reinitialize variables
	m_getAll = true;
	ui->widgetDownloadButtons->setDisabled(m_getAll);
	m_getAllDownloaded = 0;
	m_getAllExists = 0;
	m_getAllIgnored = 0;
	m_getAllIgnoredPre = 0;
	m_getAll404s = 0;
	m_getAllErrors = 0;
	m_getAllSkipped = 0;
	m_getAllResumed = 0;
	m_getAllRemaining.clear();
	m_getAllFailed.clear();
	m_getAllDownloading.clear();
	m_getAllSkippedImages.clear();
	m_batchPending.clear();
	m_waitingPackLoaders.clear();
	m_currentPackLoader = nullptr;
	m_batchUniqueDownloading.clear();

	if (!all) {
		QSet<int> tdl = selectedRows(ui->tableBatchUniques);
		for (const int row : tdl) {
			const DownloadQueryImage &batch = m_batchs[row];
			BatchDownloadImage d;
			d.image = batch.image;
			d.queryImage = &batch;

			m_getAllRemaining.append(d);
			m_batchUniqueDownloading.insert(row);
		}
	} else {
		for (int j = 0; j < m_batchs.count(); ++j) {
			const DownloadQueryImage &batch = m_batchs[j];
			if (batch.image->fileUrl().isEmpty()) {
				log(QStringLiteral("No file URL provided in image download query"), Logger::Warning);
				continue;
			}

			BatchDownloadImage d;
			d.image = batch.image;
			d.queryImage = &batch;

			m_getAllRemaining.append(d);
			m_batchUniqueDownloading.insert(j);
		}
	}
	m_getAllLimit = m_batchs.size();

	for (const auto &batch : m_groupBatchs) {
		m_groupBatchsModel->setStatus(batch, 0);
	}
	m_profile->getCommands().before();
	m_batchDownloading.clear();

	QSet<int> toDownload = selectedRows(ui->tableBatchGroups);

	int resumeCount = 0;
	if (all || !toDownload.isEmpty()) {
		for (int j = 0; j < m_groupBatchs.count(); ++j) {
			if (all || toDownload.contains(j)) {
				DownloadQueryGroup b = m_groupBatchs[j];
				m_batchPending.insert(j, b);
				m_getAllLimit += b.total;
				m_batchDownloading.insert(j);

				if (b.progressVal > 0 && !b.progressFinished) {
					resumeCount += b.progressVal;
				}
			}
		}
	}

	// Try to resume downloads that were stopped in the middle
	bool resume = resumeCount > 0;
	if (resume) {
		int resumeAnswer = QMessageBox::question(this, "", tr("Some downloads were started but not finished. Do you want to continue from where you left off?"));
		if (resumeAnswer == QMessageBox::Yes) {
			m_getAllResumed = resumeCount;
		} else {
			resume = false;
		}
	}
	for (const int b : m_batchDownloading) {
		if (m_groupBatchs[b].progressFinished || !resume) {
			m_groupBatchs[b].progressVal = 0;
			m_batchPending[b].progressVal = 0;
		}
		m_groupBatchs[b].progressFinished = false;
		m_batchPending[b].progressFinished = false;
	}

	// Confirm before downloading possibly more than 10,000 images
	bool tooBig = false;
	if (m_getAllLimit > 10000 && m_settings->value("confirm_big_downloads", true).toBool()) {
		QMessageBox msgBox(this);
		msgBox.setText(tr("You are going to download up to %1 images, which can take a long time and space on your computer. Are you sure you want to proceed?").arg(m_getAllLimit));
		msgBox.setIcon(QMessageBox::Warning);
		QCheckBox dontAskCheckBox(tr("Don't ask me again"));
		dontAskCheckBox.setCheckable(true);
		#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0))
			msgBox.setCheckBox(&dontAskCheckBox);
		#else
			msgBox.addButton(&dontShowCheckBox, QMessageBox::ResetRole);
		#endif
		msgBox.addButton(QMessageBox::Yes);
		msgBox.addButton(QMessageBox::Cancel);
		msgBox.setDefaultButton(QMessageBox::Cancel);
		int response = msgBox.exec();

		// Don't close on "cancel"
		if (response != QMessageBox::Yes) {
			tooBig = true;
		}

		// Remember checkbox
		else if (dontAskCheckBox.checkState() == Qt::Checked) {
			m_settings->setValue("confirm_big_downloads", false);
		}
	}

	if (tooBig || (m_batchPending.isEmpty() && m_getAllRemaining.isEmpty())) {
		log(tooBig ? QStringLiteral("Batch download too big") : QStringLiteral("Nothing to download"), Logger::Info);
		m_getAll = false;
		ui->widgetDownloadButtons->setEnabled(true);
		return;
	}

	m_progressDialog->show();
	getAllLogin();
}

void DownloadsTab::getAllLogin()
{
	m_progressDialog->clear();
	m_progressDialog->setText(tr("Logging in, please wait..."));

	m_getAllLogins.clear();
	for (auto it = m_batchPending.constBegin(); it != m_batchPending.constEnd(); ++it) {
		Site *site = it.value().site;
		if (!m_getAllLogins.contains(site)) {
			m_getAllLogins.append(site);
		}
	}

	if (m_getAllLogins.empty()) {
		getAllFinishedLogins();
		return;
	}

	m_progressDialog->setCurrentValue(0);
	m_progressDialog->setCurrentMax(m_getAllLogins.count());

	for (Site *site : m_getAllLogins) {
		connect(site, &Site::loggedIn, this, &DownloadsTab::getAllFinishedLogin, Qt::QueuedConnection);
		site->login();
	}
}
void DownloadsTab::getAllFinishedLogin(Site *site, Site::LoginResult result)
{
	Q_UNUSED(result)

	if (m_getAllLogins.empty()) {
		return;
	}

	m_progressDialog->setCurrentValue(m_progressDialog->currentValue() + 1);
	m_getAllLogins.removeAll(site);

	if (m_getAllLogins.empty()) {
		getAllFinishedLogins();
	}
}

void DownloadsTab::getAllFinishedLogins()
{
	bool usePacking = m_settings->value("packing_enable", true).toBool();
	int imagesPerPack = m_settings->value("packing_size", 1000).toInt();

	int total = 0;
	for (auto it = m_batchPending.constBegin(); it != m_batchPending.constEnd(); ++it) {
		DownloadQueryGroup b = it.value();
		total += b.total;

		auto *packLoader = new PackLoader(m_profile, b, usePacking ? imagesPerPack : -1, this);
		connect(packLoader, &PackLoader::finishedPage, this, &DownloadsTab::getAllFinishedPage);
		m_waitingPackLoaders.enqueue(packLoader);
	}

	m_getAllImagesCount = total;
	getNextPack();
}

void DownloadsTab::getNextPack()
{
	// If the current pack loader is not finished
	if (m_currentPackLoader != nullptr && m_currentPackLoader->hasNext()) {
		getAllGetPages();
	}
	// If there are pending packs
	else if (!m_waitingPackLoaders.isEmpty()) {
		if (m_currentPackLoader != nullptr) {
			m_currentPackLoader->deleteLater();
		}

		m_currentPackLoader = m_waitingPackLoaders.dequeue();
		m_currentPackLoader->start();

		getAllGetPages();
	}
	// Only images to download
	else {
		m_batchAutomaticRetries = m_settings->value("Save/automaticretries", 0).toInt();
		getAllImages();
	}
}

void DownloadsTab::getAllGetPages()
{
	m_progressDialog->clearImages();
	m_progressDialog->setText(tr("Downloading pages, please wait..."));

	const auto &query = m_currentPackLoader->query();
	const int images = m_currentPackLoader->nextPackSize();
	const int pages = qMax(1, qCeil(static_cast<qreal>(images) / query.perpage));

	m_progressDialog->setCurrentValue(0);
	m_progressDialog->setCurrentMax(pages);
	m_batchCurrentPackSize = images;

	getAllFinishedImages(m_currentPackLoader->next());
}

/**
 * Called when a page have been loaded and parsed.
 *
 * @param page The loaded page
 */
void DownloadsTab::getAllFinishedPage(Page *page)
{
	Q_UNUSED(page)

	m_progressDialog->setCurrentValue(m_progressDialog->currentValue() + 1);
}

/**
 * Called when a page have been loaded and parsed.
 *
 * @param images The images results on this page
 */
void DownloadsTab::getAllFinishedImages(const QList<QSharedPointer<Image>> &images)
{
	int row = -1;
	for (auto it = m_batchPending.constBegin(); it != m_batchPending.constEnd(); ++it) {
		if (it.value() == m_currentPackLoader->query()) {
			row = it.key();
			break;
		}
	}
	if (row < 0) {
		log("Images received from unknown batch", Logger::Error);
		return;
	}

	for (const auto &img : images) {
		BatchDownloadImage d;
		d.image = img;
		d.queryGroup = &m_batchPending[row];
		m_getAllRemaining.append(d);
	}

	// Ignore for aborted/resumed calls (partial packs)
	if (m_getAll && !images.isEmpty()) {
		// Update image to take into account unlisted images
		int unlisted = m_batchCurrentPackSize - images.count();
		m_getAllImagesCount -= unlisted;
	}

	// Stop here if we're paused
	if (m_getAll) {
		m_batchAutomaticRetries = m_settings->value("Save/automaticretries", 0).toInt();
		getAllImages();
	}
}

/**
 * Called when all pages have been loaded and parsed from all sources.
 */
void DownloadsTab::getAllImages()
{
	log(QStringLiteral("All images' urls have been received (%1).").arg(m_getAllRemaining.count()), Logger::Info);

	// We add the images to the download dialog
	m_progressDialog->clearImages();
	m_progressDialog->setText(tr("Preparing images, please wait..."));
	m_progressDialog->setCount(m_getAllRemaining.count());
	for (const BatchDownloadImage &download : qAsConst(m_getAllRemaining)) {
		const int siteId = download.siteId(m_groupBatchs);
		QSharedPointer<Image> img = download.image;

		// We add the image
		m_progressDialog->addImage(img->url(), siteId, img->fileSize());
		connect(img.data(), &Image::urlChanged, m_progressDialog, &BatchWindow::imageUrlChanged);
		connect(img.data(), &Image::urlChanged, this, &DownloadsTab::imageUrlChanged);
	}

	// Set some values on the batch window
	m_progressDialog->updateColumns();
	m_progressDialog->setText(tr("Downloading images..."));
	m_progressDialog->setCurrentValue(0);
	m_progressDialog->setCurrentMax(m_getAllRemaining.count());
	m_progressDialog->setTotalValue(m_getAllDownloaded + m_getAllExists + m_getAllIgnored + m_getAllErrors + m_getAllResumed);
	m_progressDialog->setTotalMax(m_getAllImagesCount);

	// We start the simultaneous downloads
	int count = qMax(1, qMin(m_settings->value("Save/simultaneous").toInt(), 10));
	m_getAllCurrentlyProcessing.storeRelaxed(count);
	for (int i = 0; i < count; i++) {
		_getAll();
	}
}

void DownloadsTab::_getAll()
{
	// We quit as soon as the user cancels
	if (m_progressDialog->cancelled()) {
		return;
	}

	// If there are still images do download
	if (!m_getAllRemaining.empty()) {
		// We take the first image to download
		BatchDownloadImage download = m_getAllRemaining.takeFirst();
		m_getAllDownloading.append(download);

		int siteId = download.siteId(m_groupBatchs);
		getAllGetImage(download, siteId);
	}
	// When the batch download finishes
	else if (m_getAllCurrentlyProcessing.fetchAndAddRelaxed(-1) == 1 && m_getAll) {
		getAllFinished();
	}
}

void DownloadsTab::getAllImageOk(const BatchDownloadImage &download, int siteId, bool retry)
{
	m_downloadTime.remove(download.image->url());
	m_downloadTimeLast.remove(download.image->url());

	if (retry) {
		return;
	}

	m_progressDialog->setCurrentValue(m_progressDialog->currentValue() + 1);
	m_progressDialog->setTotalValue(m_getAllDownloaded + m_getAllExists + m_getAllIgnored + m_getAllErrors + m_getAllResumed);

	if (siteId >= 0) {
		int row = getRowForSite(siteId);
		m_groupBatchs[row].progressVal++;
		m_batchPending[row].progressVal++;
		m_groupBatchsModel->changed(row);

		if (m_groupBatchs[row].progressVal >= m_groupBatchs[row].total) {
			m_groupBatchsModel->setStatus(m_groupBatchs[row], 2);
		}
	}

	m_getAllDownloading.removeAll(download);
	QCoreApplication::processEvents();
	QTimer::singleShot(0, this, SLOT(_getAll()));
}

void DownloadsTab::imageUrlChanged(const QUrl &before, const QUrl &after)
{
	m_downloadTimeLast.insert(after, m_downloadTimeLast[before]);
	m_downloadTimeLast.remove(before);
	m_downloadTime.insert(after, m_downloadTime[before]);
	m_downloadTime.remove(before);
}
void DownloadsTab::getAllProgress(const QSharedPointer<Image> &img, qint64 bytesReceived, qint64 bytesTotal)
{
	const QUrl url = img->url();

	if (!m_downloadTimeLast.contains(url)) {
		return;
	}

	if (m_downloadTimeLast[url].elapsed() >= 200 && bytesReceived > 0) {
		m_downloadTimeLast[url].restart();
		const int elapsed = m_downloadTime[url].elapsed();
		const double speed = elapsed != 0 ? (bytesReceived * 1000) / elapsed : 0;
		m_progressDialog->speedImage(url, speed);
	}

	int percent = 0;
	if (bytesTotal > 0) {
		const qreal pct = static_cast<qreal>(bytesReceived) / static_cast<qreal>(bytesTotal);
		percent = qFloor(pct * 100);
	}

	m_progressDialog->sizeImage(url, bytesTotal);
	m_progressDialog->statusImage(url, percent);
}

int DownloadsTab::getRowForSite(int siteId)
{
	return siteId - 1;
}

void DownloadsTab::getAllGetImage(const BatchDownloadImage &download, int siteId)
{
	QSharedPointer<Image> img = download.image;

	// If there is already a downloader for this image, we simply restart it
	if (m_getAllImageDownloaders.contains(img)) {
		m_getAllImageDownloaders[img]->save();
		return;
	}

	// Stop here if we're paused
	if (!m_getAll) {
		return;
	}

	// Row
	int row = getRowForSite(siteId);

	// Path
	QString filename = download.query()->filename;
	QString path = download.query()->path;
	if (siteId >= 0) {
		m_groupBatchsModel->setStatus(m_groupBatchs[row], 1);
	}

	// Track download progress
	m_progressDialog->loadingImage(img->url());
	m_downloadTime.insert(img->url(), QElapsedTimer());
	m_downloadTime[img->url()].start();
	m_downloadTimeLast.insert(img->url(), QElapsedTimer());
	m_downloadTimeLast[img->url()].start();

	// Start loading and saving image
	log(QStringLiteral("Loading image from `%1` %2").arg(img->fileUrl().toString()).arg(m_getAllDownloading.size()), Logger::Info);
	int count = m_getAllDownloaded + m_getAllExists + m_getAllIgnored + m_getAllErrors + m_getAllResumed + 1;
	bool getBlacklisted = download.queryGroup == nullptr || download.queryGroup->getBlacklisted;
	auto *imgDownloader = new ImageDownloader(m_profile, img, filename, path, count, true, false, this);
	if (!getBlacklisted) {
		imgDownloader->setBlacklist(&m_profile->getBlacklist());
	}
	connect(imgDownloader, &ImageDownloader::saved, this, &DownloadsTab::getAllGetImageSaved, Qt::UniqueConnection);
	connect(imgDownloader, &ImageDownloader::downloadProgress, this, &DownloadsTab::getAllProgress, Qt::UniqueConnection);
	m_getAllImageDownloaders[img] = imgDownloader;
	imgDownloader->save();
}

void DownloadsTab::getAllGetImageSaved(const QSharedPointer<Image> &img, QList<ImageSaveResult> result)
{
	// Delete ImageDownloader to prevent leaks
	m_getAllImageDownloaders[img]->deleteLater();
	m_getAllImageDownloaders.remove(img);

	// Find related download query
	const BatchDownloadImage *downloadPtr = nullptr;
	for (const BatchDownloadImage &i : qAsConst(m_getAllDownloading)) {
		if (i.image == img) {
			downloadPtr = &i;
		}
	}
	if (downloadPtr == nullptr) {
		log(QStringLiteral("Saved image signal received from unknown sender"), Logger::Error);
		return;
	}
	BatchDownloadImage download = *downloadPtr;

	// Save error count to compare it later on
	bool diskError = false;
	const auto res = result.first().result;

	// Disk writing errors
	for (const ImageSaveResult &re : result) {
		if (re.result == Image::SaveResult::Error) {
			diskError = true;

			if (!m_progressDialog->isPaused()) {
				m_progressDialog->pause();

				bool isDriveFull;
				QString drive;
				#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
					QDir destinationDir = QFileInfo(re.path).absoluteDir();
					QStorageInfo storage(destinationDir);
					isDriveFull = storage.isValid() && (storage.bytesAvailable() < img->fileSize() || storage.bytesAvailable() < 20 * 1024 * 1024);
					QString rootPath = storage.rootPath();
					#ifdef Q_OS_WIN
						drive = QStringLiteral("%1 (%2)").arg(storage.name(), rootPath.endsWith("/") ? rootPath.left(rootPath.length() - 1) : rootPath);
					#else
						drive = rootPath;
					#endif
				#else
					isDriveFull = false;
				#endif

				QString msg;
				if (isDriveFull) {
					msg = tr("Not enough space on the destination drive \"%1\".\nPlease free some space before resuming the download.").arg(drive);
				} else {
					msg = tr("An error occured saving the image.\n%1\nPlease solve the issue before resuming the download.").arg(re.path);
				}
				QMessageBox::critical(m_progressDialog, tr("Error"), msg);
			}
		}
	}

	if (res == Image::SaveResult::NetworkError || res == Image::SaveResult::DetailsLoadError) {
		m_getAllErrors++;
		m_getAllFailed.append(download);
	} else if (res == Image::SaveResult::NotFound) {
		m_getAll404s++;
	} else if (res == Image::SaveResult::AlreadyExistsDisk) {
		m_getAllExists++;
	} else if (res == Image::SaveResult::Blacklisted || res == Image::SaveResult::AlreadyExistsMd5 || res == Image::SaveResult::AlreadyExistsDeletedMd5) {
		m_getAllIgnored++;
	} else if (!diskError) {
		m_getAllDownloaded++;
	}

	m_progressDialog->loadedImage(img->url(), res);

	int siteId = download.siteId(m_groupBatchs);
	getAllImageOk(download, siteId, diskError);
}

void DownloadsTab::getAllCancel()
{
	log(QStringLiteral("Cancelling downloads..."), Logger::Info);
	m_progressDialog->cancel();
	if (m_currentPackLoader != nullptr) {
		m_currentPackLoader->abort();
	}
	for (auto it = m_getAllImageDownloaders.constBegin(); it != m_getAllImageDownloaders.constEnd(); ++it) {
		it.value()->abort();
	}
	m_getAll = false;
	ui->widgetDownloadButtons->setEnabled(true);
	DONE();
}

void DownloadsTab::getAllSkip()
{
	log(QStringLiteral("Skipping downloads..."), Logger::Info);

	int count = m_getAllDownloading.count();
	for (auto it = m_getAllImageDownloaders.constBegin(); it != m_getAllImageDownloaders.constEnd(); ++it) {
		it.value()->abort();
	}
	m_getAllSkippedImages.append(m_getAllDownloading);
	m_getAllDownloading.clear();

	m_getAllSkipped += count;
	m_progressDialog->setTotalValue(m_getAllDownloaded + m_getAllExists + m_getAllIgnored + m_getAllErrors + m_getAllResumed);
	m_getAllCurrentlyProcessing.storeRelaxed(count);
	for (int i = 0; i < count; ++i) {
		_getAll();
	}

	DONE();
}

void DownloadsTab::getAllFinished()
{
	if (!m_waitingPackLoaders.isEmpty() || (m_currentPackLoader != nullptr && m_currentPackLoader->hasNext())) {
		getNextPack();
		return;
	}

	log(QStringLiteral("Images download finished."), Logger::Info);
	m_progressDialog->setTotalValue(m_progressDialog->totalMax());

	// Delete objects
	if (m_currentPackLoader != nullptr) {
		m_currentPackLoader->deleteLater();
		m_currentPackLoader = nullptr;
	}

	// Retry in case of error
	int failedCount = m_getAllErrors + m_getAllSkipped;
	if (failedCount > 0) {
		int response;
		if (m_batchAutomaticRetries > 0) {
			m_batchAutomaticRetries--;
			response = QMessageBox::Yes;
		} else {
			// Trigger minor end actions on retry
			switch (m_progressDialog->endAction())
			{
				case 2: openTray();                   	break;
				case 4: m_finishedSoundEffect.play();	break;
			}
			activateWindow();

			int totalCount = m_getAllDownloaded + m_getAllIgnored + m_getAllExists + m_getAll404s + m_getAllErrors + m_getAllSkipped + m_getAllResumed;
			response = QMessageBox::question(this, tr("Getting images"), tr("Errors occured during the images download. Do you want to restart the download of those images? (%1/%2)").arg(failedCount).arg(totalCount), QMessageBox::Yes | QMessageBox::No);
		}

		if (response == QMessageBox::Yes) {
			m_getAll = true;
			m_progressDialog->clear();
			m_getAllRemaining.clear();
			m_getAllRemaining.append(m_getAllFailed);
			m_getAllRemaining.append(m_getAllSkippedImages);
			m_getAllImagesCount = m_getAllRemaining.count();
			m_getAllFailed.clear();
			m_getAllSkippedImages.clear();
			m_getAllDownloaded = 0;
			m_getAllExists = 0;
			m_getAllIgnored = 0;
			m_getAllIgnoredPre = 0;
			m_getAll404s = 0;
			m_getAllErrors = 0;
			m_getAllSkipped = 0;
			m_getAllResumed = 0;
			m_progressDialog->show();
			getAllImages();
			return;
		}
	}

	// Download result
	QMessageBox::information(
		this,
		tr("Getting images"),
		QString(
			tr("%n file(s) downloaded successfully.", "", m_getAllDownloaded) + "\r\n" +
			tr("%n file(s) ignored.", "", m_getAllIgnored + m_getAllIgnoredPre) + "\r\n" +
			tr("%n file(s) already existing.", "", m_getAllExists) + "\r\n" +
			tr("%n file(s) not found on the server.", "", m_getAll404s) + "\r\n" +
			tr("%n file(s) skipped.", "", m_getAllSkipped) + "\r\n" +
			tr("%n file(s) skipped from a previous download.", "", m_getAllResumed) + "\r\n" +
			tr("%n error(s).", "", m_getAllErrors)
		)
	);

	// Mark downloads as finished
	for (const int b : m_batchDownloading) {
		m_groupBatchs[b].progressFinished = true;
	}

	// Final action
	switch (m_progressDialog->endAction())
	{
		case 1: m_progressDialog->close();      break;
		case 2: openTray();                     break;
		case 3: m_parent->saveFolder();         break;
		case 4: m_finishedSoundEffect.play();	break;
		case 5: shutDown();                     break;
	}
	activateWindow();
	m_getAll = false;

	// Remove after download and retries are finished
	if (m_progressDialog->endRemove()) {
		batchRemoveGroups(m_batchDownloading.values());
		batchRemoveUniques(m_batchUniqueDownloading.values());
	}

	// End of batch download
	m_profile->getCommands().after();
	ui->widgetDownloadButtons->setEnabled(true);
	log(QStringLiteral("Batch download finished"), Logger::Info);
}

void DownloadsTab::getAllPause()
{
	if (m_progressDialog->isPaused()) {
		log(QStringLiteral("Pausing downloads..."), Logger::Info);
		m_getAll = false;
		if (m_currentPackLoader != nullptr) {
			m_currentPackLoader->abort();
		}
		for (auto it = m_getAllImageDownloaders.constBegin(); it != m_getAllImageDownloaders.constEnd(); ++it) {
			it.value()->abort();
		}
	} else {
		log(QStringLiteral("Recovery of downloads..."), Logger::Info);
		m_getAll = true;
		if (m_getAllDownloading.isEmpty()) {
			getAllFinishedImages(QList<QSharedPointer<Image>>());
		} else {
			for (const auto &download : qAsConst(m_getAllDownloading)) {
				getAllGetImage(download, download.siteId(m_groupBatchs));
			}
		}
	}
	DONE();
}
