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


/* GUI_PlaylistPreferences.cpp */

#include "GUI_PlaylistPreferences.h"
#include "Gui/Preferences/ui_GUI_PlaylistPreferences.h"
#include "Gui/Utils/Icons.h"

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

#include <QColorDialog>
#include <QPainter>
#include <QPalette>

namespace
{
	constexpr const auto PropertyCheckedColor = "checked-color";

	struct ColorConfiguration
	{
		QCheckBox* checkbox;
		QPushButton* button;

		std::function<bool(void)> isEnabled;
		std::function<void(bool)> setEnabled;
		std::function<QString(void)> getValue;
		std::function<void(QString)> setValue;
	};

	template<typename SettingEnabled, typename SettingValue>
	ColorConfiguration createColorConfig(QCheckBox* checkbox, QPushButton* button)
	{
		return {
			checkbox, button,
			[]() -> bool { return GetSetting(SettingEnabled); },
			[](const bool enabled) { SetSetting(SettingEnabled, enabled); },
			[]() -> QString { return GetSetting(SettingValue); },
			[](const QString& colorString) { SetSetting(SettingValue, colorString); }
		};
	}

	bool isCheckedAndEnabled(QCheckBox* checkbox)
	{
		return checkbox->isChecked() && checkbox->isEnabled();
	}

	QStringList extractExpressionsBetweenPercents(const QString& expr)
	{
		QStringList result;

		auto re = QRegExp("%(.*)%");
		re.setMinimal(true);

		auto idx = re.indexIn(expr);
		while((idx >= 0) && (idx < expr.size()))
		{
			const auto cap = re.cap(1);
			result << cap;
			idx = re.indexIn(expr, idx + 2 + cap.size());
		}

		return result;
	}

	bool evaluateExpression(const QString& expr)
	{
		if(expr.isEmpty())
		{
			return false;
		}

		auto starCount = 0;
		auto apostrophCount = 0;
		auto percentCount = 0;
		for(const auto& c: expr)
		{
			if(c == '\'')
			{
				apostrophCount++;
			}

			else if(c == '*')
			{
				starCount++;
			}

			else if(c == '%')
			{
				percentCount++;
			}
		}

		if((apostrophCount % 2 == 1) ||
		   (percentCount % 2 == 1) ||
		   (starCount % 2 == 1))
		{
			return false;
		}

		const auto betweenPercents = extractExpressionsBetweenPercents(expr);

		const auto isIncorrect = Util::Algorithm::contains(betweenPercents, [](const auto& betweenPercent) {
			return ((betweenPercent != QStringLiteral("nr")) &&
			        (betweenPercent != QStringLiteral("title")) &&
			        (betweenPercent != QStringLiteral("artist")) &&
			        (betweenPercent != QStringLiteral("filename")) &&
			        (betweenPercent != QStringLiteral("album")));
		});

		return (!isIncorrect);
	}

	QIcon createColorIcon(const int pixmapDimension, const QColor& color)
	{
		auto pixmap = QPixmap(pixmapDimension, pixmapDimension);
		auto painter = QPainter(&pixmap);
		painter.setBrush(color);
		painter.fillRect(0, 0, pixmapDimension, pixmapDimension, color);

		return {pixmap};
	}

	void applyColorToButton(QPushButton* button, const QColor& color, const QPalette& palette)
	{
		const auto newColor = color.isValid()
		                      ? color
		                      : palette.color(QPalette::ColorGroup::Active, QPalette::ColorRole::WindowText);

		const auto iconDimension = button->height() - 4;
		const auto colorName = newColor.name(QColor::NameFormat::HexRgb);
		button->setProperty(PropertyCheckedColor, colorName);
		button->setText(" " + colorName);
		button->setIconSize({iconDimension, iconDimension});
		button->setIcon(createColorIcon(iconDimension, newColor));
		button->setStyleSheet("font-family: monospace;");
	}
}

struct GUI_PlaylistPreferences::Private
{
	QList<ColorConfiguration> colorConfigs;
};

GUI_PlaylistPreferences::GUI_PlaylistPreferences(const QString& identifier) :
	Base(identifier),
	m {Pimpl::make<Private>()} {}

GUI_PlaylistPreferences::~GUI_PlaylistPreferences() = default;

