/* GUI_StyleSettings.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 "GUI_StyleSettings.h"
#include "Gui/Plugins/ui_GUI_Style.h"
#include "Components/Visualization/StyleSettings.h"
#include "Components/Visualization/VisualStyleTypes.h"

#include "Utils/Algorithm.h"
#include "Utils/Message/Message.h"
#include "Utils/Language/Language.h"
#include "Gui/Utils/Icons.h"
#include "Utils/Logger/Logger.h"

#include <QCloseEvent>
#include <QColorDialog>
#include <QInputDialog>
#include <QLineEdit>
#include <QList>

namespace
{
	QIcon colorToIcon(const QColor& color)
	{
		auto pm = QPixmap(18, 18); // NOLINT(*-magic-numbers)
		pm.fill(color);

		return {pm};
	}

	struct SignalBlockerRAII
	{
		explicit SignalBlockerRAII(QWidget* w) :
			m_widget {w}
		{
			w->blockSignals(true);
		}

		~SignalBlockerRAII()
		{
			m_widget->blockSignals(false);
		}

		QWidget* m_widget;
	};
}

struct GUI_StyleSettings::Private
{
	StyleSettings styleSettings {nullptr};

	std::array<QColor, 4> colors;
	std::array<QPushButton*, 4> colorButtons {nullptr};
	QList<QSpinBox*> spinBoxes;
	bool somethingChanged {false};
	QString currentText;
};

GUI_StyleSettings::GUI_StyleSettings(QWidget* parent) :
	Dialog(parent),
	m {Pimpl::make<Private>()},
	ui {std::make_shared<Ui::GUI_Style>()}
{
	ui->setupUi(this);

	m->colorButtons = {ui->btnColor1, ui->btnColor2, ui->btnColor3, ui->btnColor4};

	ui->btnMenu->showActions(
		Gui::ContextMenu::EntryUndo |
		Gui::ContextMenu::Entry::EntrySave |
		Gui::ContextMenu::Entry::EntryDelete |
		Gui::ContextMenu::Entry::EntryApply);

	connect(ui->comboStyles, combo_current_index_changed_int, this, &GUI_StyleSettings::comboStylesChanged);
	connect(ui->comboStyles->lineEdit(), &QLineEdit::textEdited, this, &GUI_StyleSettings::textEdited);
	connect(ui->btnColor1, &QPushButton::clicked, this, &GUI_StyleSettings::color1Activated);
	connect(ui->btnColor2, &QPushButton::clicked, this, &GUI_StyleSettings::color2Activated);
	connect(ui->btnColor3, &QPushButton::clicked, this, &GUI_StyleSettings::color3Activated);
	connect(ui->btnColor4, &QPushButton::clicked, this, &GUI_StyleSettings::color4Activated);
	connect(ui->btnMenu, &Gui::MenuToolButton::sigApply, this, &GUI_StyleSettings::sigStyleApplied);
	connect(ui->btnMenu, &Gui::MenuToolButton::sigUndo, this, &GUI_StyleSettings::undoPressed);
	connect(ui->btnMenu, &Gui::MenuToolButton::sigDelete, this, &GUI_StyleSettings::deletePressed);
	connect(ui->btnMenu, &Gui::MenuToolButton::sigSave, this, &GUI_StyleSettings::savePressed);
	connect(&m->styleSettings, &StyleSettings::sigStylesChanged, this, &GUI_StyleSettings::stylesChanged);
	connect(ui->btnOk, &QPushButton::clicked, this, &GUI_StyleSettings::okClicked);

	m->spinBoxes
		<< ui->sbFadingStepsLvl << ui->sbFadingStepsSpec
		<< ui->sbHorSpacingLvl << ui->sbHorSpacingSpec
		<< ui->sbRectHeightSpec << ui->sbRectWidthLvl << ui->sbRectHeightLvl
		<< ui->sbVerSpacingLvl << ui->sbVerSpacingSpec;

	for(auto* spinbox: qAsConst(m->spinBoxes))
	{
		auto signal = static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged);
		connect(spinbox, signal, this, [this]() {
			setSomethingChanged(true);
		});
	}

	initCombobox();
	setSomethingChanged(false);
}

GUI_StyleSettings::~GUI_StyleSettings() = default;

void GUI_StyleSettings::initCombobox()
{
	[[maybe_unused]] auto blocker = SignalBlockerRAII(ui->comboStyles);

	const auto oldText = m->currentText;

	ui->comboStyles->clear();

	const auto styles = m->styleSettings.styles();
	for(const auto& style: styles)
	{
		ui->comboStyles->addItem(style.colorList.name);
	}

	ui->comboStyles->setCurrentText(oldText);
	m->currentText = ui->comboStyles->currentText();

	m->styleSettings.select(ui->comboStyles->currentIndex());
}

void GUI_StyleSettings::skinChanged()
{
	initCombobox();
}

void GUI_StyleSettings::stylesChanged()
{
	initCombobox();

	setWindowTitle(windowTitle().remove("*"));
	setSomethingChanged(false);
}

void GUI_StyleSettings::comboStylesChanged(const int index)
{
	[[maybe_unused]] auto blocker = SignalBlockerRAII(ui->comboStyles); // avoid recursion

	if(m->somethingChanged)
	{
		if(const auto mayGoon = askForSaveSettings(); !mayGoon)
		{
			ui->comboStyles->setCurrentIndex(m->styleSettings.currentIndex());
			return;
		}
	}

	m->styleSettings.select(index);

	if(const auto style = m->styleSettings.currentStyle(); style)
	{
		applyStyleToUi(style.value());

		ui->btnMenu->showAction(Gui::ContextMenu::EntryDelete, (index != 0));
		ui->btnMenu->showAction(Gui::ContextMenu::EntryUndo, (index != 0));
		ui->btnMenu->showAction(Gui::ContextMenu::EntrySave, !m->currentText.isEmpty());
		ui->btnMenu->showAction(Gui::ContextMenu::EntryApply, true);

		setSomethingChanged(false);
	}
}

void GUI_StyleSettings::textEdited(const QString& text)
{
	m->currentText = text;
	ui->btnMenu->showAction(Gui::ContextMenu::EntrySave, !m->currentText.isEmpty());
}

bool GUI_StyleSettings::saveCurrent()
{
	auto style = createStyleFromUi();
	if(m->currentText.isEmpty())
	{
		const auto text =
			QInputDialog::getText(this, Lang::get(Lang::Name), tr("Please enter a name"), QLineEdit::Normal,
			                      m->styleSettings.nameProposal());
		style.colorList.name = text;
	}

	return m->styleSettings.saveCurrent(style);
}

bool GUI_StyleSettings::askForSaveSettings()
{
	const auto ret = Message::question_yn(tr("There are some unsaved settings<br />Save now?"));
	return (ret == Message::Answer::Yes)
	       ? saveCurrent()
	       : true;
}

void GUI_StyleSettings::savePressed()
{
	saveCurrent();
	emit sigStyleChanged();
}

void GUI_StyleSettings::colorActivated(const int index)
{
	if(const auto style = m->styleSettings.currentStyle(); style)
	{
		const auto hasEnoughColors = (index < style->colorList.colors.size());
		const auto oldColor = hasEnoughColors
		                      ? style->colorList.colors[index]
		                      : QColor(255, 255, 255);

		m->colors[index] = QColorDialog::getColor(oldColor, this);
		m->colorButtons[index]->setIcon(colorToIcon(m->colors[index]));

		setSomethingChanged(true);
	}
}

void GUI_StyleSettings::deletePressed()
{
	m->styleSettings.deleteCurrent();

	emit sigStyleChanged();
}

void GUI_StyleSettings::undoPressed()
{
	if(const auto style = m->styleSettings.currentStyle(); style)
	{
		applyStyleToUi(style.value());
		setSomethingChanged(false);
	}
}

void GUI_StyleSettings::color1Activated() { colorActivated(0); }

void GUI_StyleSettings::color2Activated() { colorActivated(1); }

void GUI_StyleSettings::color3Activated() { colorActivated(2); }

void GUI_StyleSettings::color4Activated() { colorActivated(3); }

void GUI_StyleSettings::closeEvent(QCloseEvent* e)
{
	if(m->somethingChanged)
	{
		const auto ret = Message::question_yn(tr("Save changes?"));
		if(ret == Message::Answer::Yes)
		{
			savePressed();
		}
	}

	Dialog::closeEvent(e);
}

void GUI_StyleSettings::setSomethingChanged(const bool b)
{
	ui->btnMenu->showAction(Gui::ContextMenu::EntryUndo, b);
	m->somethingChanged = b;

	if(b)
	{
		if(!windowTitle().endsWith("*"))
		{
			setWindowTitle(windowTitle() + "*");
		}
	}

	else
	{
		setWindowTitle(windowTitle().remove("*"));
	}
}

void GUI_StyleSettings::show(const int styleIndex)
{
	if(!isVisible())
	{
		setSomethingChanged(false);
		showNormal();

		const auto index = styleIndex < (m->styleSettings.count() - 1)
		                   ? styleIndex + 1
		                   : 0;

		ui->comboStyles->setCurrentIndex(index);
	}
}

RawColorStyle GUI_StyleSettings::createStyleFromUi()
{
	RawColorStyle style;

	style.fadingStepsLevel = ui->sbFadingStepsLvl->value();
	style.fadingStepsSpectrum = ui->sbFadingStepsSpec->value();
	style.horSpacingLevel = ui->sbHorSpacingLvl->value();
	style.horSpacingSpectrum = ui->sbHorSpacingSpec->value();
	style.verSpacingLevel = ui->sbVerSpacingLvl->value();
	style.verSpacingSpectrum = ui->sbVerSpacingSpec->value();
	style.rectHeightSpectrum = ui->sbRectHeightSpec->value();
	style.rectWidthLevel = ui->sbRectWidthLvl->value();
	style.rectHeightLevel = ui->sbRectHeightLvl->value();
	style.colorList.name = m->currentText; // text before indexChanged
	style.colorList.colors << m->colors[0] << m->colors[1];
	if(ui->cbColor3->isChecked())
	{
		style.colorList.colors << m->colors[2];
	}
	if(ui->cbColor4->isChecked())
	{
		style.colorList.colors << m->colors[3];
	}

	return style;
}

void GUI_StyleSettings::applyStyleToUi(const RawColorStyle& style)
{
	ui->sbFadingStepsLvl->setValue(style.fadingStepsLevel);
	ui->sbFadingStepsSpec->setValue(style.fadingStepsSpectrum);
	ui->sbHorSpacingLvl->setValue(style.horSpacingLevel);
	ui->sbHorSpacingSpec->setValue(style.horSpacingSpectrum);
	ui->sbVerSpacingLvl->setValue(style.verSpacingLevel);
	ui->sbVerSpacingSpec->setValue(style.verSpacingSpectrum);
	ui->sbRectHeightSpec->setValue(style.rectHeightSpectrum);
	ui->sbRectWidthLvl->setValue(style.rectWidthLevel);
	ui->sbRectHeightLvl->setValue(style.rectHeightLevel);

	ui->cbColor3->setChecked(style.colorList.colors.size() > 2 && style.colorList.colors[2].isValid());
	ui->cbColor4->setChecked(style.colorList.colors.size() > 3 && style.colorList.colors[3].isValid());

	const auto colorList = style.colorList.colors;

	for(int i = 0; i < 4; i++)
	{
		m->colors[i] = (i < colorList.size())  // NOLINT(*-pro-bounds-constant-array-index)
		               ? colorList[i]
		               : QColor(0, 0, 0, 0);
		m->colorButtons[i]->setIcon(colorToIcon(m->colors[i])); // NOLINT(*-pro-bounds-constant-array-index)
	}

	m->currentText = ui->comboStyles->currentText();
}

void GUI_StyleSettings::languageChanged()
{
	ui->retranslateUi(this);

	ui->btnOk->setText(Lang::get(Lang::OK));
	ui->btnClose->setText(Lang::get(Lang::Close));
}

void GUI_StyleSettings::okClicked()
{
	savePressed();
	close();
}
