/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
 **********************************************************************/

#pragma once

#include <util/sll/ctstring.h>
#include <util/sll/typegetter.h>
#include "flatitemsmodeltypedbase.h"

namespace LC::Util
{
	template<typename T, int RoleV>
	struct RoleOf
	{
		constexpr static auto Role = RoleV;

		using Type = T;

		T Value_ {};

		RoleOf () = default;

		RoleOf (const RoleOf& other) = default;
		RoleOf (RoleOf&& other) = default;

		RoleOf& operator= (const RoleOf& other) = default;
		RoleOf& operator= (RoleOf&& other) = default;

		template<typename U = T>
		RoleOf (U&& value)
		: Value_ (std::forward<U> (value))
		{
		}

		template<typename U = T>
		RoleOf& operator= (U&& value)
		{
			Value_ = std::forward<U> (value);
			return *this;
		}

		template<typename U = T>
			requires requires (T t, U u) { t == u; }
		bool operator== (const U& value) const
		{
			return Value_ == value;
		}

		T& operator* ()
		{
			return Value_;
		}

		const T& operator* () const
		{
			return Value_;
		}

		T* operator-> ()
		{
			return &Value_;
		}

		const T* operator-> () const
		{
			return &Value_;
		}

		operator T () const
			requires (!std::is_same_v<T, QVariant>)
		{
			return Value_;
		}

		operator QVariant () const
		{
			return Value_;
		}
	};

	namespace detail
	{
		constexpr auto GetFieldsCount (auto&& val)
		{
			auto&& [...elems] = val;
			return std::integral_constant<size_t, sizeof... (elems)> {};
		}

		template<size_t I>
		constexpr auto GetFieldAt (auto&& val)
		{
			auto&& [...elems] = val;
			return elems... [I];
		}

		template<typename T, size_t I>
		using FieldType_t = decltype (GetFieldAt<I> (std::declval<T> ()));

		template<typename T>
		constexpr size_t FieldsCount_v = decltype (GetFieldsCount (std::declval<T> ()))::value;

		template<typename T>
		using FieldGetter_t = QVariant (*) (const T&);

		template<typename T, size_t Ix>
		std::pair<int, FieldGetter_t<T>> Role2GetterPair ()
		{
			if constexpr (requires { FieldType_t<T, Ix>::Role; })
				return
				{
					FieldType_t<T, Ix>::Role,
					+[] (const T& t) { return QVariant::fromValue (*GetFieldAt<Ix> (t)); }
				};
			else
				return { -1, nullptr };
		}

		template<typename T>
		QHash<int, FieldGetter_t<T>> MkGetters ()
		{
			return []<size_t... Ixs> (std::index_sequence<Ixs...>)
			{
				return QHash<int, FieldGetter_t<T>> { Role2GetterPair<T, Ixs> ()... };
			} (std::make_index_sequence<FieldsCount_v<T>> {});
		}
	}

	template<auto F>
	constexpr auto FromField = +[] (const ArgType_t<decltype (F), 0>& t) { return QVariant::fromValue (t.*F); };

	template<typename T>
	class RoledItemsModel : public FlatItemsModelTypedBase<T>
	{
	public:
		using FieldGetter_t = detail::FieldGetter_t<T>;
	private:
		const QHash<int, FieldGetter_t> Role2Getter_;
	public:
		explicit RoledItemsModel (QObject *parent = nullptr)
		: FlatItemsModelTypedBase<T> { QStringList { {} }, parent }
		, Role2Getter_ { detail::MkGetters<T> () }
		{
		}

		explicit RoledItemsModel (const QHash<int, FieldGetter_t>& getters, QObject *parent = nullptr)
		: FlatItemsModelTypedBase<T> { QStringList { {} }, parent }
		, Role2Getter_ { getters }
		{
		}

		template<auto F, typename V>
		void SetField (int idx, V&& value)
		{
			if (this->Items_ [idx].*F == value)
				return;

			this->Items_ [idx].*F = std::forward<V> (value);
			emit this->dataChanged (this->index (idx, 0), this->index (idx, 0),
					{ std::decay_t<decltype (T {}.*F)>::Role });
		}

