/* RatingLabel.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 "Utils/globals.h"

#include "RatingLabel.h"
#include "Gui/Utils/GuiUtils.h"
#include "Gui/Utils/Icons.h"

#include <QMouseEvent>
#include <QPainter>
#include <QIcon>

#include <algorithm>

namespace
{
	constexpr const auto StarCount = 5;
	constexpr const auto HSpacing = 2;
	constexpr const auto HPadding = 2;
	constexpr const auto VPadding = 2;

	int iconSize(const QLabel* label, const int verticalOffset)
	{
		const auto availableHeight = label->height() - verticalOffset;
		const auto maximumHeight = availableHeight - 2 * VPadding;
		const auto maximumWidth = (label->width() - 2 * HPadding) / StarCount;

		return std::min(maximumHeight, maximumWidth);
	}
}

namespace Gui
{
	struct RatingLabel::Private
	{
		int offsetY {0};
		RatingLabel::Alignment alignment {RatingLabel::Alignment::Centered};
		Rating rating {Rating::Zero};
		bool enabled;

		explicit Private(const bool enabled = true) :
			enabled(enabled) {}
	};

	RatingLabel::RatingLabel(QWidget* parent, const bool enabled) :
		QLabel(parent),
		m {Pimpl::make<Private>(enabled)} {}

	RatingLabel::~RatingLabel() = default;

	Rating RatingLabel::ratingAt(const QPoint pos) const
	{
		const auto drating = ((pos.x() * 1.0) / (iconSize(this, m->offsetY) + 2.0)) + 0.5;
		const auto iRating = static_cast<int>(drating);

		auto rating = static_cast<Rating>(iRating);
		rating = std::min(rating, Rating::Five);
		rating = std::max(rating, Rating::Zero);

		return rating;
	}

	QSize RatingLabel::sizeHint() const
	{
		const auto height = m->offsetY + fontMetrics().height() * 2;
		const auto width = height * 5;

		return {width, height};
	}

	QSize RatingLabel::minimumSizeHint() const { return sizeHint(); }

	void RatingLabel::setRating(const Rating rating) { m->rating = rating; }

	Rating RatingLabel::rating() const { return m->rating; }

	void RatingLabel::setVerticalOffset(const int offset) { m->offsetY = offset; }

	void RatingLabel::setHorizontalAlignment(const RatingLabel::Alignment alignment) { m->alignment = alignment; }

	void RatingLabel::paint(QPainter* painter, const QRect& rect)
	{
		setGeometry(rect);

		static const auto enabledStar = Gui::icon(Gui::Star);
		static const auto disabledStar = Gui::icon(Gui::StarDisabled);

		const auto starSize = iconSize(this, m->offsetY);
		const auto spacesBetweenStars = StarCount - 1;
		const auto contentWidth = (starSize * StarCount) + (HSpacing * spacesBetweenStars);
		const auto contentHeight = starSize;
		const auto offsetX = (m->alignment == Alignment::Left) ? HSpacing : (rect.width() - contentWidth) / 2;
		const auto offsetY = (m->offsetY > 0) ? m->offsetY : (rect.height() - contentHeight) / 2;

		painter->save();
		painter->translate(rect.x() + offsetX, rect.y() + offsetY);

		const auto starRect = QRect {0, 0, starSize, starSize};
		for(auto i = 0; i < StarCount; i++)
		{
			const auto rating = static_cast<Rating>(i);
			const auto icon = (rating < m->rating)
			                  ? enabledStar
			                  : disabledStar;

			painter->drawPixmap(starRect, icon.pixmap(starSize, starSize));
			painter->translate(starSize + HSpacing, 0);
		}

		painter->restore();
	}

	struct RatingEditor::Private
	{
		RatingLabel* label {new RatingLabel(nullptr, true)};
		bool mouseTrackable {true};

		// this rating is the rating we want to set
		// the rating shown in RatingLabel is the visible
		// rating. actual_rating is updated on mouse click
		// This is the _ONLY_ way to update it
		Rating actualRating;

		explicit Private(const Rating rating) :
			actualRating(rating)
		{
			label->setRating(actualRating);
		}
	};

	RatingEditor::RatingEditor(QWidget* parent) :
		RatingEditor(Rating::Zero, parent) {}

	RatingEditor::RatingEditor(Rating rating, QWidget* parent) :
		QWidget(parent),
		m {Pimpl::make<Private>(rating)}
	{
		setEnabled(rating != Rating::Last);
		setStyleSheet("background: transparent;");
		setFocusPolicy(Qt::StrongFocus);
	}

	RatingEditor::~RatingEditor() = default;

	void RatingEditor::setRating(const Rating rating)
	{
		m->actualRating = rating;
		m->label->setRating(rating);

		setEnabled(rating != Rating::Last);
		repaint();
	}

	Rating RatingEditor::rating() const { return m->actualRating; }

	void RatingEditor::setVerticalOffset(const int offset)
	{
		m->label->setVerticalOffset(offset);
	}

	void RatingEditor::setHorizontalAlignment(const RatingLabel::Alignment alignment)
	{
		m->label->setHorizontalAlignment(alignment);
	}

	void RatingEditor::setMouseTrackable(bool b)
	{
		m->mouseTrackable = b;
		if(!b)
		{
			this->setMouseTracking(b);
		}
	}

	QSize RatingEditor::sizeHint() const { return m->label->sizeHint(); }

	QSize RatingEditor::minimumSizeHint() const { return m->label->minimumSizeHint(); }

	void RatingEditor::paintEvent(QPaintEvent* e)
	{
		e->accept();

		auto painter = QPainter(this);
		m->label->paint(&painter, e->rect());
	}

	void RatingEditor::focusInEvent(QFocusEvent* e)
	{
		setMouseTracking(m->mouseTrackable);
		QWidget::focusInEvent(e);
	}

	void RatingEditor::focusOutEvent(QFocusEvent* e)
	{
		setMouseTracking(false);
		m->label->setRating(m->actualRating);

		emit sigFinished(false);

		QWidget::focusOutEvent(e);
	}

	void RatingEditor::mousePressEvent(QMouseEvent* e)
	{
		const auto rating = m->label->ratingAt(e->pos());
		m->label->setRating(rating);

		repaint();

		QWidget::mousePressEvent(e);
	}

	void RatingEditor::mouseMoveEvent(QMouseEvent* e)
	{
		const auto rating = m->label->ratingAt(e->pos());
		m->label->setRating(rating);

		repaint();

		QWidget::mouseMoveEvent(e);
	}

	void RatingEditor::mouseReleaseEvent(QMouseEvent* e)
	{
		/* Important: Do not call QWidget::mouseReleaseEvent here.
		 * this causes the edit trigger QAbstractItemView::SelectedClicked
		 * to fire again and open a new Editor */
		e->accept();

		const auto rating = m->label->ratingAt(e->pos());

		m->actualRating = rating;
		m->label->setRating(rating);

		repaint();

		emit sigFinished(true);
	}
}