/* EngineHandler.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 "EngineHandler.h"
#include "Engine.h"

#include "Components/PlayManager/PlayManager.h"
#include "Interfaces/CoverDataProvider.h"
#include "Interfaces/Engine/AudioDataReceiver.h"
#include "Interfaces/Engine/CoverDataReceiver.h"
#include "Utils/Algorithm.h"
#include "Utils/Logger/Logger.h"
#include "Utils/Message/Message.h"
#include "Utils/MetaData/MetaData.h"

#include <QString>
#include <QByteArray>

#include <set>

using Engine::Handler;

namespace
{
	template<typename T>
	void unregisterReceiver(T* receiver, std::set<T*>& receivers)
	{
		auto it = receivers.find(receiver);
		if(it != receivers.end())
		{
			receivers.erase(it);
		}
	}
}

struct Handler::Private
{
	std::set<RawAudioDataReceiver*> rawSoundReceiver;
	std::set<LevelDataReceiver*> levelReceivers;
	std::set<SpectrumDataReceiver*> spectrumReceivers;
	std::set<CoverDataReceiver*> coverReceivers;

	Engine* engine;

	Private(Handler* engineHandler, const std::shared_ptr<Util::FileSystem>& fileSystem,
	        const std::shared_ptr<Tagging::TagWriter>& tagWriter,
	        const std::shared_ptr<PipelineFactory>& pipelineFactory) :
		engine(::Engine::createEngine(fileSystem, tagWriter, pipelineFactory, engineHandler)) {}
};

Handler::Handler(const std::shared_ptr<Util::FileSystem>& fileSystem,
                 const std::shared_ptr<Tagging::TagWriter>& tagWriter,
                 const std::shared_ptr<PipelineFactory>& pipelineFactory,
                 PlayManager* playManager) :
	CoverDataProvider(),
	m {Pimpl::make<Private>(this, fileSystem, tagWriter, pipelineFactory)}
{
	connect(playManager, &PlayManager::sigPlaystateChanged, this, &Handler::playstateChanged);
	connect(playManager, &PlayManager::sigCurrentTrackChanged, this, [this](const auto& track) {
		m->engine->changeTrack(track);
	});
	connect(playManager, &PlayManager::sigSeekedAbsoluteMs, m->engine, &Engine::jumpAbsMs);
	connect(playManager, &PlayManager::sigSeekedRelative, m->engine, &Engine::jumpRel);
	connect(playManager, &PlayManager::sigSeekedRelativeMs, m->engine, &Engine::jumpRelMs);
	connect(playManager, &PlayManager::sigRecording, m->engine, &Engine::setStreamRecorderRecording);

	reloadReceivers();

	const auto& currentTrack = playManager->currentTrack();
	if(!currentTrack.filepath().isEmpty())
	{
		m->engine->changeTrack(currentTrack);
	}

	connect(m->engine, &Engine::sigDataAvailable, this, &Handler::setAudioData);
	connect(m->engine, &Engine::sigCoverDataAvailable, this, &Handler::setCoverData);
	connect(m->engine, &Engine::sigError, playManager, &PlayManager::error);
	connect(m->engine, &Engine::sigCurrentPositionChanged, playManager, &PlayManager::setCurrentPositionMs);
	connect(m->engine, &Engine::sigTrackFinished, playManager, &PlayManager::setTrackFinished);
	connect(m->engine, &Engine::sigTrackReady, playManager, &PlayManager::setTrackReady);
	connect(m->engine, &Engine::sigBuffering, playManager, &PlayManager::buffering);

	connect(m->engine, &Engine::sigDurationChanged, this, [playManager](const auto& track) {
		playManager->changeDuration(track.durationMs());
	});

	connect(m->engine, &Engine::sigBitrateChanged, this, [playManager](const auto& track) {
		playManager->changeBitrate(track.bitrate());
	});

	connect(m->engine, &Engine::sigMetadataChanged, this, [playManager](const auto& track) {
		playManager->changeCurrentMetadata(track);
	});

	connect(m->engine, &Engine::sigSpectrumChanged, this, &Handler::spectrumChanged);
	connect(m->engine, &Engine::sigLevelChanged, this, &Handler::levelChanged);
}

Handler::~Handler() = default;

bool Handler::isValid() const
{
	return (m->engine != nullptr);
}

void Handler::playstateChanged(PlayState state)
{
	switch(state)
	{
		case PlayState::Playing:
			m->engine->play();
			break;

		case PlayState::Paused:
			m->engine->pause();
			break;

		case PlayState::Stopped:
			m->engine->stop();
			break;

		default:
			return;
	}
}

void Handler::registerSpectrumReceiver(SpectrumDataReceiver* receiver)
{
	m->spectrumReceivers.insert(receiver);
	reloadReceivers();
}

void Engine::Handler::unregisterSpectrumReceiver(SpectrumDataReceiver* spectrumReceiver)
{
	unregisterReceiver(spectrumReceiver, m->spectrumReceivers);
}

void Engine::Handler::setSpectrumData(const std::vector<float>& spectrum)
{
	for(auto* receiver: m->spectrumReceivers)
	{
		if(receiver->isActive())
		{
			receiver->setSpectrum(spectrum);
		}
	}
}

void Handler::spectrumChanged()
{
	setSpectrumData(m->engine->spectrum());
}

void Handler::spectrumActiveChanged(bool /*b*/)
{
	reloadReceivers();
}