bool GUI_PlaylistPreferences::commit()
{
	SetSetting(Set::PL_LoadRecentPlaylists, ui->cbLoadRecentPlaylists->isChecked());
	SetSetting(Set::PL_LoadSavedPlaylists, isCheckedAndEnabled(ui->cbLoadSavedPlaylists));
	SetSetting(Set::PL_LoadTemporaryPlaylists, isCheckedAndEnabled(ui->cbLoadTemporaryPlaylists));
	SetSetting(Set::PL_LoadLastTrack, isCheckedAndEnabled(ui->cbLoadLastTrack));
	SetSetting(Set::PL_RememberTime, isCheckedAndEnabled(ui->cbRememberTime));
	SetSetting(Set::PL_StartPlaying, isCheckedAndEnabled(ui->cbStartPlaying));

	SetSetting(Set::PL_ShowNumbers, ui->cbShowNumbers->isChecked());
	SetSetting(Set::PL_ShowCovers, ui->cbShowCovers->isChecked());
	SetSetting(Set::PL_ShowRating, ui->cbShowRating->isChecked());
	SetSetting(Set::PL_ShowBottomBar, ui->cbShowBottomBar->isChecked());

	SetSetting(Set::PL_ShowClearButton, ui->cbShowClearButton->isChecked());
	SetSetting(Set::PL_RememberTrackAfterStop, ui->cbRememberAfterStop->isChecked());

	SetSetting(Set::PL_JumpToCurrentTrack, ui->cbJumpToCurrentTrack->isChecked());
	SetSetting(Set::PL_PlayTrackAfterSearch, ui->cbPlayTrackAfterSearch->isChecked());
	SetSetting(Set::PL_StartPlayingWorkaround_Issue263, ui->cb_startupPlaybackWorkaround263->isChecked());

	SetSetting(Set::PL_CreateFilesystemPlaylist, ui->cbCreateFileystemPlaylist->isChecked());
	SetSetting(Set::PL_FilesystemPlaylistName, ui->leFilesystemPlaylistName->text());
	SetSetting(Set::PL_SpecifyFileystemPlaylistName,
	           ui->cbChooseFilesystemPlaylistName->isVisible() &&
	           ui->cbChooseFilesystemPlaylistName->isChecked());
	SetSetting(Set::PL_ShowConfirmationOnClose, ui->cbConfirmOnClose->isChecked());
	SetSetting(Set::PL_ModificatorAllowRearrangeMethods, ui->cbAllowRearrange->isChecked());
	SetSetting(Set::PL_ModificatorAllowDynamicPlayback, ui->cbAllowDynamicPlayback->isChecked());

	for(const auto& colorConfig: qAsConst(m->colorConfigs))
	{
		colorConfig.setEnabled(colorConfig.checkbox->isChecked());
		colorConfig.setValue(colorConfig.button->property(PropertyCheckedColor).toString());
	}

	const auto opacity = static_cast<float>(ui->sbCustomOpacityForCheckedButtonStd->value());
	SetSetting(Set::PL_CheckedButtonOpacityEnabled, ui->cbUseCustomOpacityForCheckedButton->isChecked());
	SetSetting(Set::PL_CheckedButtonOpacity, opacity);

	const auto success = evaluateExpression(ui->leExpression->text());
	if(success)
	{
		SetSetting(Set::PL_EntryLook, ui->leExpression->text());
	}

	return success;
}

