/*
 * Cantata
 *
 * Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "coverdialog.h"
#include "apikeys.h"
#include "mpd-interface/mpdconnection.h"
#include "network/networkaccessmanager.h"
#include "settings.h"
#include "support/icon.h"
#include "support/messagebox.h"
#include "support/spinner.h"
#include "support/utils.h"
#include "widgets/icons.h"
#include "widgets/listview.h"
#include "widgets/messageoverlay.h"
#include <QAction>
#include <QApplication>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QFileDialog>
#include <QFileInfo>
#include <QFont>
#include <QFontMetrics>
#include <QJsonDocument>
#include <QLabel>
#include <QListWidget>
#include <QMenu>
#include <QMimeData>
#include <QPainter>
#include <QPixmap>
#include <QProgressBar>
#include <QScrollArea>
#include <QScrollBar>
#include <QSslSocket>
#include <QStyle>
#include <QTemporaryFile>
#include <QUrl>
#include <QUrlQuery>
#include <QVBoxLayout>
#include <QWheelEvent>
#include <QXmlStreamReader>

#include <QDebug>
#define DBUG \
	if (Covers::debugEnabled()) qWarning() << "CoverDialog" << __FUNCTION__

static int iCount = 0;
static const int constMaxTempFiles = 20;

static QImage cropImage(QImage img, bool isArtistOrComposer)
{
	if (isArtistOrComposer && img.width() != img.height()) {
		int size = qMin(img.width(), img.height());
		return img.copy((img.width() - size) / 2, 0, size, size);
	}
	return img;
}

int CoverDialog::instanceCount()
{
	return iCount;
}

// Only really want square-ish covers!
bool canUse(int w, int h)
{
	return w > 90 && h > 90 && (w == h || (h <= (w * 1.1) && w <= (h * 1.1)));
}

class CoverItem : public QListWidgetItem {
public:
	CoverItem(const QString& u, const QString& tu, const QImage& img, const QString& text, QListWidget* parent, int w = -1, int h = -1, int sz = -1)
		: QListWidgetItem(parent), imgUrl(u), thmbUrl(tu), list(parent)
	{
		setSizeHint(parent->gridSize());
		setTextAlignment(Qt::AlignHCenter | Qt::AlignTop);
		setToolTip(u);
		if (!img.isNull()) {
			setImage(img);
		}
		if (sz > 0) {
			setText(QObject::tr("%1\n%2 x %3 (%4)", "name\nwidth x height (file size)").arg(text).arg(w).arg(h).arg(Utils::formatByteSize(sz)));
		}
		else if (w > 0 && h > 0) {
			setText(QObject::tr("%1\n%2 x %3", "name\nwidth x height").arg(text).arg(w).arg(h));
		}
		else {
			setText(text);
		}
	}
	const QString& url() const { return imgUrl; }
	const QString& thumbUrl() const { return thmbUrl; }
	virtual bool isLocal() const { return false; }
	virtual bool isExisting() const { return false; }

protected:
	void setImage(const QImage& img)
	{
		int size = list && list->parentWidget() && qobject_cast<CoverDialog*>(list->parentWidget())
				? static_cast<CoverDialog*>(list->parentWidget())->imageSize()
				: 100;
		QPixmap pix = QPixmap::fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation));
		if (pix.width() < size || pix.height() < size) {
			QPixmap newPix(size, size);
			newPix.fill(Qt::transparent);
			QPainter p(&newPix);
			p.drawPixmap((size - pix.width()) / 2, (size - pix.height()) / 2, pix.width(), pix.height(), pix);
			p.end();
			pix = newPix;
		}
		setIcon(pix);
	}

protected:
	QString imgUrl;
	QString thmbUrl;
	QListWidget* list;
};

class ExistingCover : public CoverItem {
public:
	ExistingCover(const Covers::Image& i, QListWidget* parent)
		: CoverItem(i.fileName, QString(), i.img, QObject::tr("Current Cover"), parent, i.img.width(), i.img.height(), QFileInfo(i.fileName).size()), img(i.img)
	{
		QFont f(font());
		f.setBold((true));
		setFont(f);
	}
	bool isExisting() const override { return true; }
	const QImage& image() const { return img; }

private:
	QImage img;
};

class LocalCover : public CoverItem {
public:
	LocalCover(const QString& u, const QImage& i, QListWidget* parent)
		: CoverItem(u, QString(), i, Utils::getFile(u), parent, i.width(), i.height(), QFileInfo(u).size()), img(i) {}
	bool isLocal() const override { return true; }
	const QImage& image() const { return img; }

private:
	QImage img;
};

class LastFmCover : public CoverItem {
public:
	LastFmCover(const QString& u, const QString& tu, const QImage& img, QListWidget* parent)
		: CoverItem(u, tu, img, QLatin1String("Last.fm"), parent) {}
};

/*
class GoogleCover : public CoverItem
{
public:
    GoogleCover(const QString &u, const QString &tu, const QImage &img, int w, int h, int size, QListWidget *parent)
        : CoverItem(u, tu, img, QLatin1String("Google"), parent, w, h, size*1024) { }
};
*/

class CoverArtArchiveCover : public CoverItem {
public:
	CoverArtArchiveCover(const QString& u, const QString& tu, const QImage& img, QListWidget* parent)
		: CoverItem(u, tu, img, QObject::tr("CoverArt Archive"), parent) {}
};

class DeezerCover : public CoverItem {
public:
	DeezerCover(const QString& u, const QString& tu, const QImage& img, QListWidget* parent)
		: CoverItem(u, tu, img, QLatin1String("Deezer"), parent) {}
};

