/* Lookup.cpp */

/* Copyright (C) 2011-2024 Michael Lugmair (Lucio Carreras)
 *
 * This file is part of sayonara player
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Lookup.cpp
 *
 *  Created on: May 21, 2011
 *      Author: Michael Lugmair (Lucio Carreras)
 */

#include "LyricLookup.h"
#include "LyricServer.h"
#include "LyricsServerProvider.h"
#include "LyricWebpageParser.h"
#include "LyricServerJsonWriter.h"

#include "Utils/WebAccess/WebClientImpl.h"
#include "Utils/Algorithm.h"
#include "Utils/Logger/Logger.h"
#include "Utils/StandardPaths.h"

#include <QStringList>
#include <QRegExp>
#include <QMap>
#include <QFile>
#include <QDir>

using namespace Lyrics;

namespace
{
	QString extractUrlFromSearchResults(Server* server, const QString& website)
	{
		if(website.isEmpty())
		{
			return {};
		}

		QString url;

		auto re = QRegExp(server->searchResultRegex());
		re.setMinimal(true);
		if(re.indexIn(website) > 0)
		{
			const auto parsedUrl = re.cap(1);
			url = server->searchResultUrlTemplate();
			url.replace("<SERVER>", server->address());
			url.replace("<SEARCH_RESULT_CAPTION>", parsedUrl);
		}

		return url;
	}

	QString calcUrl(Server* server, const QString& urlTemplate, const QString& artist, const QString& song)
	{
		const auto replacedArtist = Server::applyReplacements(artist, server->replacements());
		const auto replacedSong = Server::applyReplacements(song, server->replacements());

		auto url = urlTemplate;
		url.replace("<SERVER>", server->address());
		url.replace("<FIRST_ARTIST_LETTER>", QString(replacedArtist).trimmed());
		url.replace("<ARTIST>", replacedArtist.trimmed());
		url.replace("<TITLE>", replacedSong.trimmed());

		return server->isLowercase()
		       ? url.toLower()
		       : url;
	}

	QString calcSearchUrl(Server* server, const QString& artist, const QString& song)
	{
		return calcUrl(server, server->searchUrlTemplate(), artist, song);
	}

	QString calcServerUrl(Server* server, const QString& artist, const QString& song)
	{
		return calcUrl(server, server->directUrlTemplate(), artist, song);
	}

	QString getLyricHeader(const QString& artist, const QString& title, const QString& serverName, const QString& url)
	{
		return QString("<b>%1 - %2</b><br>%3: %4")
			.arg(artist)
			.arg(title)
			.arg(serverName)
			.arg(url);
	}

	QMap<QString, QString> getRegexConversions()
	{
		return {{"$", "\\$"},
		        {"*", "\\*"},
		        {"+", "\\+"},
		        {"?", "\\?"},
		        {"[", "\\["},
		        {"]", "\\]"},
		        {"(", "\\("},
		        {")", "\\)"},
		        {"{", "\\{"},
		        {"}", "\\}"},
		        {"^", "\\^"},
		        {"|", "\\|"},
		        {".", "\\."}};
	}

	class LyricsWebClient :
		public WebClientImpl
	{
		public:
			LyricsWebClient(Server* server, QObject* parent) :
				WebClientImpl(parent),
				m_server {server} {}

			~LyricsWebClient() override = default;

			[[nodiscard]] Server* server() const { return m_server; }

		private:
			Server* m_server;
	};
}

struct Lookup::Private
{
	QString artist;
	QString title;
	QString lyricsData;
	QString lyricHeader;

	WebClient* currentWebClient {nullptr};
	bool hasError {false};
};

Lookup::Lookup(QObject* parent) :
	QObject(parent),
	m {Pimpl::make<Lookup::Private>()} {}

Lookup::~Lookup() = default;

void Lookup::run(const QString& artist, const QString& title, Server* server)
{
	m->artist = artist;
	m->title = title;

	if(m->artist.isEmpty() && m->title.isEmpty())
	{
		m->lyricsData = "No track selected";
		return;
	}

	m->lyricsData.clear();

	if(server->canFetchDirectly())
	{
		const auto url = calcServerUrl(server, artist, title);
		callWebsite(url, server);
	}

	else if(server->canSearch())
	{
		const auto url = calcSearchUrl(server, artist, title);
		startSearch(url, server);
	}

	else
	{
		spLog(Log::Warning, this) << "Search server " << server->name() << " cannot do anything at all!";
		emit sigFinished();
	}
}

void Lookup::startSearch(const QString& url, Server* server)
{
	spLog(Log::Debug, this) << "Search Lyrics from " << url;

	auto* webClient = new LyricsWebClient(server, this);
	connect(webClient, &WebClient::sigFinished, this, &Lookup::searchFinished);
	webClient->run(url);
}

void Lookup::searchFinished()
{
	auto* webClient = dynamic_cast<LyricsWebClient*>(sender());
	auto* server = webClient->server();

	const auto data = webClient->data();
	const auto url = extractUrlFromSearchResults(server, QString::fromLocal8Bit(data));

	if(!url.isEmpty())
	{
		callWebsite(url, server);
	}

	else
	{
		spLog(Log::Debug, this) << "Search Lyrics not successful ";
		m->lyricsData = tr("Cannot fetch lyrics from %1").arg(webClient->url());
		m->hasError = true;
		emit sigFinished();
	}

	webClient->deleteLater();
}

void Lookup::callWebsite(const QString& url, Server* server)
{
	stop();

	spLog(Log::Debug, this) << "Fetch Lyrics from " << url;

	m->currentWebClient = new LyricsWebClient(server, this);
	connect(m->currentWebClient, &WebClient::sigFinished, this, &Lookup::contentFetched);
	m->currentWebClient->run(url);
}

void Lookup::contentFetched()
{
	auto* webClient = dynamic_cast<LyricsWebClient*>(sender());
	auto* server = webClient->server();

	m->currentWebClient = nullptr;
	m->lyricHeader = getLyricHeader(m->artist, m->title, server->name(), webClient->url());

	m->hasError = (!webClient->hasData() || webClient->hasError());
	if(m->hasError)
	{
		m->lyricsData = tr("Cannot fetch lyrics from %1").arg(webClient->url());
	}

	else if(webClient->data().isEmpty())
	{
		m->lyricsData = tr("No lyrics found") + "<br />" + webClient->url();
	}

	else
	{
		m->lyricsData = Lyrics::WebpageParser::parseWebpage(webClient->data(), getRegexConversions(), server);
	}

	webClient->deleteLater();
	emit sigFinished();
}

void Lookup::stop()
{
	if(m->currentWebClient)
	{
		disconnect(m->currentWebClient, &WebClient::sigFinished, this, &Lookup::contentFetched);
		m->currentWebClient->stop();
	}
}

bool Lookup::hasError() const {	return m->hasError; }

QString Lookup::lyricHeader() const { return m->lyricHeader; }

QString Lookup::lyricData() const { return m->lyricsData; }
