/*
 * 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 "podcastsearchdialog.h"
#include "config.h"
#include "gui/covers.h"
#include "network/networkaccessmanager.h"
#include "opmlparser.h"
#include "podcastservice.h"
#include "rssparser.h"
#include "support/action.h"
#include "support/flattoolbutton.h"
#include "support/lineedit.h"
#include "support/messagewidget.h"
#include "support/pagewidget.h"
#include "support/spinner.h"
#include "support/utils.h"
#include "widgets/basicitemdelegate.h"
#include "widgets/icons.h"
#include "widgets/textbrowser.h"
#include <QBoxLayout>
#include <QBuffer>
#include <QCache>
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QFile>
#include <QFileDialog>
#include <QGridLayout>
#include <QHeaderView>
#include <QImage>
#include <QJsonDocument>
#include <QLabel>
#include <QNetworkRequest>
#include <QPushButton>
#include <QStringList>
#include <QTreeWidget>
#include <QUrlQuery>
#include <QXmlStreamReader>

static int iCount = 0;

static QCache<QUrl, QImage> imageCache(200 * 1024);
static int maxImageSize = -1;
static const char* constOrigUrlProperty = "orig-url";

enum Roles {
	IsPodcastRole = Qt::UserRole,
	UrlRole,
	ImageUrlRole,
	DescriptionRole,
	WebPageUrlRole
};

QString PodcastSearchDialog::constCacheDir = QLatin1String("podcast-directories");
QString PodcastSearchDialog::constExt = QLatin1String(".opml");

static QString generateCacheFileName(const QUrl& url, bool create)
{
	QString hash = QCryptographicHash::hash(url.toString().toUtf8(), QCryptographicHash::Md5).toHex();
	QString dir = Utils::cacheDir(PodcastSearchDialog::constCacheDir, create);
	return dir.isEmpty() ? QString() : (dir + hash + PodcastSearchDialog::constExt);
}

class ITunesSearchPage : public PodcastSearchPage {
public:
	ITunesSearchPage(QWidget* p)
		: PodcastSearchPage(p,
	                        QLatin1String("iTunes"),
	                        fa::fa_brands,
	                        fa::fa_apple,
	                        QUrl(QLatin1String("http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStoreServices.woa/wa/wsSearch")),
	                        QLatin1String("term"),
	                        QStringList() << QLatin1String("country") << QLatin1String("US") << QLatin1String("media") << QLatin1String("podcast"))
	{
	}

	void parse(const QVariant& data) override
	{
		for (const QVariant& resultVariant : data.toMap()[QLatin1String("results")].toList()) {
			QVariantMap result(resultVariant.toMap());
			if (result[QLatin1String("kind")].toString() != QLatin1String("podcast")) {
				continue;
			}

			addPodcast(result[QLatin1String("trackName")].toString(),
			           result[QLatin1String("feedUrl")].toUrl(),
			           result[QLatin1String("artworkUrl100")].toUrl(),
			           QString(),
			           result[QLatin1String("collectionViewUrl")].toString(),
			           nullptr);
		}
	}
};

class GPodderSearchPage : public PodcastSearchPage {
public:
	GPodderSearchPage(QWidget* p)
		: PodcastSearchPage(p,
	                        QLatin1String("GPodder"),
	                        fa::fa_solid,
	                        fa::fa_podcast,
	                        QUrl(QLatin1String("http://gpodder.net/search.json")),
	                        QLatin1String("q"))
	{
	}

	void parse(const QVariant& data) override
	{
		QVariantList list = data.toList();
		for (const QVariant& var : list) {
			QVariantMap map = var.toMap();
			addPodcast(map[QLatin1String("title")].toString(),
			           map[QLatin1String("url")].toUrl(),
			           map[QLatin1String("logo_url")].toUrl(),
			           map[QLatin1String("description")].toString(),
			           map[QLatin1String("website")].toString(),
			           nullptr);
		}
	}
};

PodcastPage::PodcastPage(QWidget* p, const QString& n)
	: QWidget(p), pageName(n), job(nullptr), imageJob(nullptr)
{
	tree = new QTreeWidget(this);
	tree->setItemDelegate(new BasicItemDelegate(tree));
	tree->header()->setVisible(false);
	text = new TextBrowser(this);
	spinner = new Spinner(this);
	spinner->setWidget(tree);
	imageSpinner = new Spinner(this);
	imageSpinner->setWidget(text);
	connect(tree, SIGNAL(itemSelectionChanged()), SLOT(selectionChanged()));
	tree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
	text->setOpenLinks(false);
	connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(openLink(QUrl)));
	updateText();
}

QUrl PodcastPage::currentRss() const
{
	QList<QTreeWidgetItem*> selection = tree->selectedItems();
	return selection.isEmpty() ? QUrl() : selection.at(0)->data(0, UrlRole).toUrl();
}

void PodcastPage::fetch(const QUrl& url)
{
	cancel();
	tree->clear();
	spinner->start();
	job = NetworkAccessManager::self()->get(url);
	connect(job, SIGNAL(finished()), this, SLOT(jobFinished()));
}

void PodcastPage::fetchImage(const QUrl& url)
{
	cancelImage();
	imageSpinner->start();
	imageJob = NetworkAccessManager::self()->get(url, 5000);
	imageJob->setProperty(constOrigUrlProperty, url);
	connect(imageJob, SIGNAL(finished()), this, SLOT(imageJobFinished()));
}

void PodcastPage::cancel()
{
	spinner->stop();
	if (job) {
		job->cancelAndDelete();
		job = nullptr;
	}
}

void PodcastPage::cancelImage()
{
	imageSpinner->stop();
	if (imageJob) {
		imageJob->cancelAndDelete();
		imageJob = nullptr;
	}
}

void PodcastPage::addPodcast(const QString& title, const QUrl& url, const QUrl& image, const QString& description, const QString& webPage, QTreeWidgetItem* p)
{
	if (title.isEmpty() || url.isEmpty()) {
		return;
	}

	QTreeWidgetItem* podItem = p ? new QTreeWidgetItem(p, QStringList() << title)
								 : new QTreeWidgetItem(tree, QStringList() << title);

	podItem->setData(0, IsPodcastRole, true);
	podItem->setData(0, UrlRole, url);
	podItem->setData(0, ImageUrlRole, image);
	podItem->setData(0, DescriptionRole, description);
	podItem->setData(0, WebPageUrlRole, webPage);
	podItem->setIcon(0, Icons::self()->audioListIcon);
}

void PodcastPage::addCategory(const OpmlParser::Category& cat, QTreeWidgetItem* p)
{
	if (cat.categories.isEmpty() && cat.podcasts.isEmpty()) {
		return;
	}

	QTreeWidgetItem* catItem = p ? new QTreeWidgetItem(p, QStringList() << cat.name)
								 : new QTreeWidgetItem(tree, QStringList() << cat.name);

	catItem->setData(0, IsPodcastRole, false);
	catItem->setIcon(0, Icons::self()->folderListIcon);
	for (const OpmlParser::Podcast& pod : cat.podcasts) {
		addPodcast(pod, catItem);
	}
	for (const OpmlParser::Category& subCat : cat.categories) {
		addCategory(subCat, catItem);
	}
}

void PodcastPage::addPodcast(const OpmlParser::Podcast& pod, QTreeWidgetItem* p)
{
	addPodcast(pod.name, pod.url, pod.image, pod.description, pod.htmlUrl, p);
}

static QString encode(const QImage& img)
{
	QByteArray bytes;
	QBuffer buffer(&bytes);
	buffer.open(QIODevice::WriteOnly);
	img.save(&buffer, "PNG");
	return QString("<br/><img src=\"data:image/png;base64,%1\"><br/>").arg(QString(buffer.data().toBase64()));
}

void PodcastPage::updateText()
{
	QList<QTreeWidgetItem*> selection = tree->selectedItems();
	if (!selection.isEmpty()) {
		QTreeWidgetItem* item = selection.at(0);
		if (item->data(0, IsPodcastRole).toBool()) {
			QUrl url = item->data(0, UrlRole).toUrl();
			QUrl imageUrl = item->data(0, ImageUrlRole).toUrl();
			QString descr = item->data(0, DescriptionRole).toString();
			QString web = item->data(0, WebPageUrlRole).toString();
			QString str = "<b>" + item->text(0) + "</b><br/>";
			if (!imageUrl.isEmpty()) {
				QImage* img = imageCache.object(imageUrl);
				if (img) {
					str += encode(*img);
				}
				else {
					fetchImage(imageUrl);
				}
			}
			if (!descr.isEmpty()) {
				str += "<p>" + descr + "</p><br/>";
			}
			str += "<table><tr><td><b>" + tr("RSS:") + "</b></td><td><a href=\"" + url.toString() + "\">" + url.toString() + "</a></td></tr>";
			if (!web.isEmpty()) {
				str += "<tr><td><b>" + tr("Website:") + "</b></td><td><a href=\"" + web + "\">" + web + "</a></td></tr>";
			}
			str += "</table>";
			text->setHtml(str);
			return;
		}
	}
	text->setHtml("<b>" + tr("Podcast details") + "</b><p><i>" + tr("Select a podcast to display its details") + "</i></p>");
}

void PodcastPage::selectionChanged()
{
	cancelImage();
	updateText();
	QList<QTreeWidgetItem*> selection = tree->selectedItems();
	emit rssSelected(selection.isEmpty() ? QUrl() : selection.at(0)->data(0, UrlRole).toUrl());
}

void PodcastPage::imageJobFinished()
{
	NetworkJob* j = qobject_cast<NetworkJob*>(sender());
	if (!j) {
		return;
	}
	j->deleteLater();
	if (j != imageJob) {
		return;
	}
	if (imageSpinner) {
		imageSpinner->stop();
	}
	QByteArray data = imageJob->readAll();
	QImage img = QImage::fromData(data, Covers::imageFormat(data));
	if (!img.isNull()) {
		if (img.width() > maxImageSize || img.height() > maxImageSize) {
			img = img.scaled(maxImageSize, maxImageSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
		}
		imageCache.insert(imageJob->property(constOrigUrlProperty).toUrl(), new QImage(img), img.sizeInBytes());
		updateText();
	}
	imageJob = nullptr;
}

void PodcastPage::jobFinished()
{
	NetworkJob* j = qobject_cast<NetworkJob*>(sender());
	if (!j) {
		return;
	}
	j->deleteLater();
	if (j != job) {
		return;
	}
	if (spinner) {
		spinner->stop();
	}
	parseResonse(j->ok() ? j->actualJob() : nullptr);
	job = nullptr;
}

void PodcastPage::openLink(const QUrl& url)
{
	QDesktopServices::openUrl(url);
}

PodcastSearchPage::PodcastSearchPage(QWidget* p, const QString& n, int s, int i, const QUrl& qu, const QString& qk, const QStringList& other)
	: PodcastPage(p, n), queryUrl(qu), queryKey(qk), otherArgs(other)
{
	QBoxLayout* searchLayout = new QBoxLayout(QBoxLayout::LeftToRight);
	QBoxLayout* viewLayout = new QBoxLayout(QBoxLayout::LeftToRight);
	QBoxLayout* mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
	searchLayout->setContentsMargins(0, 0, 0, 0);
	viewLayout->setContentsMargins(0, 0, 0, 0);
	mainLayout->setContentsMargins(0, 0, 0, 0);
	search = new LineEdit(p);
	search->setPlaceholderText(tr("Enter search term..."));
	searchButton = new QPushButton(tr("Search"), p);
	QWidget::setTabOrder(search, searchButton);
	QWidget::setTabOrder(searchButton, tree);
	searchLayout->addWidget(search);
	searchLayout->addWidget(searchButton);
	viewLayout->addWidget(tree, 1);
	viewLayout->addWidget(text, 0);
	mainLayout->addLayout(searchLayout);
	mainLayout->addLayout(viewLayout);
	connect(search, SIGNAL(returnPressed()), SLOT(doSearch()));
	connect(searchButton, SIGNAL(clicked()), SLOT(doSearch()));
	icn = Icon::fa(s, i);
}

void PodcastSearchPage::showEvent(QShowEvent* e)
{
	search->setFocus();
	QWidget::showEvent(e);
}

void PodcastSearchPage::doSearch()
{
	QString text = search->text().trimmed();
	if (text.isEmpty() || text == currentSearch) {
		return;
	}

	currentSearch = text;
	QUrl url = queryUrl;
	QUrlQuery query;
	query.addQueryItem(queryKey, text);
	if (otherArgs.size() > 1) {
		for (int i = 0; i < otherArgs.size() - 1; i += 2) {
			query.addQueryItem(otherArgs.at(i), otherArgs.at(i + 1));
		}
	}
	url.setQuery(query);
	fetch(url);
}

void PodcastSearchPage::parseResonse(QIODevice* dev)
{
	if (!dev) {
		emit error(tr("Failed to fetch podcasts from %1").arg(name()));
		return;
	}
	QVariant data = QJsonDocument::fromJson(dev->readAll()).toVariant();
	if (data.isNull()) {
		emit error(tr("There was a problem parsing the response from %1").arg(name()));
		return;
	}
	parse(data);
}

OpmlBrowsePage::OpmlBrowsePage(QWidget* p, const QString& n, const QIcon& i, const QUrl& u)
	: PodcastPage(p, n), loaded(false), url(u)
{
	QBoxLayout* mainLayout = new QBoxLayout(QBoxLayout::LeftToRight, this);
	mainLayout->setContentsMargins(0, 0, 0, 0);
	mainLayout->addWidget(tree, 1);
	mainLayout->addWidget(text, 0);
	Action* act = new Action(tr("Reload"), this);
	tree->addAction(act);
	connect(act, SIGNAL(triggered()), this, SLOT(reload()));
	tree->setContextMenuPolicy(Qt::ActionsContextMenu);
	icn = i;
}

void OpmlBrowsePage::showEvent(QShowEvent* e)
{
	if (!loaded) {
		QString cacheFile = generateCacheFileName(url, false);
		if (!cacheFile.isEmpty() && QFile::exists(cacheFile)) {
			QFile f(cacheFile);
			if (f.open(QIODevice::ReadOnly)) {
				parseResonse(&f);
				if (tree->topLevelItemCount() > 0) {
					Utils::touchFile(cacheFile);
					loaded = true;
					return;
				}
			}
		}
		fetch(url);
		loaded = true;
	}
	QWidget::showEvent(e);
}

void OpmlBrowsePage::reload()
{
	QString cacheFile = generateCacheFileName(url, false);
	if (!cacheFile.isEmpty() && QFile::exists(cacheFile)) {
		QFile::remove(cacheFile);
	}
	fetch(url);
}

void OpmlBrowsePage::parseResonse(QIODevice* dev)
{
	bool isLoadingFromCache = dynamic_cast<QFile*>(dev) ? true : false;

	if (!dev) {
		if (!isLoadingFromCache) {
			emit error(tr("Failed to download directory listing"));
		}
		return;
	}
	QByteArray data = dev->readAll();
	OpmlParser::Category parsed = OpmlParser::parse(data);
	if (parsed.categories.isEmpty() && parsed.podcasts.isEmpty()) {
		if (!isLoadingFromCache) {
			emit error(tr("Failed to parse directory listing"));
		}
		return;
	}

	if (1 == parsed.categories.count() && parsed.podcasts.isEmpty()) {
		parsed = parsed.categories.at(0);
	}
	for (const OpmlParser::Category& cat : parsed.categories) {
		addCategory(cat, nullptr);
	}
	for (const OpmlParser::Podcast& pod : parsed.podcasts) {
		addPodcast(pod, nullptr);
	}
	if (!isLoadingFromCache && tree->topLevelItemCount() > 0) {
		QString cacheFile = generateCacheFileName(url, true);
		if (!cacheFile.isEmpty()) {
			QFile f(cacheFile);
			if (f.open(QIODevice::WriteOnly)) {
				f.write(data);
			}
		}
	}
}

PodcastUrlPage::PodcastUrlPage(QWidget* p)
	: PodcastPage(p, tr("URL"))
{
	QBoxLayout* searchLayout = new QBoxLayout(QBoxLayout::LeftToRight);
	QBoxLayout* viewLayout = new QBoxLayout(QBoxLayout::LeftToRight);
	QBoxLayout* mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
	searchLayout->setContentsMargins(0, 0, 0, 0);
	viewLayout->setContentsMargins(0, 0, 0, 0);
	mainLayout->setContentsMargins(0, 0, 0, 0);
	urlEntry = new LineEdit(p);
	urlEntry->setPlaceholderText(tr("Enter podcast URL..."));
	QPushButton* loadButton = new QPushButton(tr("Load"), p);
	FlatToolButton* pathReq = new FlatToolButton(p);
	pathReq->setIcon(Icon::fa(fa::fa_regular, fa::fa_folder));
	pathReq->setToolTip(tr("Load local podcast file"));
	QWidget::setTabOrder(urlEntry, loadButton);
	QWidget::setTabOrder(loadButton, tree);
	searchLayout->addWidget(urlEntry);
	searchLayout->addWidget(loadButton);
	searchLayout->addWidget(pathReq);
	viewLayout->addWidget(tree, 1);
	viewLayout->addWidget(text, 0);
	mainLayout->addWidget(new QLabel(tr("Enter podcast URL below, and press 'Load', or press the folder icon to load a local podcast file."), this));
	mainLayout->addLayout(searchLayout);
	mainLayout->addLayout(viewLayout);
	connect(urlEntry, SIGNAL(returnPressed()), SLOT(loadUrl()));
	connect(loadButton, SIGNAL(clicked()), SLOT(loadUrl()));
	connect(pathReq, SIGNAL(clicked()), SLOT(openPath()));
	icn = Icons::self()->rssListIcon;
}

void PodcastUrlPage::showEvent(QShowEvent* e)
{
	urlEntry->setFocus();
	QWidget::showEvent(e);
}

void PodcastUrlPage::loadUrl()
{
	QString text = urlEntry->text().trimmed();
	if (text.isEmpty()) {
		return;
	}

	QUrl url(PodcastService::fixUrl(text));
	if (url == currentUrl) {
		return;
	}

	if (!PodcastService::isUrlOk(url)) {
		emit error(tr("Invalid URL!"));
	}
	else {
		currentUrl = url;
		fetch(url);
	}
}

void PodcastUrlPage::openPath()
{
	QString path = QFileDialog::getOpenFileName(this, QString(), QDir::homePath(), tr("Podcasts (*.xml, *.rss, *.opml"));

	if (!path.isEmpty()) {
		QFile f(path);
		if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
			parse(&f);
		}
		else {
			emit error(tr("Failed to read file!"));
		}
	}
}
void PodcastUrlPage::parseResonse(QIODevice* dev)
{
	if (!dev) {
		emit error(tr("Failed to fetch podcast!"));
		return;
	}
	parse(dev);
}

void PodcastUrlPage::parse(QIODevice* dev)
{
	auto data = dev->readAll();
	QBuffer buf(&data);
	buf.open(QIODevice::ReadOnly | QIODevice::Text);
	RssParser::Channel ch = RssParser::parse(&buf, false, true);
	if (ch.isValid()) {
		if (ch.video) {
			emit error(tr("Cantata only supports audio podcasts! The URL entered contains only video podcasts."));
		}
		else {
			addPodcast(ch.name, currentUrl, ch.image, ch.description, QString(), nullptr);
		}
	}
	else {
		OpmlParser::Category parsed = OpmlParser::parse(data);
		if (parsed.categories.isEmpty() && parsed.podcasts.isEmpty()) {
			emit error(tr("Failed to parse podcast."));
		}
		else {
			if (1 == parsed.categories.count() && parsed.podcasts.isEmpty()) {
				parsed = parsed.categories.at(0);
			}
			for (const OpmlParser::Category& cat : parsed.categories) {
				addCategory(cat, nullptr);
			}
			for (const OpmlParser::Podcast& pod : parsed.podcasts) {
				addPodcast(pod, nullptr);
			}
		}
	}
}

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

PodcastSearchDialog::PodcastSearchDialog(PodcastService* s, QWidget* parent)
	: Dialog(parent, "PodcastSearchDialog", QSize(800, 600)), service(s)
{
	Utils::clearOldCache(constCacheDir, 2);

	iCount++;
	setButtons(User1 | Close);
	setButtonText(User1, tr("Subscribe"));

	QWidget* mainWidget = new QWidget(this);
	messageWidget = new MessageWidget(mainWidget);
	spacer = new QWidget(this);
	spacer->setFixedSize(Utils::layoutSpacing(this), 0);
	QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, mainWidget);
	layout->setContentsMargins(0, 0, 0, 0);
	layout->setSpacing(0);

	pageWidget = new PageWidget(mainWidget);
	QList<PodcastPage*> pages;

	layout->addWidget(messageWidget);
	layout->addWidget(spacer);
	layout->addWidget(pageWidget);

	PodcastUrlPage* urlPage = new PodcastUrlPage(pageWidget);
	pageWidget->addPage(urlPage, tr("Enter URL"), urlPage->icon(), tr("Manual podcast URL"));
	pages << urlPage;

	ITunesSearchPage* itunes = new ITunesSearchPage(pageWidget);
	pageWidget->addPage(itunes, tr("Search %1").arg(itunes->name()), itunes->icon(), tr("Search for podcasts on %1").arg(itunes->name()));
	pages << itunes;

	GPodderSearchPage* gpodder = new GPodderSearchPage(pageWidget);
	pageWidget->addPage(gpodder, tr("Search %1").arg(gpodder->name()), gpodder->icon(), tr("Search for podcasts on %1").arg(gpodder->name()));
	pages << gpodder;

	QSet<QString> loaded;
	pages << loadDirectories(Utils::dataDir(), false, loaded);
	pages << loadDirectories(QString(), true, loaded);

	for (PodcastPage* p : pages) {
		connect(p, SIGNAL(rssSelected(QUrl)), SLOT(rssSelected(QUrl)));
		connect(p, SIGNAL(error(QString)), SLOT(showError(QString)));
	}

	setCaption(tr("Add Podcast Subscription"));
	setMainWidget(mainWidget);
	setAttribute(Qt::WA_DeleteOnClose);
	enableButton(User1, false);
	setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

	if (-1 == maxImageSize) {
		maxImageSize = fontMetrics().height() * 8;
	}
	connect(service, SIGNAL(newError(QString)), this, SLOT(showError(QString)));
	connect(messageWidget, SIGNAL(visible(bool)), SLOT(msgWidgetVisible(bool)));
	connect(pageWidget, SIGNAL(currentPageChanged()), this, SLOT(pageChanged()));
	messageWidget->hide();
}

PodcastSearchDialog::~PodcastSearchDialog()
{
	iCount--;
	imageCache.clear();
}

void PodcastSearchDialog::rssSelected(const QUrl& url)
{
	currentUrl = url;
	enableButton(User1, currentUrl.isValid());
}

void PodcastSearchDialog::showError(const QString& msg)
{
	messageWidget->setError(msg);
}

void PodcastSearchDialog::showInfo(const QString& msg)
{
	messageWidget->setInformation(msg);
}

void PodcastSearchDialog::msgWidgetVisible(bool v)
{
	spacer->setFixedSize(spacer->width(), v ? spacer->width() : 0);
}

void PodcastSearchDialog::pageChanged()
{
	PageWidgetItem* pwi = pageWidget->currentPage();
	PodcastPage* page = pwi ? qobject_cast<PodcastPage*>(pwi->widget()) : nullptr;
	rssSelected(page ? page->currentRss() : QUrl());
}

QList<PodcastPage*> PodcastSearchDialog::loadDirectories(const QString& dir, bool isSystem, QSet<QString>& loaded)
{
	QList<PodcastPage*> pages;

	if (dir.isEmpty() && !isSystem) {
		return pages;
	}

	QFile file(isSystem ? QLatin1String(":podcast_directories.xml") : (dir + QLatin1String("/podcast_directories.xml")));

	if (file.open(QIODevice::ReadOnly)) {
		QXmlStreamReader reader(&file);
		while (!reader.atEnd()) {
			reader.readNext();
			if (reader.isStartElement() && QLatin1String("directory") == reader.name()) {
				QString url = reader.attributes().value(QLatin1String("url")).toString();
				if (!loaded.contains(url)) {
					QString iconName = reader.attributes().value(QLatin1String("icon")).toString();
					QIcon icon;

					if (iconName.isEmpty()) {
						icon = Icon::fa(fa::fa_solid, fa::fa_rss_square);
					}
					else if (iconName.startsWith(":")) {
						icon = Icon::fa(iconName);
					}
					else {
						icon.addFile(iconName);
					}

					if (icon.isNull()) {
						icon = Icon::fa(fa::fa_solid, fa::fa_rss_square);
					}

					OpmlBrowsePage* page = new OpmlBrowsePage(pageWidget,
					                                          reader.attributes().value(QLatin1String("name")).toString(),
					                                          icon,
					                                          QUrl(url));
					pageWidget->addPage(page, tr("Browse %1").arg(page->name()), page->icon(), tr("Browse %1 podcasts").arg(page->name()));
					pages << page;
					loaded.insert(url);
				}
			}
		}
	}
	return pages;
}

void PodcastSearchDialog::slotButtonClicked(int button)
{
	switch (button) {
	case User1: {
		QUrl fixed = PodcastService::fixUrl(currentUrl);
		if (service->subscribedToUrl(fixed)) {
			showError(tr("You are already subscribed to this podcast!"));
		}
		else {
			service->addUrl(fixed);
			showInfo(tr("Subscription added"));
		}
		break;
	}
	case Close:
		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;
	}
}

#include "moc_podcastsearchdialog.cpp"