/*
class SpotifyCover : public CoverItem
{
public:
    SpotifyCover(const QString &u, const QString &tu, const QImage &img, QListWidget *parent)
        : CoverItem(u, tu, img, QLatin1String("Spotify"), parent, 640, 640)  { }
};
*/

class ITunesCover : public CoverItem {
public:
	ITunesCover(const QString& u, const QString& tu, const QImage& img, QListWidget* parent)
		: CoverItem(u, tu, img, QLatin1String("iTunes"), parent, 600, 600) {}
};

CoverPreview::CoverPreview(QWidget* p)
	: Dialog(p), zoom(1.0), imgW(0),
	  imgH(0)
{
	setButtons(Close);
	setWindowTitle(tr("Image"));
	QWidget* mw = new QWidget(this);
	QVBoxLayout* layout = new QVBoxLayout(mw);
	loadingLabel = new QLabel(tr("Downloading..."), mw);
	pbar = new QProgressBar(mw);
	imageLabel = new QLabel;
	pbar->setRange(0, 100);
	imageLabel->setBackgroundRole(QPalette::Base);
	imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
	imageLabel->setScaledContents(true);
	scrollArea = new QScrollArea(mw);
	scrollArea->setBackgroundRole(QPalette::Dark);
	scrollArea->setWidget(imageLabel);

	layout->addWidget(loadingLabel);
	layout->addWidget(pbar);
	layout->addWidget(scrollArea);
	layout->setContentsMargins(0, 0, 0, 0);
	setMainWidget(mw);
}

void CoverPreview::showImage(const QImage& img, const QString& u)
{
	if (u == url) {
		zoom = 1.0;
		url = u;
		loadingLabel->hide();
		pbar->hide();
		imageLabel->setPixmap(QPixmap::fromImage(img));
		imageLabel->adjustSize();
		scrollArea->show();
		QApplication::processEvents();
		adjustSize();
		QStyleOptionFrame opt;
		opt.initFrom(scrollArea);
		int fw = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, scrollArea);
		if (fw < 0) {
			fw = 2;
		}
		fw *= 2;
		QRect desktop = qApp->primaryScreen()->availableGeometry();
		int maxWidth = desktop.width() * 0.75;
		int maxHeight = desktop.height() * 0.75;
		int lrPad = width() - mainWidget()->width();
		int tbPad = height() - mainWidget()->height();
		imgW = img.width();
		imgH = img.height();
		resize(lrPad + qMax(100, qMin(maxWidth, imgW + fw)), tbPad + qMax(100, qMin(maxHeight, imgH + fw)));
		setWindowTitle(tr("Image (%1 x %2 %3%)", "Image (width x height zoom%)").arg(imgW).arg(imgH).arg(zoom * 100));
		show();
	}
}

void CoverPreview::downloading(const QString& u)
{
	url = u;
	if (!url.isEmpty()) {
		loadingLabel->show();
		scrollArea->hide();
		pbar->show();
		pbar->setValue(0);
		int spacing = Utils::layoutSpacing(this);
		QApplication::processEvents();
		adjustSize();
		resize(qMin(200, loadingLabel->width() + 32), loadingLabel->height() + pbar->height() + (3 * spacing));
		show();
	}
}

void CoverPreview::progress(qint64 rx, qint64 total)
{
	pbar->setValue((int)(((rx * 100.0) / (total * 1.0)) + 0.5));
}

void CoverPreview::scaleImage(int adjust)
{
	double newZoom = zoom + (adjust * 0.25);

	if (newZoom < 0.25 || newZoom > 4.0 || (fabs(newZoom - zoom) < 0.01)) {
		return;
	}
	zoom = newZoom;
	imageLabel->resize(zoom * CANTATA_GET_LABEL_PIXMAP(imageLabel).size());
	setWindowTitle(tr("Image (%1 x %2 %3%)", "Image (width x height zoom%)").arg(imgW).arg(imgH).arg(zoom * 100));
}

void CoverPreview::wheelEvent(QWheelEvent* event)
{
	if (scrollArea->isVisible() && QApplication::keyboardModifiers() & Qt::ControlModifier) {
		const int numDegrees = event->angleDelta().y() / 8;
		const int numSteps = numDegrees / 15;
		if (0 != numSteps) {
			scaleImage(numSteps);
		}
		event->accept();
		return;
	}
	Dialog::wheelEvent(event);
}

