/* SimplePipeline.cpp, (Created on 16.01.2025) */

/* Copyright (C) 2011-2025 Michael Lugmair
 *
 * 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 "SimplePipeline.h"

#include "Components/Engine/Callbacks.h"
#include "Components/Engine/EngineUtils.h"
#include "Components/Engine/Pipeline.h"
#include "Components/Engine/PipelineExtensions/Changeable.h"
#include "Components/Engine/PipelineExtensions/PipelineInterfaces.h"
#include "Components/Engine/PipelineExtensions/DelayedPlayback.h"
#include "Components/Engine/PipelineExtensions/PositionAccessor.h"
#include "Utils/Logger/Logger.h"
#include "Utils/Settings/Settings.h"
#include "Utils/FileUtils.h"

#include <QTimer>

#include <gst/gst.h>

namespace Engine
{
	class SimplePipeline :
		public Pipeline,
		public PipelineExtensions::PlaystateController
	{
		Q_OBJECT
		public:
			SimplePipeline(QString name, QObject* parent) :
				Pipeline(parent),
				m_name {std::move(name)} {}

			~SimplePipeline() override
			{
				if(m_pipeline)
				{
					Utils::setState(m_pipeline, GST_STATE_NULL);
					gst_object_unref(GST_OBJECT(m_pipeline));
					m_pipeline = nullptr;
				}
			}

			bool init(Engine* engine) override
			{
				if(m_pipeline)
				{
					return true;
				}

				if(!createElements() || !addAndLinkElements())
				{
					return false;
				}

				configureElements();
				initCallbacks(engine);

				SetSetting(SetNoDB::MP3enc_found, Utils::isLameAvailable());
				SetSetting(SetNoDB::Pitch_found, Utils::isPitchAvailable());

				ListenSettingNoCall(Set::Engine_Sink, SimplePipeline::sinkChanged);

				return true;
			}

			void initCallbacks(Engine* engine)
			{
				auto* bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline));
				gst_bus_add_watch(bus, Callbacks::busStateChanged, engine);

				m_progressTimer = new QTimer(this);
				m_progressTimer->setTimerType(Qt::PreciseTimer);
				m_progressTimer->setInterval(Utils::getUpdateInterval()); // NOLINT(bugprone-narrowing-conversions)
				connect(m_progressTimer, &QTimer::timeout, this, [this]() {
					if(Utils::getState(m_pipeline) != GST_STATE_NULL)
					{
						Callbacks::positionChanged(this);
					}
				});

				m_progressTimer->start();
			}

			bool prepare(const QString& uri, const QString& userAgent) override
			{
				stop();

				m_trackContext.userAgent = userAgent;

				Utils::setInt64Value(G_OBJECT(m_source), "buffer-duration", GetSetting(Set::Engine_BufferSizeMS));
				Utils::setValues(G_OBJECT(m_source),
				                 "use-buffering", Util::File::isWWW(uri),
				                 "uri", uri.toUtf8().data());

				Utils::setState(m_pipeline, GST_STATE_PAUSED);

				return true;
			}

			void setVisualizerEnabled(const bool /*isLevelActive*/, const bool /*isSpectrumActive*/) override {}

			[[nodiscard]] bool isLevelVisualizerEnabled() const override { return false; }

			[[nodiscard]] bool isSpectrumVisualizerEnabled() const override { return false; }

			void setBroadcastingEnabled(const bool /*b*/) override {}

			[[nodiscard]] bool isBroadcastingEnabled() const override { return false; }

			[[nodiscard]] GstState state() const override { return Utils::getState(m_pipeline); }

			void checkPosition() override
			{
				emit sigPositionChangedMs(std::max<MilliSeconds>(0, m_positionAccessor->positionMs()));

				constexpr const MilliSeconds AboutToFinishThreshold = 300;
				if(m_positionAccessor->isAboutToFinish(AboutToFinishThreshold))
				{
					const auto timeToGo = m_positionAccessor->durationMs() - m_positionAccessor->positionMs();
					emit sigAboutToFinishMs(timeToGo);
				}
			}

			[[nodiscard]] bool hasElement(GstElement* e) const override
			{
				return Utils::hasElement(GST_BIN(m_pipeline), e);
			}

			void fadeIn() override {}

			void fadeOut() override {}

			void startDelayedPlayback(const MilliSeconds ms) override { m_delayedInvoker->playIn(ms); }

			void seekRelative(const double percent, const MilliSeconds duration) override
			{
				m_positionAccessor->seekRelative(percent, duration);
			}

			void seekAbsoluteMs(const MilliSeconds duration) override { m_positionAccessor->seekAbsoluteMs(duration); }

			void seekRelativeMs(const MilliSeconds ms) override
			{
				m_positionAccessor->seekAbsoluteMs(m_positionAccessor->positionMs() + ms);
			}

			[[nodiscard]] MilliSeconds duration() const override { return m_positionAccessor->durationMs(); }

			[[nodiscard]] MilliSeconds timeToGo() const override { return m_positionAccessor->timeToGo(); }

			void setEqualizerBand(const int /*band*/, const int /*value*/) override {}

		public slots: // NOLINT(*-redundant-access-specifiers)
			void play() override
			{
				Utils::setState(m_pipeline, GST_STATE_PLAYING);
			}

			void pause() override
			{
				Utils::setState(m_pipeline, GST_STATE_PAUSED);
			}

			void stop() override
			{
				Utils::setState(m_pipeline, GST_STATE_NULL);

				m_delayedInvoker->abortDelayedPlaying();
			}

		protected:
			void prepareForRecording() override {}

			void finishRecording() override {}

			void setRecordingPath(const QString& /*path*/) override {}

		private:
			bool createElements()
			{
				m_pipeline = gst_pipeline_new(m_name.toStdString().c_str());
				if(!Utils::testAndError(m_pipeline, "SimplePipeline: Pipeline could not be created"))
				{
					return false;
				}

				if(!Utils::createElement(&m_source, "uridecodebin", "src")) { return false; }
				if(!Utils::createElement(&m_audioConvert, "audioconvert")) { return false; }

				m_playbackSink = Utils::createSink(GetSetting(Set::Engine_Sink));
				m_delayedInvoker = PipelineExtensions::createDelayedPlaybackInvoker(this);
				m_positionAccessor = PipelineExtensions::createPositionAccessor(m_pipeline, m_audioConvert);

				return (m_playbackSink != nullptr);
			}

			bool addAndLinkElements()
			{
				Utils::addElements(GST_BIN(m_pipeline), {m_source, m_audioConvert, m_playbackSink});
				const auto success = Utils::linkElements({m_audioConvert, m_playbackSink});
				return Utils::testAndErrorBool(success, "SimplePipeline: Cannot link audio convert with sink");
			}

			void configureElements()
			{
				Utils::setState(m_pipeline, GST_STATE_NULL);

				g_signal_connect (m_source, "pad-added", G_CALLBACK(Callbacks::decodebinReady), m_audioConvert);
				g_signal_connect (m_source, "source-setup", G_CALLBACK(Callbacks::sourceReady), &m_trackContext);
			}

		private slots: // NOLINT(*-redundant-access-specifiers)
			void sinkChanged()
			{
				auto* newSink = Utils::createSink(GetSetting(Set::Engine_Sink));
				if(newSink)
				{
					PipelineExtensions::Changeable::replaceSink(m_playbackSink, newSink, m_audioConvert, m_pipeline);
					m_playbackSink = newSink;
				}
			}

		private: // NOLINT(*-redundant-access-specifiers)
			QString m_name;

			GstElement* m_pipeline = nullptr;
			GstElement* m_source = nullptr;
			GstElement* m_audioConvert = nullptr;
			GstElement* m_playbackSink = nullptr;

			std::shared_ptr<PipelineExtensions::DelayedPlaybackInvoker> m_delayedInvoker = nullptr;
			std::shared_ptr<PipelineExtensions::PositionAccessor> m_positionAccessor = nullptr;

			QTimer* m_progressTimer = nullptr;
			Callbacks::TrackContext m_trackContext;
	};

	SimplePipelineFactory::~SimplePipelineFactory() noexcept = default;

	std::shared_ptr<Pipeline> SimplePipelineFactory::createPipeline(const QString& name) const
	{
		return std::make_shared<SimplePipeline>(name, nullptr);
	}
} // Engine

#include "SimplePipeline.moc"
