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

#include <optional>

#include "Gui/Utils/GuiUtils.h"
#include "Library/TableView/TableView.h"
#include "Utils/Algorithm.h"
#include "Utils/Logger/Logger.h"

#include <QPair>
#include <QTimer>
#include <variant>

namespace Library
{
	using ColumnActionPair = QPair<ColumnHeaderPtr, QAction*>;
	using ColumnActionPairList = QList<ColumnActionPair>;

	namespace
	{
		int columnWidth(const ColumnHeaderPtr& columnHeader, QWidget* widget)
		{
			return std::max(columnHeader->defaultSize(),
			                Gui::Util::textWidth(widget, columnHeader->title() + QStringLiteral("MMM")));
		}

		double getScaleFactor(QHeaderView* header, const ColumnActionPairList& columns)
		{
			auto spaceNeeded = 0;
			auto freeSpace = header->width();

			for(auto i = 0; i < columns.size(); i++)
			{
				if(header->isSectionHidden(i))
				{
					continue;
				}

				const auto& [columnHeader, action] = columns[i];
				const auto size = columnWidth(columnHeader, header);

				if(columnHeader->isStretchable())
				{
					spaceNeeded += size;
				}

				else
				{
					freeSpace -= size;
				}
			}

			return std::max(freeSpace * 1.0 / spaceNeeded, 1.0);
		}

		constexpr const auto* actionIndex = "actionIndex";

		void setActionIndex(QAction* action, const int index) { action->setProperty(actionIndex, index); }

		std::optional<int> getActionIndex(const QAction* action)
		{
			if(const auto var = action->property(actionIndex); !var.isNull())
			{
				auto success = false;
				if(const auto value = var.toInt(&success); success)
				{
					return value;
				}
			}

			return std::nullopt;
		}
	}

	struct HeaderView::Private
	{
		ColumnActionPairList columns;
		QAction* actionResize;
		QAction* actionAutoResize;

		QByteArray initialState;
		bool isInitialized{false};

		explicit Private(HeaderView* parent) :
			actionResize{new QAction(parent)},
			actionAutoResize{new QAction(parent)}
		{
			actionAutoResize->setCheckable(parent);
		}
	};

	HeaderView::HeaderView(Qt::Orientation orientation, QWidget* parent) :
		Gui::HeaderView(orientation, parent),
		m{Pimpl::make<Private>(this)}
	{
		connect(m->actionResize, &QAction::triggered, this, &HeaderView::actionResizeTriggered);
		connect(m->actionAutoResize, &QAction::triggered, this, &HeaderView::actionAutoResizeTriggered);
		connect(this, &QHeaderView::sectionDoubleClicked, this, [this](int /*logicalIndex*/) {
			resizeColumnsAutomatically();
		});
	}

	HeaderView::~HeaderView() = default;

	void HeaderView::init(const ColumnHeaderList& columnHeaderList, const QByteArray& state,
	                      const VariableSortorder sortOrder, const bool autoResizeState)
	{
		m->initialState = state;

		for(auto i = 0; i < columnHeaderList.size(); i++)
		{
			const auto& columnHeader = columnHeaderList[i];
			const auto indicator = (sortOrder == columnHeader->sortorder(Qt::AscendingOrder))
			                       ? Qt::AscendingOrder
			                       : Qt::DescendingOrder;

			setSortIndicator(i, indicator);

			auto* action = new QAction(columnHeader->title(), this);
			addAction(action);
			m->columns << ColumnActionPair(columnHeader, action);
		}

		auto sep = new QAction(this);
		sep->setSeparator(true);

		addAction(sep);
		addAction(m->actionResize);
		addAction(m->actionAutoResize);

		m->actionAutoResize->setChecked(autoResizeState);
	}