CoverDialog::CoverDialog(QWidget* parent)
	: Dialog(parent, "CoverDialog"), existing(nullptr), preview(nullptr), saving(false), isArtist(false), isComposer(false), spinner(nullptr), msgOverlay(nullptr), page(0), menu(nullptr), showAction(nullptr), removeAction(nullptr)
{
	iCount++;
	QWidget* mainWidet = new QWidget(this);
	setupUi(mainWidet);
	setMainWidget(mainWidet);
	setAttribute(Qt::WA_DeleteOnClose);
	setButtons(Cancel | Ok);
	enableButton(Ok, false);
	connect(list, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(showImage(QListWidgetItem*)));
	connect(list, SIGNAL(itemSelectionChanged()), SLOT(checkStatus()));
	connect(search, SIGNAL(clicked()), SLOT(sendQuery()));
	connect(query, SIGNAL(returnPressed()), SLOT(sendQuery()));
	connect(addFileButton, SIGNAL(clicked()), SLOT(addLocalFile()));
	connect(list, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(menuRequested(const QPoint&)));

	QFont f(list->font());
	QFontMetrics origFm(f);
	iSize = origFm.height() * 7;
	iSize = ((iSize / 10) * 10) + ((iSize % 10) ? 10 : 0);
	f.setPointSizeF(f.pointSizeF() * 0.75);
	QFontMetrics fm(f);
	list->setFont(f);
	list->setAcceptDrops(false);
	list->setContextMenuPolicy(Qt::CustomContextMenu);
	list->setDragDropMode(QAbstractItemView::NoDragDrop);
	list->setDragEnabled(false);
	list->setDropIndicatorShown(false);
	list->setMovement(QListView::Static);
	list->setGridSize(QSize(imageSize() + 10, imageSize() + 10 + (2.25 * fm.height())));
	list->setIconSize(QSize(imageSize(), imageSize()));
	int spacing = style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical);
	if (spacing < 0) {
		spacing = 4;
	}
	list->setSpacing(spacing);
	list->setViewMode(QListView::IconMode);
	list->setResizeMode(QListView::Adjust);
	list->setMinimumSize((list->gridSize().width() * 3) + style()->pixelMetric(QStyle::PM_ScrollBarExtent) + spacing, list->gridSize().height() * 2);
	list->setSortingEnabled(false);

	addFileButton->setIcon(Icons::self()->folderListIcon);
	addFileButton->setAutoRaise(true);

	setAcceptDrops(true);
}

CoverDialog::~CoverDialog()
{
	iCount--;
	cancelQuery();
	clearTempFiles();
}

void CoverDialog::show(const Song& s, const Covers::Image& current)
{
	song = s;
	isArtist = song.isArtistImageRequest();
	isComposer = song.isComposerImageRequest();
	Covers::Image img = current.img.isNull() ? Covers::locateImage(song) : current;

	if (img.validFileName() && !QFileInfo(img.fileName).isWritable()) {
		MessageBox::error(parentWidget(),
		                  (isArtist
		                           ? tr("An image already exists for this artist, and the file is not writeable.")
		                           : isComposer
		                           ? tr("An image already exists for this composer, and the file is not writeable.")
		                           : tr("A cover already exists for this album, and the file is not writeable."))
		                          + QString("<br/><br/><a href=\"%1\">%1</a>").arg(img.fileName));
		deleteLater();
		return;
	}
	note->setVisible(!isArtist && !isComposer);
	if (isArtist) {
		setCaption(tr("'%1' Artist Image").arg(song.albumArtist()));
	}
	else if (isComposer) {
		setCaption(tr("'%1' Composer Image").arg(song.albumArtist()));
	}
	else {
		setCaption(tr("'%1 - %2' Album Cover", "'Artist - Album' Album Cover").arg(song.albumArtist()).arg(song.album));
	}
	if (!img.img.isNull()) {
		existing = new ExistingCover(isArtist || isComposer ? Covers::Image(cropImage(img.img, true), img.fileName) : img, list);
		list->addItem(existing);
	}
	/*
    Add other images in the source folder? #716
    if (!isArtist) {
        QString dirName=MPDConnection::self()->getDetails().dir+Utils::getDir(s.file);
        QStringList files=QDir(dirName).entryList(QStringList() << QLatin1String("*.jpg") << QLatin1String("*.jpeg") << QLatin1String("*.png"), QDir::Files|QDir::Readable);
        qWarning() << files;
        for (const QString &f: files) {
            QString fileName=dirName+f;
            if (fileName!=img.fileName) {
                QImage i(fileName);
                if (!i.isNull()) {
                    currentLocalCovers.insert(fileName);
                    insertItem(new LocalCover(fileName, i, list));
                }
            }
        }
    }
    */
	query->setText(isArtist ? song.albumArtist() : isComposer ? song.albumArtistOrComposer()
	                                                          : QString(song.albumArtist() + QLatin1String(" ") + song.album));
	Dialog::show();
	sendQuery();
}

static const char* constHostProperty = "host";
static const char* constLargeProperty = "large";
static const char* constThumbProperty = "thumb";
static const char* constWidthProperty = "w";
static const char* constHeightProperty = "h";
static const char* constSizeProperty = "sz";
static const char* constTypeProperty = "type";
static const char* constLastFmHost = "ws.audioscrobbler.com";
//static const char * constGoogleHost="images.google.com";
static const char* constCoverArtArchiveHost = "coverartarchive.org";
//static const char * constSpotifyHost="ws.spotify.com";
static const char* constITunesHost = "itunes.apple.com";
static const char* constDeezerHost = "api.deezer.com";

void CoverDialog::queryJobFinished()
{
	NetworkJob* reply = qobject_cast<NetworkJob*>(sender());
	if (!reply) {
		return;
	}
	reply->deleteLater();
	if (!currentQuery.contains(reply)) {
		return;
	}

	DBUG << reply->origUrl().toString() << reply->ok();
	currentQuery.remove(reply);
	if (reply->ok()) {
		QString host = reply->property(constHostProperty).toString();
		QByteArray resp = reply->readAll();
		if (constLastFmHost == host) {
			parseLastFmQueryResponse(resp);
			//} else if (constGoogleHost==host) {
			//    parseGoogleQueryResponse(resp);
		}
		else if (constCoverArtArchiveHost == host) {
			parseCoverArtArchiveQueryResponse(resp);
			//} else if (constSpotifyHost==host) {
			//    parseSpotifyQueryResponse(resp);
		}
		else if (constITunesHost == host) {
			parseITunesQueryResponse(resp);
		}
		else if (constDeezerHost == host) {
			parseDeezerQueryResponse(resp);
		}
	}
	if (currentQuery.isEmpty()) {
		setSearching(false);
	}
}

