/* ${CLASS_NAME}.h */
/*
 * Copyright (C) 2011-2024 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 "TaggingExtraFields.h"
#include "TaggingUtils.h"

#include "ID3v2/Popularimeter.h"
#include "ID3v2/Discnumber.h"
#include "ID3v2/AlbumArtist.h"
#include "Xiph/AlbumArtist.h"
#include "Xiph/RatingFrame.h"
#include "Xiph/FmpsRatingFrame.h"
#include "Xiph/DiscnumberFrame.h"
#include "MP4/AlbumArtist.h"
#include "MP4/DiscnumberFrame.h"
#include "MP4/PopularimeterFrame.h"

namespace Tagging
{
	namespace
	{
		template<typename Frame_t, typename Data_t, typename Tag>
		std::optional<Data_t> tryToRead(Tag* tag)
		{
			if (tag)
			{
				Data_t model;
				if (const auto success = Frame_t(tag).read(model); success)
				{
					return model;
				}
			}

			return std::nullopt;
		}

		template<typename Frame_t, typename Data_t, typename Tag>
		void tryToWrite(Tag* tag, const Data_t& model)
		{
			if (tag)
			{
				auto frame = Frame_t(tag);
				frame.write(model);
			}
		}

		template<typename... Frame_ts>
		struct MultiFrameAccessor
		{
			template<typename Data_t, typename Tag>
			static std::optional<Data_t> readInternal(Tag* tag)
			{
				std::optional<Data_t> result;
				( (result = tryToRead<Frame_ts, Data_t>(tag), result.has_value()) || ... );
				return result;
			}

			template<typename Data_t, typename Tag>
			static void writeInternal(Tag* tag, const Data_t& data)
			{
				( tryToWrite<Frame_ts>(tag, data), ... );
			}
		};
	}

	template<typename Data_t, typename ID3_t, typename Xiph_t, typename MP4_t>
	struct FrameAccessor
	{
		static std::optional<Data_t> read(const ParsedTag& parsedTag)
		{
			if (parsedTag.type == TagType::ID3v2)
			{
				return ID3_t::template readInternal<Data_t>(parsedTag.id3Tag());
			}

			if (parsedTag.type == TagType::Xiph)
			{
				return Xiph_t::template readInternal<Data_t>(parsedTag.xiphTag());
			}

			if (parsedTag.type == TagType::MP4)
			{
				return MP4_t::template readInternal<Data_t>(parsedTag.mp4Tag());
			}

			return std::nullopt;
		}

		static void write(const ParsedTag& parsedTag, const Data_t& data)
		{
			if (parsedTag.type == TagType::ID3v2)
			{
				ID3_t::writeInternal(parsedTag.id3Tag(), data);
			}

			else if (parsedTag.type == TagType::Xiph)
			{
				Xiph_t::writeInternal(parsedTag.xiphTag(), data);
			}

			else if (parsedTag.type == TagType::MP4)
			{
				MP4_t::writeInternal(parsedTag.mp4Tag(), data);
			}
		}
	};

	using DiscnumberAccessor = FrameAccessor<Models::Discnumber,
		MultiFrameAccessor<ID3v2::DiscnumberFrame>,
		MultiFrameAccessor<Xiph::DiscnumberFrame>,
		MultiFrameAccessor<MP4::DiscnumberFrame> >;

	using AlbumArtistAccessor = FrameAccessor<QString,
		MultiFrameAccessor<ID3v2::AlbumArtistFrame>,
		MultiFrameAccessor<Xiph::AlbumArtistFrame>,
		MultiFrameAccessor<MP4::AlbumArtistFrame> >;

	using PopularimeterAccessor = FrameAccessor<Models::Popularimeter,
		MultiFrameAccessor<ID3v2::PopularimeterFrame>,
		MultiFrameAccessor<Xiph::FmpsUserRatingFrame, Xiph::FmpsRatingFrame, Xiph::RatingFrame>,
		MultiFrameAccessor<MP4::ITunesRatingFrame, MP4::MediaMonkeyRateFrame> >;

	std::optional<Models::Discnumber> readDiscnumber(const ParsedTag& parsedTag)
	{
		return DiscnumberAccessor::read(parsedTag);
	}

	void writeDiscnumber(const ParsedTag& parsedTag, const Models::Discnumber& discnumber)
	{
		DiscnumberAccessor::write(parsedTag, discnumber);
	}

	std::optional<QString> readAlbumArtist(const ParsedTag& parsedTag)
	{
		return AlbumArtistAccessor::read(parsedTag);
	}

	void writeAlbumArtist(const ParsedTag& parsedTag, const QString& albumArtist)
	{
		AlbumArtistAccessor::write(parsedTag, albumArtist);
	}

	std::optional<Models::Popularimeter> readPopularimeter(const ParsedTag& parsedTag)
	{
		return PopularimeterAccessor::read(parsedTag);
	}

	void writePopularimeter(const ParsedTag& parsedTag, const Models::Popularimeter& popularimeter)
	{
		PopularimeterAccessor::write(parsedTag, popularimeter);
	}
}