		template<auto... Fs, typename... Vs>
		void SetFields (int idx, Vs&&... values)
		{
			QList<int> changedRoles;
			changedRoles.reserve (sizeof... (values));
			(([&, this]
			{
				if (this->Items_ [idx].*Fs != std::forward<Vs> (values))
				{
					this->Items_ [idx].*Fs = std::forward<Vs> (values);
					changedRoles << std::decay_t<decltype (T {}.*Fs)>::Role;
				}
			} ()), ...);

			if (!changedRoles.isEmpty ())
				emit this->dataChanged (this->index (idx, 0), this->index (idx, 0), changedRoles);
		}
	protected:
		QVariant GetData (int row, int, int role) const override
		{
			if (const auto getter = Role2Getter_.value (role))
				return getter (this->Items_.at (row));
			return {};
		}
	};


	template<CtString RoleArg, auto GetterArg>
	struct NamedMemberField
	{
		static constexpr auto Getter = GetterArg;
		static constexpr auto Role = RoleArg;
	};

	template<CtString RoleArg, auto GetterArg>
	NamedMemberField<RoleArg, GetterArg> NamedMemberField_v;

	template<typename T>
	class NamedItemsModel : public FlatItemsModelTypedBase<T>
	{
	public:
		using FieldGetter_t = detail::FieldGetter_t<T>;
		using FieldsList_t = QVector<QPair<QByteArray, FieldGetter_t>>;
	private:
		const QVector<FieldGetter_t> Fields_;
		const QHash<int, QByteArray> Roles_;
	public:
		template<typename... Fields>
		explicit NamedItemsModel (QObject *parent, Fields...) noexcept
		: FlatItemsModelTypedBase<T> { QStringList { {} }, parent }
		, Fields_ { +[] (const T& t) -> QVariant { return t.*Fields::Getter; }... }
		, Roles_ { MakeRoles ({ ToByteArray<Fields::Role> ()... }) }
		{
		}

		QHash<int, QByteArray> roleNames () const override
		{
			return Roles_;
		}
	protected:
		QVariant GetData (int row, int, int role) const override
		{
			if (const auto getter = Fields_.value (role - this->DataRole - 1))
				return getter (this->Items_.at (row));
			return {};
		}
	private:
		QHash<int, QByteArray> MakeRoles (QVector<QByteArray> fields) const
		{
			auto result = FlatItemsModelTypedBase<T>::roleNames ();
			result.reserve (result.size () + fields.size ());
			for (int i = 0; i < fields.size (); ++i)
				result [this->DataRole + i + 1] = std::move (fields [i]);
			return result;
		}
	};

	template<auto Getter>
	struct Field
	{
		QString Name_;
	};

	namespace detail
	{
		template<Qt::ItemDataRole Role>
		using RoleTag = std::integral_constant<Qt::ItemDataRole, Role>;

		struct Extension
		{
			explicit Extension (auto&&...) {}

			static Qt::ItemFlags GetFlags (auto&&...) { return {}; }
			static bool SetData (auto&&...) { return false; }
			static void GetDataForRole () {}
		};

		template<typename P>
		struct ParameterizedExtension : Extension
		{
			struct Param
			{
				P Param_;
			};

			P Param_;

			template<typename... Params>
				requires (std::is_same_v<Param, Params> || ...)
			explicit ParameterizedExtension (const std::tuple<Params...>& args)
			: Param_ { std::get<Param> (args).Param_ }
			{
			}
		};
	}

	template<auto IconField>
	struct ItemsDecorated : detail::Extension
	{
		using Extension::Extension;

		static QVariant GetDataForRole (detail::RoleTag<Qt::DecorationRole>, const auto& item, int column)
		{
			if (column)
				return {};
			return item.*IconField;
		}
	};

	struct ItemsEditable : detail::ParameterizedExtension<QList<int>>
	{
		using ParameterizedExtension::ParameterizedExtension;

		Qt::ItemFlags GetFlags (int column) const
		{
			return Param_.contains (column) ? Qt::ItemIsEditable : Qt::ItemFlags {};
		}
	};

	template<auto CheckField>
	struct ItemsCheckable : detail::Extension
	{
		using Extension::Extension;

		static Qt::ItemFlags GetFlags (int column)
		{
			return column ? Qt::ItemFlags {} : Qt::ItemIsUserCheckable;
		}

		static QVariant GetDataForRole (detail::RoleTag<Qt::CheckStateRole>, const auto& item, int, int column)
		{
			if (column)
				return QVariant {};

			if constexpr (std::is_same_v<std::decay_t<decltype (item.*CheckField)>, Qt::CheckState>)
				return item.*CheckField;
			else if constexpr (std::is_same_v<std::decay_t<decltype (item.*CheckField)>, bool>)
				return item.*CheckField ? Qt::Checked : Qt::Unchecked;
			else
				static_assert (false, "expected Qt::CheckState or bool field");
		}