void CoverDialog::insertItem(CoverItem* item)
{
	list->addItem(item);
	if (item->isLocal()) {
		list->scrollToItem(item);
		item->setSelected(true);
	}
}

void CoverDialog::downloadJobFinished()
{
	NetworkJob* reply = qobject_cast<NetworkJob*>(sender());
	if (!reply) {
		return;
	}
	reply->deleteLater();
	if (!currentQuery.contains(reply)) {
		return;
	}

	DownloadType dlType = (DownloadType)reply->property(constTypeProperty).toInt();

	if (DL_LargeSave == dlType) {
		saving = false;
	}

	DBUG << reply->origUrl().toString() << reply->ok();
	currentQuery.remove(reply);
	if (reply->ok()) {
		QString host = reply->property(constHostProperty).toString();
		QString url = reply->url().toString();
		QByteArray data = reply->readAll();
		const char* format = Covers::imageFormat(data);
		QImage img = QImage::fromData(data, format);
		if (!img.isNull()) {
			bool isLarge = reply->property(constThumbProperty).toString().isEmpty();
			QTemporaryFile* temp = nullptr;

			if (isLarge || (reply->property(constThumbProperty).toString() == reply->property(constLargeProperty).toString())) {
				temp = new QTemporaryFile("cantata_XXXXXX." + (format ? QString(QLatin1String(format)).toLower() : "png"));

				if (temp->open()) {
					if (!format) {
						img.save(temp, "PNG");
					}
					else {
						temp->write(data);
					}
					if (tempFiles.size() >= constMaxTempFiles) {
						QTemporaryFile* last = tempFiles.takeLast();
						last->remove();
						delete last;
					}
					temp->close();
					temp->setProperty(constLargeProperty, reply->property(constLargeProperty));
					tempFiles.prepend(temp);
				}
				else {
					delete temp;
					temp = nullptr;
				}
			}
			if (isLarge) {
				if (DL_LargePreview == dlType) {
					previewDialog()->showImage(cropImage(img, isArtist || isComposer), reply->property(constLargeProperty).toString());
				}
				else if (DL_LargeSave == dlType) {
					if (!temp) {
						MessageBox::error(this, tr("Failed to set cover!\n\nCould not download to temporary file!"));
					}
					else if (saveCover(temp->fileName(), img)) {
						accept();
					}
				}
			}
			else {
				CoverItem* item = nullptr;
				img = cropImage(img, isArtist || isComposer);
				if (constLastFmHost == host) {
					item = new LastFmCover(reply->property(constLargeProperty).toString(), url, img, list);
					//} else if (constGoogleHost==host) {
					//    item=new GoogleCover(reply->property(constLargeProperty).toString(), url, img, reply->property(constWidthProperty).toInt(),
					//                         reply->property(constHeightProperty).toInt(), reply->property(constSizeProperty).toInt(), list);
				}
				else if (constCoverArtArchiveHost == host) {
					item = new CoverArtArchiveCover(reply->property(constLargeProperty).toString(), url, img, list);
					//} else if (constSpotifyHost==host) {
					//    item=new SpotifyCover(reply->property(constLargeProperty).toString(), url, img, list);
				}
				else if (constITunesHost == host) {
					item = new ITunesCover(reply->property(constLargeProperty).toString(), url, img, list);
				}
				else if (constDeezerHost == host) {
					item = new DeezerCover(reply->property(constLargeProperty).toString(), url, img, list);
				}
				if (item) {
					insertItem(item);
				}
			}
		}
	}
	else if (reply->property(constThumbProperty).toString().isEmpty()) {
		if (preview && preview->aboutToShow(reply->property(constLargeProperty).toString())) {
			preview->hide();
		}
		MessageBox::error(this, tr("Failed to download image!"));
	}
	if (currentQuery.isEmpty()) {
		setSearching(false);
	}
}

void CoverDialog::showImage(QListWidgetItem* item)
{
	if (saving) {
		return;
	}

	CoverItem* cover = static_cast<CoverItem*>(item);

	if (cover->isExisting()) {
		previewDialog()->downloading(cover->url());
		previewDialog()->showImage(static_cast<ExistingCover*>(cover)->image(), cover->url());
	}
	else if (cover->isLocal()) {
		previewDialog()->downloading(cover->url());
		previewDialog()->showImage(static_cast<LocalCover*>(cover)->image(), cover->url());
	}
	else {
		previewDialog()->downloading(cover->url());
		NetworkJob* j = downloadImage(cover->url(), DL_LargePreview);
		if (j) {
			j->setProperty(constLargeProperty, cover->url());
			connect(j, SIGNAL(downloadProgress(qint64, qint64)), preview, SLOT(progress(qint64, qint64)));
		}
	}
}

CoverPreview* CoverDialog::previewDialog()
{
	if (!preview) {
		preview = new CoverPreview(this);
	}
	return preview;
}

void CoverDialog::sendQuery()
{
	if (saving) {
		return;
	}

	QString fixedQuery(query->text().trimmed());
	fixedQuery.remove(QChar('?'));

	if (fixedQuery.isEmpty()) {
		return;
	}

	if (currentQueryString == fixedQuery) {
		page++;
	}
	else {
		page = 0;
	}

	if (0 == page) {
		QList<CoverItem*> keep;

		while (list->count()) {
			CoverItem* item = static_cast<CoverItem*>(list->takeItem(0));
			if (item->isExisting() || item->isLocal()) {
				keep.append(item);
			}
			else {
				currentUrls.remove(item->url());
				currentUrls.remove(item->thumbUrl());
				delete item;
			}
		}

		for (CoverItem* item : keep) {
			list->addItem(item);
		}

		cancelQuery();
	}

	currentQueryString = fixedQuery;
	sendLastFmQuery(fixedQuery, page);
	//sendGoogleQuery(fixedQuery, page);
	if (page == 0) {
		//sendSpotifyQuery(fixedQuery);
		sendITunesQuery(fixedQuery);
		sendDeezerQuery(fixedQuery);
	}
	setSearching(!currentQuery.isEmpty());
}