void Handler::registerLevelReceiver(LevelDataReceiver* receiver)
{
	m->levelReceivers.insert(receiver);
	reloadReceivers();
}

void Engine::Handler::unregisterLevelReceiver(LevelDataReceiver* levelReceiver)
{
	unregisterReceiver(levelReceiver, m->levelReceivers);
}

void Engine::Handler::setLevelData(float left, float right)
{
	for(auto* receiver: m->levelReceivers)
	{
		if(receiver->isActive())
		{
			receiver->setLevel(left, right);
		}
	}
}

void Handler::levelChanged()
{
	const auto [left, right] = m->engine->level();
	setLevelData(left, right);
}

void Handler::levelActiveChanged(bool /*b*/)
{
	reloadReceivers();
}

void Handler::reloadReceivers()
{
	const auto isSpectrumActive = Util::Algorithm::contains(m->spectrumReceivers, [](auto* receiver) {
		return receiver->isActive();
	});

	const auto isLevelActive = Util::Algorithm::contains(m->levelReceivers, [](auto* receiver) {
		return receiver->isActive();
	});

	m->engine->setVisualizerEnabled(isLevelActive, isSpectrumActive);
}

void Handler::setEqualizer(int band, int value)
{
	if(m->engine)
	{
		m->engine->setEqualizer(band, value);
	}
}

void Engine::Handler::registerCoverReceiver(CoverDataReceiver* coverDataReceiver)
{
	m->coverReceivers.insert(coverDataReceiver);
}

void Engine::Handler::unregisterCoverReceiver(CoverDataReceiver* coverDataReceiver)
{
	unregisterReceiver(coverDataReceiver, m->coverReceivers);
}

void Engine::Handler::setCoverData(const QByteArray& imageData, const QString& mimeData)
{
	for(auto* receiver: m->coverReceivers)
	{
		if(receiver->isActive())
		{
			receiver->setCoverData(imageData, mimeData);
		}
	}
}

void Engine::Handler::registerAudioDataReceiver(RawAudioDataReceiver* receiver)
{
	m->rawSoundReceiver.insert(receiver);
	m->engine->setBroadcastEnabled(!m->rawSoundReceiver.empty());
}

void Engine::Handler::unregisterAudioDataReceiver(RawAudioDataReceiver* receiver)
{
	unregisterReceiver(receiver, m->rawSoundReceiver);
	m->engine->setBroadcastEnabled(!m->rawSoundReceiver.empty());
}

void Engine::Handler::setAudioData(const QByteArray& data)
{
	for(auto* receiver: qAsConst(m->rawSoundReceiver))
	{
		receiver->writeAudioData(data);
	}
}
