// clazy:excludeall=non-pod-global-static

/* Icons.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 "Icons.h"

#include "Gui/Utils/GuiUtils.h"
#include "Gui/Utils/Style.h"

#include "Utils/Algorithm.h"
#include "Utils/Settings/Settings.h"

#include <QDirIterator>
#include <QIcon>
#include <QMap>
#include <QPair>
#include <optional>
#include <unordered_map>

namespace
{
	struct IconInfo
	{
		Gui::IconName name;
		const char* xdgName;
		const char* darkName;
	};

	QString standardTheme;
	std::unordered_map<Gui::IconName, IconInfo> iconInfoMap;

	constexpr const auto iconInfos = std::array {
		IconInfo {Gui::AudioFile, "audio-x-generic", "cd"},
		IconInfo {Gui::Cd, "", "cd"},
		IconInfo {Gui::Cds, "", "cds"},
		IconInfo {Gui::Append, "list-add", "append"},
		IconInfo {Gui::Backward, "media-skip-backward", "bwd"},
		IconInfo {Gui::Clear, "edit-clear", ""},
		IconInfo {Gui::Close, "window-close", "tool_grey"},
		IconInfo {Gui::Delete, "edit-delete", ""},
		IconInfo {Gui::Dynamic, "", "dynamic"},
		IconInfo {Gui::Edit, "edit-copy", ""},
		IconInfo {Gui::Exit, "application-exit", "tool_grey"},
		IconInfo {Gui::File, "text-x-generic", ""},
		IconInfo {Gui::FileManager, "system-file-manager", ""},
		IconInfo {Gui::Folder, "folder", ""},
		IconInfo {Gui::FolderOpen, "folder-open", ""},
		IconInfo {Gui::Forward, "media-skip-forward", "fwd"},
		IconInfo {Gui::Gapless, "gapless", "gapless"},
		IconInfo {Gui::Grid, "view-grid", ""},
		IconInfo {Gui::ImageFile, "image-x-generic", ""},
		IconInfo {Gui::Info, "dialog-information", ""},
		IconInfo {Gui::LocalLibrary, "audio-x-generic", "append"},
		IconInfo {Gui::Lock, "security-high", ""},
		IconInfo {Gui::Logo, "", "logo.png"},
		IconInfo {Gui::Lyrics, "format-justify-left", ""},
		IconInfo {Gui::New, "document-new", ""},
		IconInfo {Gui::Next, "media-skip-forward", "fwd"},
		IconInfo {Gui::Open, "document-open", ""},
		IconInfo {Gui::Pause, "media-playback-pause", "pause"},
		IconInfo {Gui::Play, "media-playback-start", "play"},
		IconInfo {Gui::PlaySmall, "media-playback-start", ""},
		IconInfo {Gui::PlayBorder, "media-playback-start", ""},
		IconInfo {Gui::PlaylistFile, "text-x-generic", ""},
		IconInfo {Gui::Preferences, "applications-system", ""},
		IconInfo {Gui::Previous, "media-skip-backward", "bwd"},
		IconInfo {Gui::Record, "media-record", "rec"},
		IconInfo {Gui::Refresh, "view-refresh", ""},
		IconInfo {Gui::Remove, "list-remove", ""},
		IconInfo {Gui::Rename, "edit-copy", ""},
		IconInfo {Gui::Repeat1, "", "rep_1"},
		IconInfo {Gui::RepeatAll, "", "rep_all"},
		IconInfo {Gui::Save, "document-save", ""},
		IconInfo {Gui::SaveAs, "document-save-as", ""},
		IconInfo {Gui::Search, "edit-find", ""},
		IconInfo {Gui::Shuffle, "shuffle", "shuffle"},
		IconInfo {Gui::Shutdown, "system-shutdown", ""},
		IconInfo {Gui::Star, "", "star"},
		IconInfo {Gui::StarDisabled, "", "star_disabled"},
		IconInfo {Gui::Stop, "media-playback-stop", "stop"},
		IconInfo {Gui::Table, "format-justify-fill", ""},
		IconInfo {Gui::ToolDarkGrey, "", "tool_dark_grey"},
		IconInfo {Gui::ToolDisabled, "", "tool_disabled"},
		IconInfo {Gui::ToolGrey, "", "tool_grey"},
		IconInfo {Gui::Undo, "edit-undo", ""},
		IconInfo {Gui::Unlock, "security-low", ""},
		IconInfo {Gui::Vol1, "audio-volume-low", ""},
		IconInfo {Gui::Vol2, "audio-volume-medium", ""},
		IconInfo {Gui::Vol3, "audio-volume-high", ""},
		IconInfo {Gui::VolMute, "audio-volume-muted", ""},
	};

	std::optional<IconInfo> findIconInfo(const Gui::IconName iconName)
	{
		const auto it = iconInfoMap.find(iconName);
		if(it == iconInfoMap.end())
		{
			const auto* const listIt = ::Util::Algorithm::find(iconInfos, [&](const auto& info) {
				return (info.name == iconName);
			});

			if(listIt != iconInfos.end())
			{
				iconInfoMap.emplace(iconName, *listIt);
				return {*listIt};
			}

			return std::nullopt;
		}

		return it->second;
	}

	QIcon loadSystemThemeIcon(const QString& iconName)
	{
		auto icon = QIcon::fromTheme(iconName);
		if(icon.isNull())
		{
			icon = QIcon::fromTheme(iconName + "-symbolic");
		}

		return icon;
	}

	QStringList searchInResource(const QString& resourceName, const QString& filenameRegex)
	{
		const auto re = QRegExp(resourceName + "/" + filenameRegex);
		const auto themePath = QString(":/%1").arg(resourceName);

		QStringList paths;
		auto dirIterator = QDirIterator(themePath);
		while(dirIterator.hasNext())
		{
			const auto path = dirIterator.next();
			if(re.indexIn(path) >= 0)
			{
				paths << path;
			}
		}

		return paths;
	}

	struct IconResourceAccessor
	{
		virtual ~IconResourceAccessor() = default;

		[[nodiscard]] virtual QString regex(const QString& iconName) const = 0;
		[[nodiscard]] virtual QString themeName() const = 0;

		[[nodiscard]] QStringList paths(const QString& iconName) const
		{
			return searchInResource(themeName(), regex(iconName));
		}
	};

	struct MintYIconResourceAccessor :
		public IconResourceAccessor
	{
		~MintYIconResourceAccessor() override = default;

		[[nodiscard]] QString regex(const QString& iconName) const override
		{
			return QString("%1-[0-9]+.*").arg(iconName);
		}

		[[nodiscard]] QString themeName() const override { return "MintYIcons"; }
	};

	struct InternalIconResourceAccessor :
		public IconResourceAccessor
	{
		~InternalIconResourceAccessor() override = default;

		[[nodiscard]] QString regex(const QString& iconName) const override
		{
			return QString("%1\\.[a-z][a-z][a-z]$").arg(iconName);
		}

		[[nodiscard]] QString themeName() const override { return "Icons"; }
	};

	std::shared_ptr<IconResourceAccessor> createIconResourceAccessor(const Gui::Util::IconTheme iconTheme)
	{
		return (iconTheme == Gui::Util::IconTheme::Dark)
		       ? std::static_pointer_cast<IconResourceAccessor>(std::make_shared<MintYIconResourceAccessor>())
		       : std::static_pointer_cast<IconResourceAccessor>(std::make_shared<InternalIconResourceAccessor>());
	}

	QStringList pixmapPaths(const QString& iconName, const Gui::Util::IconTheme iconTheme)
	{
		if(iconName.trimmed().isEmpty())
		{
			return {};
		}

		const auto resourceAccessor = createIconResourceAccessor(iconTheme);
		const auto paths = resourceAccessor->paths(iconName);
		return paths.isEmpty()
		       ? InternalIconResourceAccessor().paths(iconName)
		       : paths;
	}

	QList<QPixmap> loadPixmaps(const QString& iconName, const Gui::Util::IconTheme iconTheme)
	{
		if(iconName.isEmpty())
		{
			return {};
		}

		const auto paths = pixmapPaths(iconName, iconTheme);
		auto pixmaps = QList<QPixmap> {};
		for(const auto& path: paths)
		{
			const auto pixmap = QPixmap(path);
			if(!pixmap.isNull())
			{
				pixmaps << pixmap;
			}
		}

		return pixmaps;
	}

	QIcon pixmapsToIcon(const QList<QPixmap>& pixmaps)
	{
		auto result = QIcon {};
		for(const auto& pixmap: pixmaps)
		{
			result.addPixmap(pixmap);
		}

		return result;
	}

	QList<QPixmap> iconToPixmaps(const QIcon& icon)
	{
		auto result = QList<QPixmap> {};
		const auto sizes = icon.availableSizes();
		::Util::Algorithm::transform(sizes, result, [&](const auto& size) {
			return icon.pixmap(size);
		});

		return result;
	}
}

namespace Gui
{
	QIcon icon(const IconName iconName, const IconMode mode)
	{
		const auto iconInfo = findIconInfo(iconName);
		if(!iconInfo)
		{
			return {};
		}

		QIcon icon;
		switch(mode)
		{
			case IconMode::Automatic:
				icon = Style::isDark()
				       ? pixmapsToIcon(loadPixmaps(iconInfo->xdgName, Gui::Util::Dark))
				       : loadSystemThemeIcon(iconInfo->xdgName);
				break;
			case IconMode::ForceStdIcon:
				icon = loadSystemThemeIcon(iconInfo->xdgName);
				break;
			case IconMode::ForceSayonaraIcon:
			default:
				break;
		}

		return icon.isNull()
		       ? pixmapsToIcon(loadPixmaps(iconInfo->darkName, Gui::Util::Internal))
		       : icon;
	}

	QIcon icon(const IconName iconName)
	{
		return (GetSetting(Set::Icon_ForceInDarkTheme))
		       ? icon(iconName, IconMode::ForceStdIcon)
		       : icon(iconName, IconMode::Automatic);
	}

	QPixmap pixmap(IconName iconName) { return pixmap(iconName, {}); }

	QPixmap pixmap(IconName iconName, const QSize& size) { return pixmap(iconName, size, true); }

	QPixmap pixmap(IconName iconName, const QSize& size, const bool keepAspectRatio)
	{
		return GetSetting(Set::Icon_ForceInDarkTheme)
		       ? pixmap(iconName, size, IconMode::ForceStdIcon, keepAspectRatio)
		       : pixmap(iconName, size, IconMode::Automatic, keepAspectRatio);
	}

	QPixmap pixmap(const IconName iconName, const QSize& size, const IconMode mode, const bool keepAspectRatio)
	{
		const auto fetchedIcon = icon(iconName, mode);
		auto pixmap = fetchedIcon.pixmap(size);
		if(pixmap.isNull())
		{
			auto pixmaps = iconToPixmaps(fetchedIcon);
			::Util::Algorithm::sort(pixmaps, [](const auto& p1, const auto& p2) {
				return (p1.width() > p2.width());
			});

			if(!pixmaps.isEmpty())
			{
				pixmap = pixmaps[0];
			}
		}

		return !pixmap.isNull() && size.isValid()
		       ? pixmap.scaled(size, (keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio))
		       : pixmap;
	}

	void changeTheme()
	{
		const static auto initialTheme = QIcon::themeName();
		const auto settingTheme = GetSetting(Set::Icon_Theme);
		const auto themeName = !settingTheme.isEmpty()
		                       ? settingTheme
		                       : initialTheme;

		QIcon::setThemeName(themeName);
	}

	QString defaultSystemTheme() { return standardTheme; }

	void setDefaultSystemTheme(const QString& themeName) { standardTheme = themeName; }
}