void CoverDialog::sendLastFmQuery(const QString& fixedQuery, int page)
{
	QUrl url;
	QUrlQuery query;
	url.setScheme("https");
	url.setHost(constLastFmHost);
	url.setPath("/2.0/");
	ApiKeys::self()->addKey(query, ApiKeys::LastFm);
	query.addQueryItem("limit", QString::number(20));
	query.addQueryItem("page", QString::number(page + 1));
	query.addQueryItem(isArtist || isComposer ? "artist" : "album", fixedQuery);
	query.addQueryItem("method", isArtist || isComposer ? "artist.search" : "album.search");
	url.setQuery(query);
	sendQueryRequest(url);
}

/*
void CoverDialog::sendGoogleQuery(const QString &fixedQuery, int page)
{
    QUrl url;
    QUrlQuery query;
    url.setScheme("https");
    url.setHost(constGoogleHost);
    url.setPath("/images");
    query.addQueryItem("q", fixedQuery);
    query.addQueryItem("gbv", QChar('1'));
    query.addQueryItem("filter", QChar('1'));
    query.addQueryItem("start", QString::number(20 * page));
    url.setQuery(query);
    sendQueryRequest(url);
}
*/

/*
void CoverDialog::sendSpotifyQuery(const QString &fixedQuery)
{
    #ifdef QT_NO_SSL
    return;
    #else
    if (!QSslSocket::supportsSsl()) {
        return;
    }
    QUrl url;
    QUrlQuery query;
    url.setScheme("https");
    url.setHost(constSpotifyHost);
    url.setPath(isArtist||isComposer ? "/search/1/artist.json" : "/search/1/album.json");
    query.addQueryItem("q", fixedQuery);
    url.setQuery(query);
    sendQueryRequest(url);
    #endif
}
*/

void CoverDialog::sendITunesQuery(const QString& fixedQuery)
{
	if (isArtist || isComposer) {// TODO???
		return;
	}

	QUrl url;
	QUrlQuery query;
	url.setScheme("https");
	url.setHost(constITunesHost);
	url.setPath("/search");
	query.addQueryItem("term", fixedQuery);
	query.addQueryItem("limit", QString::number(10));
	query.addQueryItem("media", "music");
	query.addQueryItem("entity", "album");
	url.setQuery(query);
	sendQueryRequest(url);
}

void CoverDialog::sendDeezerQuery(const QString& fixedQuery)
{
	QUrl url;
	QUrlQuery query;
	url.setScheme("https");
	url.setHost(constDeezerHost);
	url.setPath(isArtist || isComposer ? "/search/artist" : "/search/album");
	query.addQueryItem("q", fixedQuery);
	query.addQueryItem("nb_items", QString::number(10));
	query.addQueryItem("output", "json");
	url.setQuery(query);
	sendQueryRequest(url);
}

void CoverDialog::checkStatus()
{
	QList<QListWidgetItem*> items = list->selectedItems();
	enableButtonOk(1 == items.size() && !static_cast<CoverItem*>(items.at(0))->isExisting());
}

void CoverDialog::cancelQuery()
{
	for (NetworkJob* job : currentQuery) {
		job->cancelAndDelete();
	}
	currentQuery.clear();
	setSearching(false);
}

void CoverDialog::addLocalFile()
{
	QString fileName = QFileDialog::getOpenFileName(this, tr("Load Local Cover"), QDir::homePath(), tr("Images (*.png *.jpg *.jpeg)"));

	if (!fileName.isEmpty()) {
		if (currentLocalCovers.contains(fileName)) {
			MessageBox::error(this, tr("File is already in list!"));
		}
		else {
			QImage img(fileName);
			if (img.isNull()) {
				MessageBox::error(this, tr("Failed to read image!"));
			}
			else {
				currentLocalCovers.insert(fileName);
				insertItem(new LocalCover(fileName, img, list));
			}
		}
	}
}

void CoverDialog::menuRequested(const QPoint& pos)
{
	if (!menu) {
		menu = new QMenu(list);
		showAction = menu->addAction(tr("Display"));
		removeAction = menu->addAction(tr("Remove"));
		connect(showAction, SIGNAL(triggered()), SLOT(showImage()));
		connect(removeAction, SIGNAL(triggered()), SLOT(removeImages()));
	}

	QList<QListWidgetItem*> items = list->selectedItems();
	showAction->setEnabled(1 == items.count());
	removeAction->setEnabled(!items.isEmpty());
	if (removeAction->isEnabled()) {
		for (QListWidgetItem* i : items) {
			if (static_cast<CoverItem*>(i)->isExisting()) {
				removeAction->setEnabled(false);
			}
		}
	}
	menu->popup(list->mapToGlobal(pos));
}

void CoverDialog::showImage()
{
	QList<QListWidgetItem*> items = list->selectedItems();
	if (1 == items.count()) {
		showImage(items.at(0));
	}
}

void CoverDialog::removeImages()
{
	QList<QListWidgetItem*> items = list->selectedItems();
	for (QListWidgetItem* i : items) {
		delete i;
	}
}

