/* GUI_AbstractStream.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/>.
 */

#include "AbstractStationPlugin.h"

#include "Components/Playlist/PlaylistInterface.h"
#include "Components/Streaming/Streams/AbstractStationHandler.h"
#include "Gui/Utils/Icons.h"
#include "Gui/Utils/MenuTool/MenuToolButton.h"
#include "Gui/Utils/SignalAliases.h"
#include "Gui/Utils/Style.h"
#include "Gui/Utils/Widgets/ProgressBar.h"
#include "Utils/Algorithm.h"
#include "Utils/Language/Language.h"
#include "Utils/Logger/Logger.h"
#include "Utils/Message/Message.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Streams/Station.h"

#include <QComboBox>
#include <QPushButton>

namespace
{
	QString streamsAndPodcastString()
	{
		return QString("%1 && %2").arg(Lang::get(Lang::Streams)).arg(Lang::get(Lang::Podcasts));
	}
}

namespace Gui
{
	struct AbstractStationPlugin::Private
	{
		Playlist::Creator* playlistCreator;
		AbstractStationHandler* stationHandler;
		ProgressBar* loadingBar {nullptr};
		QComboBox* comboStream {nullptr};
		QPushButton* btnPlayStop {nullptr};
		MenuToolButton* btnTool {nullptr};
		bool searching {false};

		Private(Playlist::Creator* playlistCreator, AbstractStationHandler* stationHandler) :
			playlistCreator(playlistCreator),
			stationHandler(stationHandler) {}
	};

	AbstractStationPlugin::AbstractStationPlugin(
		Playlist::Creator* playlistCreator, AbstractStationHandler* stationHandler, QWidget* parent) :
		PlayerPlugin::Base(parent),
		m {Pimpl::make<Private>(playlistCreator, stationHandler)} {}

	AbstractStationPlugin::~AbstractStationPlugin() = default;

	void AbstractStationPlugin::assignUiVariables()
	{
		m->comboStream = comboStream();
		m->btnPlayStop = btnPlay();
		m->btnTool = btnMenu();
		m->loadingBar = new ProgressBar(this);
	}

	void AbstractStationPlugin::initUi()
	{
		m->btnPlayStop->setFocusPolicy(Qt::StrongFocus);
		m->btnTool->showActions(ContextMenuEntries(ContextMenu::EntryNew));
		m->btnTool->registerPreferenceAction(new StreamPreferenceAction(m->btnTool));
		m->btnTool->registerPreferenceAction(new StreamRecorderPreferenceAction(m->btnTool));

		setTabOrder(m->comboStream, m->btnPlayStop);
		initConnections();
		setupStations();
		skinChanged();
		setSearching(false);
	}

	void AbstractStationPlugin::initConnections()
	{
		connect(m->btnPlayStop, &QPushButton::clicked, this, &AbstractStationPlugin::playStopClicked);
		connect(m->btnTool, &MenuToolButton::sigEdit, this, &AbstractStationPlugin::editClicked);
		connect(m->btnTool, &MenuToolButton::sigDelete, this, &AbstractStationPlugin::deleteClicked);
		connect(m->btnTool, &MenuToolButton::sigNew, this, &AbstractStationPlugin::newClicked);
		connect(m->btnTool, &MenuToolButton::sigSave, this, &AbstractStationPlugin::saveClicked);
		connect(m->comboStream, Gui::comboActivatedInt, this, &AbstractStationPlugin::currentIndexChanged);
		connect(m->stationHandler, &AbstractStationHandler::sigError, this, &AbstractStationPlugin::errorReceived);
		connect(m->stationHandler, &AbstractStationHandler::sigDataAvailable, this, [this]() { setSearching(false); });
		connect(m->stationHandler, &AbstractStationHandler::sigStopped, this, [this]() { setSearching(false); });
		connect(m->stationHandler, &AbstractStationHandler::sigUrlCountExceeded, this,
		        &AbstractStationPlugin::urlCountExceeded);
	}

	void AbstractStationPlugin::currentIndexChanged(const int /*index*/)
	{
		m->comboStream->setToolTip(currentUrl());

		configureToolButton();
		checkPlayStopButton();
	}

	void AbstractStationPlugin::restorePreviousIndex(const QString& name)
	{
		if(m->comboStream->count() == 0)
		{
			currentIndexChanged(-1);
			return;
		}

		const auto index = std::max(0, m->comboStream->findText(name));
		m->comboStream->setCurrentIndex(index);
		currentIndexChanged(index);
	}

	void AbstractStationPlugin::setupStations()
	{
		m->comboStream->clear();

		const auto stations = m->stationHandler->getAllStations();
		for(const auto& station: stations)
		{
			m->comboStream->addItem(station->name(), station->url());
		}

		currentIndexChanged(m->comboStream->currentIndex());
	}

	void AbstractStationPlugin::configureToolButton()
	{
		const auto isIndexValid = (m->comboStream->currentIndex() >= 0);
		const auto isTemporary = m->stationHandler->isTemporary(currentName());

		m->btnTool->showAction(ContextMenu::EntrySave, isIndexValid && isTemporary);
		m->btnTool->showAction(ContextMenu::EntryEdit, isIndexValid && !isTemporary);
		m->btnTool->showAction(ContextMenu::EntryDelete, isIndexValid);
	}

	void AbstractStationPlugin::playStopClicked()
	{
		if(m->searching)
		{
			checkPlayStopButton();
			m->stationHandler->stop();
			return;
		}

		startPlayback();
	}