	void HeaderView::finalizeActions()
	{
		auto i = 0;
		for(const auto& [columnHeader, action]: m->columns)
		{
			action->setCheckable(columnHeader->isSwitchable());
			action->setChecked(!isSectionHidden(i));
			setActionIndex(action, i);

			connect(action, &QAction::toggled, this, &HeaderView::actionTriggered);
			i++;
		}
	}

	void HeaderView::initializeView()
	{
		if(m->initialState.isEmpty())
		{
			resizeColumnsAutomatically();
		}

		else
		{
			restoreState(m->initialState);
			m->initialState.clear();
			finalizeActions();
		}

		setContextMenuPolicy(Qt::ActionsContextMenu);
		setHighlightSections(true);
		setMinimumSectionSize(25); // NOLINT(readability-magic-numbers)
		setSectionsClickable(true);
		setSectionsMovable(true);
		setSortIndicatorShown(true);
		setSectionResizeMode(Interactive);
		setStretchLastSection(true);
		setTextElideMode(Qt::TextElideMode::ElideRight);

		m->isInitialized = true;

		connect(this, &QHeaderView::sectionCountChanged, this, [this](auto /*o*/, auto /*n*/) {
			saveHeaderState();
		});
		connect(this, &QHeaderView::sectionResized, this, [this](auto /*i*/, auto /*o*/, auto /*n*/) {
			saveHeaderState();
		});
		connect(this, &QHeaderView::sectionMoved, this, [this](auto /*i*/, auto /*o*/, auto /*n*/) {
			saveHeaderState();
		});
	}

	void HeaderView::saveHeaderState()
	{
		if(isVisible() && m->isInitialized)
		{
			emit sigStateChanged(saveState());
		}
	}

	VariableSortorder HeaderView::sortorder(const int index, const Qt::SortOrder sortOrder)
	{
		const auto& columnHeader = m->columns[index].first;
		return columnHeader->sortorder(sortOrder);
	}

	QString HeaderView::columnText(const int index) const
	{
		return Util::between(index, m->columns)
		       ? m->columns[index].first->title()
		       : QString();
	}

	void HeaderView::actionTriggered(const bool b)
	{
		const auto* action = dynamic_cast<QAction*>(sender());
		if(const auto index = getActionIndex(action); index.has_value())
		{
			setSectionHidden(index.value(), !b);
		}

		actionResizeTriggered();
	}

	void HeaderView::actionResizeTriggered()
	{
		resizeColumnsAutomatically();
	}

	void HeaderView::actionAutoResizeTriggered(bool b)
	{
		emit sigAutoResizeToggled(b);
	}

	void HeaderView::resizeColumnsAutomatically()
	{
		const auto scaleFactor = getScaleFactor(this, m->columns);

		for(auto i = 0; i < m->columns.size(); i++)
		{
			const auto& columnHeader = m->columns[i].first;
			const auto size = columnWidth(columnHeader, this);

			if(!isSectionHidden(i))
			{
				const auto stretchedSize = columnHeader->isStretchable()
				                           ? static_cast<int>(size * scaleFactor)
				                           : size;

				resizeSection(i, stretchedSize);
			}
		}
	}

	void HeaderView::reloadColumnTexts()
	{
		for(auto i = 0; i < m->columns.size(); i++)
		{
			auto* action = m->columns[i].second;
			action->setText(columnText(i));
		}
	}

	bool HeaderView::isInitialized() const { return m->isInitialized; }

	void HeaderView::languageChanged()
	{
		m->actionResize->setText(tr("Resize columns"));
		m->actionAutoResize->setText(tr("Resize columns automatically"));

		reloadColumnTexts();
	}

	void HeaderView::showEvent(QShowEvent* e)
	{
		Gui::HeaderView::showEvent(e);

		if(!m->isInitialized)
		{
			initializeView();
		}
	}

	void HeaderView::resizeEvent(QResizeEvent* e)
	{
		Gui::HeaderView::resizeEvent(e);

		if(m && m->actionAutoResize->isChecked() && m->initialState.isEmpty())
		{
			resizeColumnsAutomatically();
		}
	}
}