void CoverDialog::clearTempFiles()
{
	for (QTemporaryFile* file : tempFiles) {
		file->remove();
		delete file;
	}
}

void CoverDialog::sendQueryRequest(const QUrl& url, const QString& host)
{
	DBUG << url.toString();
	NetworkJob* j = NetworkAccessManager::self()->get(QNetworkRequest(url));
	j->setProperty(constHostProperty, host.isEmpty() ? url.host() : host);
	j->setProperty(constTypeProperty, (int)DL_Query);
	connect(j, SIGNAL(finished()), this, SLOT(queryJobFinished()));
	currentQuery.insert(j);
}

NetworkJob* CoverDialog::downloadImage(const QString& url, DownloadType dlType)
{
	DBUG << url << dlType;
	if (DL_Thumbnail == dlType) {
		if (currentUrls.contains(url)) {
			return nullptr;
		}
		currentUrls.insert(url);
	}
	else {
		for (QTemporaryFile* tmp : tempFiles) {
			if (tmp->property(constLargeProperty).toString() == url) {
				QImage img;
				if (img.load(tmp->fileName())) {
					if (DL_LargePreview == dlType) {
						previewDialog()->downloading(url);
						previewDialog()->showImage(img, url);
					}
					else if (DL_LargeSave == dlType) {
						if (saveCover(tmp->fileName(), img)) {
							accept();
						}
					}
					return nullptr;
				}
				tmp->remove();
				tempFiles.removeAll(tmp);
				delete tmp;
				break;
			}
		}
	}

	if (DL_LargeSave == dlType) {
		saving = true;
	}

	NetworkJob* j = NetworkAccessManager::self()->get(QNetworkRequest(QUrl(url)));
	connect(j, SIGNAL(finished()), this, SLOT(downloadJobFinished()));
	currentQuery.insert(j);
	j->setProperty(constTypeProperty, (int)dlType);
	if (saving) {
		previewDialog()->downloading(url);
		connect(j, SIGNAL(downloadProgress(qint64, qint64)), preview, SLOT(progress(qint64, qint64)));
	}
	return j;
}

void CoverDialog::downloadThumbnail(const QString& thumbUrl, const QString& largeUrl, const QString& host, int w, int h, int sz)
{
	if (thumbUrl.isEmpty() || largeUrl.isEmpty()) {
		return;
	}

	NetworkJob* j = downloadImage(thumbUrl, DL_Thumbnail);
	if (j) {
		j->setProperty(constThumbProperty, thumbUrl);
		j->setProperty(constLargeProperty, largeUrl);
		j->setProperty(constHostProperty, host);
		if (w > 0) {
			j->setProperty(constWidthProperty, w);
		}
		if (h > 0) {
			j->setProperty(constHeightProperty, h);
		}
		if (sz > 0) {
			j->setProperty(constSizeProperty, sz);
		}
	}
}

typedef QMap<QString, QString> SizeMap;
void CoverDialog::parseLastFmQueryResponse(const QByteArray& resp)
{
	bool inSection = false;
	QXmlStreamReader doc(resp);
	SizeMap urls;
	QList<SizeMap> entries;
	QStringList musicBrainzIds;

	doc.setNamespaceProcessing(false);
	while (!doc.atEnd()) {
		doc.readNext();

		if (doc.isStartElement()) {
			if (!inSection && QLatin1String(isArtist || isComposer ? "artist" : "album") == doc.name()) {
				inSection = true;
				urls.clear();
			}
			else if (inSection && QLatin1String("image") == doc.name()) {
				QString size = doc.attributes().value("size").toString();
				QString url = doc.readElementText();
				if (!size.isEmpty() && !url.isEmpty()) {
					urls.insert(size, url);
				}
			}
			else if (inSection && QLatin1String("mbid") == doc.name()) {
				QString id = doc.readElementText();
				if (id.length() > 4) {
					musicBrainzIds.append(id);
				}
			}
		}
		else if (doc.isEndElement() && inSection && QLatin1String(isArtist || isComposer ? "artist" : "album") == doc.name()) {
			if (!urls.isEmpty()) {
				entries.append(urls);
				urls.clear();
			}
			inSection = false;
		}
	}

	if (!(isArtist || isComposer)) {
		QStringList largeUrls = QStringList() << "extralarge"
											  << "large";
		QStringList thumbUrls = QStringList() << "large"
											  << "medium"
											  << "small";

		for (const SizeMap& urls : entries) {
			QString largeUrl;
			QString thumbUrl;

			for (const QString& u : largeUrls) {
				if (urls.contains(u)) {
					largeUrl = urls[u];
					break;
				}
			}

			for (const QString& u : thumbUrls) {
				if (urls.contains(u)) {
					thumbUrl = urls[u];
					break;
				}
			}
			downloadThumbnail(thumbUrl, largeUrl, constLastFmHost);
		}
	}

	for (const QString& id : musicBrainzIds) {
		QUrl coverartUrl;
		coverartUrl.setScheme("http");
		coverartUrl.setHost(constCoverArtArchiveHost);
		coverartUrl.setPath("/release/" + id);
		sendQueryRequest(coverartUrl);
	}
}