void GUI_PlaylistPreferences::revert()
{
	const auto loadRecentPlaylists = GetSetting(Set::PL_LoadRecentPlaylists);
	const auto loadSavedPlaylists = GetSetting(Set::PL_LoadSavedPlaylists);
	const auto loadTemporaryPlaylists = GetSetting(Set::PL_LoadTemporaryPlaylists);
	const auto loadLastTrack = GetSetting(Set::PL_LoadLastTrack);
	const auto rememberTime = GetSetting(Set::PL_RememberTime);
	const auto startPlaying = GetSetting(Set::PL_StartPlaying);

	ui->cbLoadRecentPlaylists->setChecked(loadRecentPlaylists);
	ui->cbLoadSavedPlaylists->setChecked(loadSavedPlaylists);
	ui->cbLoadTemporaryPlaylists->setChecked(loadTemporaryPlaylists);
	ui->cbLoadLastTrack->setChecked(loadLastTrack);
	ui->cbRememberTime->setChecked(rememberTime);
	ui->cbStartPlaying->setChecked(startPlaying);
	loadRecentPlaylistsToggled(loadRecentPlaylists);

	ui->leExpression->setText(GetSetting(Set::PL_EntryLook));
	ui->cbShowNumbers->setChecked(GetSetting(Set::PL_ShowNumbers));
	ui->cbShowCovers->setChecked(GetSetting(Set::PL_ShowCovers));
	ui->cbShowRating->setChecked(GetSetting(Set::PL_ShowRating));
	ui->cbShowClearButton->setChecked(GetSetting(Set::PL_ShowClearButton));
	ui->cbRememberAfterStop->setChecked(GetSetting(Set::PL_RememberTrackAfterStop));
	ui->cbShowBottomBar->setChecked(GetSetting(Set::PL_ShowBottomBar));

	ui->cbJumpToCurrentTrack->setChecked(GetSetting(Set::PL_JumpToCurrentTrack));
	ui->cbPlayTrackAfterSearch->setChecked(GetSetting(Set::PL_PlayTrackAfterSearch));
	ui->cb_startupPlaybackWorkaround263->setChecked(GetSetting(Set::PL_StartPlayingWorkaround_Issue263));

	ui->cbCreateFileystemPlaylist->setChecked(GetSetting(Set::PL_CreateFilesystemPlaylist));
	ui->cbChooseFilesystemPlaylistName->setVisible(ui->cbCreateFileystemPlaylist->isChecked());
	ui->cbChooseFilesystemPlaylistName->setChecked(GetSetting(Set::PL_SpecifyFileystemPlaylistName));
	ui->leFilesystemPlaylistName->setVisible(ui->cbChooseFilesystemPlaylistName->isChecked());
	ui->leFilesystemPlaylistName->setText(GetSetting(Set::PL_FilesystemPlaylistName));
	ui->cbConfirmOnClose->setChecked(GetSetting(Set::PL_ShowConfirmationOnClose));
	ui->cbAllowDynamicPlayback->setChecked(GetSetting(Set::PL_ModificatorAllowDynamicPlayback));
	ui->cbAllowRearrange->setChecked(GetSetting(Set::PL_ModificatorAllowRearrangeMethods));

	for(const auto& colorConfig: qAsConst(m->colorConfigs))
	{
		colorConfig.checkbox->setChecked(colorConfig.isEnabled());
		colorConfig.button->setEnabled(colorConfig.isEnabled());
		applyColorToButton(colorConfig.button, {colorConfig.getValue()}, palette());
	}

	const auto opacityEnabled = GetSetting(Set::PL_CheckedButtonOpacityEnabled);
	const auto opacity = GetSetting(Set::PL_CheckedButtonOpacity);
	ui->cbUseCustomOpacityForCheckedButton->setChecked(opacityEnabled);
	ui->sbCustomOpacityForCheckedButtonStd->setValue(opacity);
	ui->sbCustomOpacityForCheckedButtonStd->setEnabled(opacityEnabled);
}