	void AbstractStationPlugin::checkPlayStopButton()
	{
		if(m->searching)
		{
			m->btnPlayStop->setEnabled(true);
		}

		else
		{
			m->btnPlayStop->setEnabled(currentUrl().size() >= 8);
		}

		m->btnPlayStop->setText(m->searching ? Lang::get(Lang::Stop) : Lang::get(Lang::Listen));
	}

	void AbstractStationPlugin::startPlayback()
	{
		const auto stationName = currentName().isEmpty() ? titleFallbackName() : currentName();
		if(const auto station = m->stationHandler->station(stationName); station)
		{
			setSearching(true);

			if(const auto parsed = m->stationHandler->parseStation(station); !parsed)
			{
				spLog(Log::Warning, this) << "Stream Handler busy";
				setSearching(false);
			}
		}
	}

	void AbstractStationPlugin::showConfigDialog(const QString& name, const StationPtr& station,
	                                             const GUI_ConfigureStation::Mode mode,
	                                             std::function<bool(GUI_ConfigureStation*)>&& callback)
	{
		auto* configDialog = createConfigDialog();
		connect(configDialog, &Dialog::accepted, this, [this, configDialog, mode, callback = std::move(callback)]() {
			const auto success = callback(configDialog);
			if(success)
			{
				setupStations();
				restorePreviousIndex(configDialog->configuredStation()->name());
				configDialog->deleteLater();
			}

			else
			{
				spLog(Log::Warning, this) << "Could not touch radio station (Mode " << static_cast<int>(mode) << ")";
				configDialog->open();
			}
		});

		connect(configDialog, &Dialog::rejected, configDialog, &QObject::deleteLater);

		configDialog->open();
		configDialog->initUi();
		configDialog->setMode(name, mode);
		configDialog->configureWidgets(station);
	}

	void AbstractStationPlugin::newClicked()
	{
		showConfigDialog(titleFallbackName(), nullptr, GUI_ConfigureStation::Mode::New, [this](auto* configDialog) {
			const auto station = configDialog->configuredStation();
			if(!station->isAnonymous())
			{
				if(const auto isNameValid = (m->comboStream->findText(station->name()) < 0); !isNameValid)
				{
					configDialog->setError(tr("Please choose another name"));
					return false;
				}
			}

			createStation(station, station->isAnonymous());
			return true;
		});
	}

	void AbstractStationPlugin::saveClicked() // temporary -> non-temporary
	{
		if(const auto station = m->stationHandler->station(currentName()); station && station->isAnonymous())
		{
			showConfigDialog(currentName(), station, GUI_ConfigureStation::Mode::Edit, [this](auto* configDialog) {
				return m->stationHandler->addNewStation(configDialog->configuredStation());
			});
		}
	}

	void AbstractStationPlugin::editClicked()
	{
		if(const auto station = m->stationHandler->station(currentName()); station && !station->isAnonymous())
		{
			showConfigDialog(currentName(), station, GUI_ConfigureStation::Mode::Edit, [this](auto* configDialog) {
				return m->stationHandler->updateStation(currentName(), configDialog->configuredStation());
			});
		}
	}

	void AbstractStationPlugin::deleteClicked()
	{
		const auto answer = Message::question_yn(tr("Do you really want to delete %1").arg(currentName()) + "?");
		if(answer == Message::Answer::Yes)
		{
			const auto success = m->stationHandler->removeStation(currentName());
			spLog(Log::Info, this) << "Delete " << currentName() << success;
		}

		setupStations();
	}

	void AbstractStationPlugin::createStation(const StationPtr& station, const bool temporary)
	{
		m->stationHandler->addTemporaryStation(station);
		m->comboStream->addItem(station->name(), station->url());
		m->comboStream->setCurrentText(station->name());
		currentIndexChanged(m->comboStream->currentIndex()); // Also trigger menu re-creation

		if(!temporary)
		{
			m->stationHandler->addNewStation(station);
		}
		else
		{
			startPlayback();
		}
	}

	void AbstractStationPlugin::urlCountExceeded(const int urlCount, const int maxUrlCount)
	{
		Message::error(QString("Found %1 urls").arg(urlCount) + "<br />" +
		               QString("Maximum number is %1").arg(maxUrlCount)
		);

		setSearching(false);
	}

	void AbstractStationPlugin::errorReceived()
	{
		setSearching(false);

		const auto question = QString("%1\n%2\n\n%3")
			.arg(tr("Cannot open stream"))
			.arg(currentUrl())
			.arg(Lang::get(Lang::Retry).question());

		const auto answer = Message::question_yn(question);
		if(answer == Message::Answer::Yes)
		{
			startPlayback();
		}
	}

	bool AbstractStationPlugin::hasLoadingBar() const { return true; }

	void AbstractStationPlugin::retranslate()
	{
		checkPlayStopButton();
	}

	void AbstractStationPlugin::skinChanged()
	{
		if(isUiInitialized())
		{
			setSearching(m->searching);
			btnPlay()->setIcon(Gui::icon(Gui::Play));
		}
	}

	void AbstractStationPlugin::setSearching(const bool isSearching)
	{
		m->loadingBar->setVisible(isSearching);
		m->searching = isSearching;

		checkPlayStopButton();
	}

	QString AbstractStationPlugin::currentName() const { return m->comboStream->currentText(); }

	QString AbstractStationPlugin::currentUrl() const { return m->comboStream->currentData().toString(); }

	StreamPreferenceAction::StreamPreferenceAction(QWidget* parent) :
		PreferenceAction(streamsAndPodcastString(), "streams", parent) {}

	StreamPreferenceAction::~StreamPreferenceAction() = default;

	QString StreamPreferenceAction::identifier() const { return "streams"; }

	QString StreamPreferenceAction::displayName() const { return streamsAndPodcastString(); }
}