/*
void CoverDialog::parseGoogleQueryResponse(const QByteArray &resp)
{
    // Code based on Audex CDDA Extractor
    QRegExp rx("<a\\shref=\"(\\/imgres\\?imgurl=[^\"]+)\">[\\s\\n]*<img[^>]+src=\"([^\"]+)\"");
    rx.setCaseSensitivity(Qt::CaseInsensitive);
    rx.setMinimal(true);

    int pos = 0;
    QString xml(QString::fromUtf8(resp));
    QString html = xml.replace(QLatin1String("&amp;"), QLatin1String("&"));

    while (-1!=(pos=rx.indexIn(html, pos))) {
        QUrl u("http://www.google.com"+rx.cap(1));
        QUrlQuery url(u);
        int width=url.queryItemValue("w").toInt();
        int height=url.queryItemValue("h").toInt();
        if (canUse(width, height)) {
            downloadThumbnail(rx.cap(2), url.queryItemValue("imgurl"), constGoogleHost, width, height, url.queryItemValue("sz").toInt());
        }
        pos += rx.matchedLength();
    }
}
*/

void CoverDialog::parseCoverArtArchiveQueryResponse(const QByteArray& resp)
{
	QJsonParseError jsonParseError;
	QVariantMap parsed = QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap();
	bool ok = QJsonParseError::NoError == jsonParseError.error;

	if (ok && parsed.contains("images")) {
		QVariantList images = parsed["images"].toList();
		for (const QVariant& i : images) {
			QVariantMap im = i.toMap();
			if (im.contains("front") && im["front"].toBool() && im.contains("image") && im.contains("thumbnails")) {
				QVariantMap thumb = im["thumbnails"].toMap();
				QString largeUrl = im["image"].toString();
				QString thumbUrl;
				if (thumb.contains("small")) {
					thumbUrl = thumb["small"].toString();
				}
				else if (thumb.contains("large")) {
					thumbUrl = thumb["large"].toString();
				}
				downloadThumbnail(thumbUrl, largeUrl, constCoverArtArchiveHost);
			}
		}
	}
}

/*
void CoverDialog::parseSpotifyQueryResponse(const QByteArray &resp)
{
    QJsonParseError jsonParseError;
    QVariantMap parsed=QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap();
    bool ok=QJsonParseError::NoError==jsonParseError.error;

    if (ok) {
        if (parsed.contains("info")) { // Initial query response...
            const QString key=QLatin1String(isArtist||isComposer ? "artists" : "albums");
            const QString href=QLatin1String("spotify:")+QLatin1String(isArtist||isComposer ? "artist:" : "album:");
            const QString baseUrl=QLatin1String("https://embed.spotify.com/oembed/?url=http://open.spotify.com/")+QLatin1String(isArtist ||isComposer? "artist/" : "album/");
            if (parsed.contains(key)) {
                QVariantList results=parsed[key].toList();
                for (const QVariant &res: results) {
                    QVariantMap item=res.toMap();
                    if (item.contains("href")) {
                        QString url=item["href"].toString();
                        if (url.contains(href)) {
                            url=baseUrl+url.remove(href);
                            sendQueryRequest(QUrl(url), constSpotifyHost);
                        }
                    }
                }
            }
        } else if (parsed.contains("provider_url") && parsed.contains("thumbnail_url")) { // Response to query above
            QString thumbUrl=parsed["thumbnail_url"].toString();
            downloadThumbnail(thumbUrl, QString(thumbUrl).replace("/cover/", "/640/"), constSpotifyHost);
        }
    }
}
*/

void CoverDialog::parseITunesQueryResponse(const QByteArray& resp)
{
	QJsonParseError jsonParseError;
	QVariantMap parsed = QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap();
	bool ok = QJsonParseError::NoError == jsonParseError.error;

	if (ok && parsed.contains("results")) {
		QVariantList results = parsed["results"].toList();
		for (const QVariant& res : results) {
			QVariantMap item = res.toMap();
			if (item.contains("artworkUrl100")) {
				QString thumbUrl = item["artworkUrl100"].toString();
				downloadThumbnail(thumbUrl, QString(thumbUrl).replace("100x100", "600x600"), constITunesHost);
			}
		}
	}
}

void CoverDialog::parseDeezerQueryResponse(const QByteArray& resp)
{
	const QString key = QLatin1String(isArtist || isComposer ? "picture" : "cover");
	QJsonParseError jsonParseError;
	QVariantMap parsed = QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap();
	bool ok = QJsonParseError::NoError == jsonParseError.error;

	if (ok && parsed.contains("data")) {
		QVariantList results = parsed["data"].toList();
		for (const QVariant& res : results) {
			QVariantMap item = res.toMap();
			if (item.contains(key)) {
				QString thumbUrl = item[key].toString();
				downloadThumbnail(thumbUrl, thumbUrl + "&size=big", constDeezerHost);
			}
		}
	}
}

void CoverDialog::slotButtonClicked(int button)
{
	switch (button) {
	case Ok: {
		QList<QListWidgetItem*> items = list->selectedItems();
		if (1 == items.size()) {
			CoverItem* cover = static_cast<CoverItem*>(items.at(0));
			if (cover->isLocal()) {
				if (saveCover(cover->url(), static_cast<LocalCover*>(cover)->image())) {
					accept();
				}
			}
			else if (!cover->isExisting()) {
				NetworkJob* j = downloadImage(cover->url(), DL_LargeSave);
				if (j) {
					j->setProperty(constLargeProperty, cover->url());
				}
			}
		}
		break;
	}
	case Cancel:
		reject();
		// Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!!
		Dialog::slotButtonClicked(button);
		break;
	default:
		break;
	}
}