		template<typename Item>
		static bool SetData (Item& item, int, int column, const QVariant& value, int role)
		{
			if (role != Qt::CheckStateRole || column)
				return false;

			const auto state = value.value<Qt::CheckState> ();
			if constexpr (std::is_same_v<std::decay_t<decltype (item.*CheckField)>, Qt::CheckState>)
				item.*CheckField = state;
			else if constexpr (std::is_same_v<std::decay_t<decltype (item.*CheckField)>, bool>)
				item.*CheckField = state == Qt::Checked;
			else
				static_assert (false, "expected Qt::CheckState or bool field");
			return true;
		}
	};

	struct NoField {};
	inline constexpr auto NoField_v = NoField {};

	template<>
	struct ItemsCheckable<NoField_v> : detail::ParameterizedExtension<Qt::CheckState>
	{
		QHash<int, Qt::CheckState> RowsStates_;

		using ParameterizedExtension::ParameterizedExtension;

		static Qt::ItemFlags GetFlags (int column)
		{
			return column ? Qt::ItemFlags {} : Qt::ItemIsUserCheckable;
		}

		bool IsChecked (int row) const
		{
			return RowsStates_.value (row, Param_) == Qt::Checked;
		}

		QVariant GetDataForRole (detail::RoleTag<Qt::CheckStateRole>, const auto&, int row, int column)
		{
			return column ? QVariant {} : RowsStates_.value (row, Param_);
		}

		template<typename Item>
		bool SetData (Item&, int row, int column, const QVariant& value, int role)
		{
			if (role != Qt::CheckStateRole || column)
				return false;

			RowsStates_ [row] = value.value<Qt::CheckState> ();
			return true;
		}
	};

	template<typename T, typename... Extensions>
	class ItemsModel : public FlatItemsModelTypedBase<T>
					 , public Extensions...
	{
		using FieldGetter_t = QVariant (*) (const T&);
		const QVector<FieldGetter_t> Fields_;

		using FieldSetter_t = void (*) (T&, const QVariant&);
		const QVector<FieldSetter_t> Setters_;
	public:
		template<auto... Getter>
		explicit ItemsModel (const Field<Getter>&... fields)
		: ItemsModel { std::tuple {}, fields... }
		{
		}

		template<auto... Member, typename... ExtParams>
		explicit ItemsModel (const std::tuple<ExtParams...>& extParams, const Field<Member>&... fields)
		: FlatItemsModelTypedBase<T> { { fields.Name_... } }
		, Extensions { extParams }...
		, Fields_ { +[] (const T& t) -> QVariant { return t.*Member; }... }
		, Setters_ { +[] (T& t, const QVariant& v) { t.*Member = v.value<std::decay_t<decltype (t.*Member)>> (); }... }
		{
		}

		Qt::ItemFlags flags (const QModelIndex& index) const override
		{
			auto flags = FlatItemsModelTypedBase<T>::flags (index);
			flags |= (Extensions::GetFlags (index.column ()) | ...);
			return flags;
		}

		bool setData (const QModelIndex& index, const QVariant& value, int role) override
		{
			auto& item = this->Items_ [index.row ()];
			if ((Extensions::SetData (item, index.row (), index.column (), value, role) ||...))
			{
				emit this->dataChanged (index, index);
				return true;
			}

			if (role != Qt::EditRole)
				return false;

			Setters_ [index.column ()] (item, value);
			emit this->dataChanged (index, index);
			return true;
		}
	protected:
		QVariant GetData (int row, int column, int role) const override
		{
			const auto& item = this->Items_ [row];

			switch (role)
			{
			case Qt::DisplayRole:
			case Qt::EditRole:
				if (const auto getter = Fields_.value (column))
					return getter (item);
				return {};
			case Qt::CheckStateRole:
				return this->GetDataForRole (detail::RoleTag<Qt::CheckStateRole> {}, item, row, column);
			case Qt::DecorationRole:
				return this->GetDataForRole (detail::RoleTag<Qt::DecorationRole> {}, item, row, column);
			default:
				return {};
			}
		}

		using Extensions::GetDataForRole...;

		static QVariant GetDataForRole (auto&&...)
		{
			return {};
		}
	};
}