void GUI_PlaylistPreferences::initUi()
{
	ui = std::make_unique<Ui::GUI_PlaylistPreferences>();
	ui->setupUi(this);

	m->colorConfigs = {
		createColorConfig<Set::PL_CurrentTrackCustomColorStandard, Set::PL_CurrentTrackColorStringStandard>(
			ui->cbCustomColorStandard, ui->btnCustomColorStandard),
		createColorConfig<Set::PL_CurrentTrackCustomColorDark, Set::PL_CurrentTrackColorStringDark>(
			ui->cbCustomColorDark, ui->btnCustomColorDark),
		createColorConfig<Set::PL_CheckedButtonColorStdThemeEnabled, Set::PL_CheckedButtonColorStdTheme>(
			ui->cbUseCustomColorForCheckedButtonStd, ui->btnCustomColorForCheckedButtonStd),
		createColorConfig<Set::PL_CheckedButtonColorDarkThemeEnabled, Set::PL_CheckedButtonColorDarkTheme>(
			ui->cbUseCustomColorForCheckedButtonDark, ui->btnCustomColorForCheckedButtonDark)
	};

	ui->tabWidget->setCurrentIndex(0);
	ui->leExpression->setStyleSheet("font-family: mono;");
	ui->widgetTemplateHelp->setVisible(false);

	checkboxToggled(true);

	connect(ui->cbLoadRecentPlaylists, &QCheckBox::toggled, this, &GUI_PlaylistPreferences::loadRecentPlaylistsToggled);
	connect(ui->cbLoadLastTrack, &QCheckBox::toggled, this, &GUI_PlaylistPreferences::checkboxToggled);
	connect(ui->cbLoadSavedPlaylists, &QCheckBox::toggled, this, &GUI_PlaylistPreferences::checkboxToggled);
	connect(ui->cbLoadTemporaryPlaylists, &QCheckBox::toggled, this, &GUI_PlaylistPreferences::checkboxToggled);
	connect(ui->cbRememberTime, &QCheckBox::toggled, this, &GUI_PlaylistPreferences::checkboxToggled);
	connect(ui->cbStartPlaying, &QCheckBox::toggled, this, &GUI_PlaylistPreferences::checkboxToggled);

	for(const auto& colorConfig: qAsConst(m->colorConfigs))
	{
		colorConfig.button->setText({});
		connect(colorConfig.checkbox, &QCheckBox::toggled, colorConfig.button, &QWidget::setEnabled);
		connect(colorConfig.button, &QPushButton::clicked, this, &GUI_PlaylistPreferences::chooseColorClicked);
	}

	connect(ui->cbUseCustomOpacityForCheckedButton, &QCheckBox::toggled,
	        ui->sbCustomOpacityForCheckedButtonStd, &QWidget::setVisible);

	connect(ui->btnDefault, &QPushButton::clicked, this, [&]() {
		ui->leExpression->setText("*%title%* - %artist%");
	});
	connect(ui->btnTemplateHelp, &QPushButton::clicked, this, [&]() {
		ui->widgetTemplateHelp->setVisible(!ui->widgetTemplateHelp->isVisible());
	});

	connect(ui->cbChooseFilesystemPlaylistName, &QCheckBox::toggled,
	        ui->leFilesystemPlaylistName, &QWidget::setVisible);
	connect(ui->cbCreateFileystemPlaylist, &QCheckBox::toggled, this, [&](const auto b) {
		ui->cbChooseFilesystemPlaylistName->setVisible(b);
		ui->leFilesystemPlaylistName->setVisible(
			ui->cbChooseFilesystemPlaylistName->isVisible() &&
			ui->cbChooseFilesystemPlaylistName->isChecked());
	});
}

QString GUI_PlaylistPreferences::actionName() const
{
	return Lang::get(Lang::Playlist);
}

void GUI_PlaylistPreferences::retranslate()
{
	ui->retranslateUi(this);

	ui->labAlbum->setText(Lang::get(Lang::Album));
	ui->labArtist->setText(Lang::get(Lang::Artist));
	ui->labTitle->setText(Lang::get(Lang::Title));
	ui->labTrackNumber->setText(Lang::get(Lang::TrackNo));
	ui->btnDefault->setText(Lang::get(Lang::Default));
	ui->leFilesystemPlaylistName->setPlaceholderText(Lang::get(Lang::Files));

	const auto workaroundText = tr("Fix startup playback issue") + " " + "(#263)";
	ui->cb_startupPlaybackWorkaround263->setText(workaroundText);
}

void GUI_PlaylistPreferences::skinChanged()
{
	if(ui)
	{
		ui->btnDefault->setIcon(Gui::icon(Gui::Undo));
	}
}

QString GUI_PlaylistPreferences::errorString() const
{
	return tr("Playlist look: Invalid expression");
}

void GUI_PlaylistPreferences::loadRecentPlaylistsToggled(const bool b)
{
	ui->cbLoadTemporaryPlaylists->setEnabled(!b);
	ui->cbLoadSavedPlaylists->setEnabled(!b);

	checkboxToggled(b);
}

void GUI_PlaylistPreferences::checkboxToggled([[maybe_unused]] bool b)
{
	const auto load = ui->cbLoadRecentPlaylists ||
	                  ui->cbLoadSavedPlaylists->isChecked() ||
	                  ui->cbLoadTemporaryPlaylists->isChecked();

	ui->cbLoadLastTrack->setEnabled(load);
	ui->cbRememberTime->setEnabled(load);
	ui->cbStartPlaying->setEnabled(load);

	const auto loadLastTrack = ui->cbLoadLastTrack->isChecked() && ui->cbLoadLastTrack->isEnabled();
	ui->cbRememberTime->setEnabled(loadLastTrack);
}

void GUI_PlaylistPreferences::chooseColorClicked()
{
	if(auto* button = dynamic_cast<QPushButton*>(sender()); button != nullptr)
	{
		const auto currentColor = QColor(button->text());
		const auto newColor = QColorDialog::getColor(currentColor.isValid() ? currentColor : Qt::black, this);
		if(newColor.isValid())
		{
			applyColorToButton(button, newColor, palette());
		}
	}
}