bool CoverDialog::saveCover(const QString& src, const QImage& img)
{
	QString filePath = song.filePath();
	QString ext = Utils::getExtension(src);
	if (0 == ext.compare(".jpeg", Qt::CaseInsensitive)) {
		ext = ".jpg";
	}
	if (song.isCdda()) {
		QString dir = Utils::cacheDir(Covers::constCddaCoverDir, true);
		if (!dir.isEmpty()) {
			QString destName = dir + filePath.mid(Song::constCddaProtocol.length()) + ext;
			if (QFile::exists(destName)) {
				QFile::remove(destName);
			}
			if (QFile::copy(src, destName)) {
				emit selectedCover(img, destName);
				return true;
			}
		}
		MessageBox::error(this, tr("Failed to set cover!\n\nCould not make copy!"));
		return false;
	}
	QString existingBackup;

	if (existing && !existing->url().isEmpty() && !existing->url().startsWith(Covers::constCoverInTagPrefix)) {
		static const QLatin1String constBakExt(".bak");
		existingBackup = existing->url() + constBakExt;
		if (!QFile::rename(existing->url(), existingBackup)) {
			MessageBox::error(this, tr("Failed to set cover!\n\nCould not backup original!"));
			return false;
		}
	}

	QString destName;
	QString mpdDir;
	QString dirName;
	bool saveInMpd = !isArtist && !isComposer && Settings::self()->storeCoversInMpdDir();

	if (saveInMpd) {
		bool haveAbsPath = filePath.startsWith('/');
		mpdDir = MPDConnection::self()->getDetails().dir;
		if (haveAbsPath || !mpdDir.isEmpty()) {
			dirName = filePath.endsWith('/') ? (haveAbsPath ? QString() : mpdDir) + filePath
											 : Utils::fixPath((haveAbsPath ? QString() : mpdDir) + song.getDir());
		}
		saveInMpd = QDir(dirName).exists();
	}

	if (isArtist) {
		/*if (saveInMpd && !mpdDir.isEmpty() && dirName.startsWith(mpdDir) && 2==dirName.mid(mpdDir.length()).split('/', CANTATA_SKIP_EMPTY).count()) {
            QDir d(dirName);
            d.cdUp();
            destName=d.absolutePath()+'/'+Covers::constArtistImage+ext;
        } else*/
		{
			destName = Utils::cacheDir(Covers::constCoverDir, true) + Covers::encodeName(song.albumArtist()) + ext;
		}
	}
	else if (isComposer) {
		/*if (saveInMpd && !mpdDir.isEmpty() && dirName.startsWith(mpdDir) && 2==dirName.mid(mpdDir.length()).split('/', CANTATA_SKIP_EMPTY).count()) {
            QDir d(dirName);
            d.cdUp();
            destName=d.absolutePath()+'/'+Covers::constComposerImage+ext;
        } else*/
		{
			destName = Utils::cacheDir(Covers::constCoverDir, true) + Covers::encodeName(song.albumArtistOrComposer()) + ext;
		}
	}
	else {
		if (saveInMpd) {
			destName = dirName + Covers::albumFileName(song) + ext;
		}
		else {// Save to cache dir...
			QString dir(Utils::cacheDir(Covers::constCoverDir + Covers::encodeName(song.albumArtist()), true));
			destName = dir + Covers::encodeName(song.album) + ext;
		}
	}

	if (!destName.startsWith("http:/", Qt::CaseInsensitive) && !destName.startsWith("https:/", Qt::CaseInsensitive) && QFile::copy(src, destName)) {
		Utils::setFilePerms(destName);
		if (!existingBackup.isEmpty() && QFile::exists(existingBackup)) {
			QFile::remove(existingBackup);
		}
		Covers::self()->updateCover(song, img, destName);
		return true;
	}
	else {
		if (existing && !existingBackup.isEmpty()) {
			QFile::rename(existingBackup, existing->url());
		}
		MessageBox::error(this, tr("Failed to set cover!\n\nCould not copy file to '%1'!").arg(destName));
		return false;
	}
}

static bool hasMimeType(QDropEvent* event)
{
	return event->mimeData()->formats().contains(QLatin1String("text/uri-list"));
}

void CoverDialog::dragEnterEvent(QDragEnterEvent* event)
{
	if (hasMimeType(event)) {
		event->acceptProposedAction();
	}
}

void CoverDialog::dropEvent(QDropEvent* event)
{
	if (hasMimeType(event) && Qt::CopyAction == event->dropAction()) {
		event->acceptProposedAction();

		QList<QUrl> urls(event->mimeData()->urls());
		for (const QUrl& url : urls) {
			if (url.scheme().isEmpty() || "file" == url.scheme()) {
				QString path = url.path();
				if (!currentLocalCovers.contains(path) && (path.endsWith(".jpg", Qt::CaseInsensitive) || path.endsWith(".jpeg", Qt::CaseInsensitive) || path.endsWith(".png", Qt::CaseInsensitive))) {
					QImage img(path);
					if (!img.isNull()) {
						currentLocalCovers.insert(path);
						insertItem(new LocalCover(path, img, list));
					}
				}
			}
		}
	}
}

void CoverDialog::setSearching(bool s)
{
	if (!spinner && !s) {
		return;
	}
	if (!spinner) {
		spinner = new Spinner(this);
		spinner->setWidget(list);
	}
	if (!msgOverlay) {
		msgOverlay = new MessageOverlay(this);
		msgOverlay->setWidget(list);
		connect(msgOverlay, SIGNAL(cancel()), SLOT(cancelQuery()));
	}
	if (s) {
		spinner->start();
		msgOverlay->setText(tr("Searching..."));
	}
	else {
		spinner->stop();
		msgOverlay->setText(QString());
	}
}

#include "moc_coverdialog.cpp"
