#pragma once

#include <psemek/group/cyclic.hpp>
#include <psemek/group/permutation.hpp>

#include <cstddef>
#include <type_traits>
#include <limits>

namespace psemek::group
{

	template <std::size_t N, typename Repr = std::size_t>
	struct dihedral
	{
		static_assert(std::is_integral_v<Repr> && std::is_unsigned_v<Repr>);
		static_assert(std::numeric_limits<Repr>::max() > N);

		static constexpr std::size_t size()
		{
			return 2 * N;
		}

		dihedral() = default;

		static dihedral identity()
		{
			return dihedral{};
		}

		static dihedral rotation(Repr const & repr)
		{
			return dihedral{repr % static_cast<Repr>(N)};
		}

		template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
		static dihedral rotation(T const & value)
		{
			if (value >= 0)
				return dihedral{static_cast<Repr>(static_cast<std::size_t>(value) % N)};
			else
			{
				auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
				return dihedral{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(N) : r)};
			}
		}

		template <typename Repr2>
		static dihedral rotation(cyclic<N, Repr2> const & value)
		{
			return dihedral{static_cast<Repr>(value.repr())};
		}

		static dihedral reflection(Repr repr)
		{
			return dihedral{(repr % static_cast<Repr>(N)) + static_cast<Repr>(N)};
		}

		template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
		static dihedral reflection(T const & value)
		{
			if (value >= 0)
				return dihedral{static_cast<Repr>((static_cast<std::size_t>(value) % N) + N)};
			else
			{
				auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
				return dihedral{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(2 * N) : r + static_cast<std::int64_t>(N))};
			}
		}

		static dihedral from_repr(Repr const & repr)
		{
			return dihedral{repr % static_cast<Repr>(2 * N)};
		}

		Repr value() const
		{
			return repr_;
		}

		bool is_rotation() const
		{
			return repr_ < N;
		}

		bool is_reflection() const
		{
			return repr_ >= N;
		}

		group::permutation<N, Repr> permutation() const
		{
			if (repr_ < N)
			{
				typename group::permutation<N, Repr>::repr_type repr;
				for (Repr i = 0; i < N; ++i)
					repr[i] = (i + repr_) % N;
				return group::permutation<N, Repr>::from_repr(repr);
			}
			else
			{
				typename group::permutation<N, Repr>::repr_type repr;
				for (Repr i = 0; i < N; ++i)
					repr[i] = (N - i) % N;

				return rotation(repr_ % N).permutation() * group::permutation<N, Repr>::from_repr(repr);
			}
		}

		struct value_iterator
		{
			using difference_type = Repr;
			using value_type = dihedral<N, Repr>;
			using pointer = value_type *;
			using reference	= value_type &;
			using iterator_category = std::forward_iterator_tag;

			Repr repr;

			dihedral<N, Repr> operator *() const
			{
				return dihedral<N, Repr>{repr};
			}

			value_iterator & operator++()
			{
				++repr;
				return *this;
			}

			friend bool operator == (value_iterator const & it1, value_iterator const & it2)
			{
				return it1.repr == it2.repr;
			}

			friend auto operator - (value_iterator const & it1, value_iterator const & it2)
			{
				return it2.repr - it1.repr;
			}
		};

		static auto values()
		{
			return util::range{value_iterator{0}, value_iterator{2 * N}};
		}

	private:
		Repr repr_{0};

		explicit dihedral(Repr const & repr)
			: repr_(repr)
		{}
	};

	template <std::size_t N, typename Repr>
	bool operator == (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
	{
		return g1.value() == g2.value();
	}

	template <std::size_t N, typename Repr>
	auto operator <=> (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
	{
		return g1.value() <=> g2.value();
	}

	template <std::size_t N, typename Repr>
	dihedral<N, Repr> operator * (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
	{
		if (g1.is_rotation())
		{
			auto repr = g1.value() + g2.value();
			if (g2.is_rotation())
				return dihedral<N, Repr>::rotation(repr);
			else
				return dihedral<N, Repr>::reflection(repr);
		}
		else
		{
			auto repr = g1.value() + static_cast<Repr>(N) - g2.value();
			if (g2.is_rotation())
				return dihedral<N, Repr>::reflection(repr);
			else
				return dihedral<N, Repr>::rotation(repr);
		}
	}

	template <std::size_t N, typename Repr>
	dihedral<N, Repr> inverse(dihedral<N, Repr> const & g)
	{
		if (g.is_rotation())
			return dihedral<N, Repr>::rotation(N - g.value());
		else
			return g;
	}

	template <typename OStream, std::size_t N, typename Repr>
	OStream & operator << (OStream & os, dihedral<N, Repr> const & g)
	{
		if (g.is_rotation())
			os << 'D' << N << "(r" << static_cast<std::size_t>(g.value()) << ")";
		else
			os << 'D' << N << "(s" << (static_cast<std::size_t>(g.value()) % N) << ")";
		return os;
	}

	template <typename OStream, std::size_t N, typename Repr>
	void write(OStream & out, dihedral<N, Repr> const & g)
	{
		write(out, g.value());
	}

	template <typename IStream, std::size_t N, typename Repr>
	void read(IStream & in, dihedral<N, Repr> & g)
	{
		Repr value;
		read(in, value);
		g = dihedral<N, Repr>::from_repr(value);
	}

}
