#pragma once

#include <psemek/group/permutation.hpp>
#include <psemek/util/range.hpp>

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

namespace psemek::group
{

	template <std::size_t N, typename Repr = std::size_t>
	struct cyclic
	{
		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 N;
		}

		cyclic() = default;

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

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

		static cyclic from_repr(Repr const & value)
		{
			return rotation(value);
		}

		template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
		static cyclic rotation(T const & value)
		{
			if (value >= 0)
				return cyclic{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 cyclic{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(N) : r)};
			}
		}

		Repr value() const
		{
			return repr_;
		}

		group::permutation<N, Repr> permutation() const
		{
			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);
		}

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

			Repr repr;

			cyclic<N, Repr> operator *() const
			{
				return cyclic<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{N}};
		}

	private:
		Repr repr_{0};

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

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

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

	template <std::size_t N, typename Repr>
	cyclic<N, Repr> operator * (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
	{
		return cyclic<N, Repr>::rotation(g1.value() + g2.value());
	}

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

	template <typename OStream, std::size_t N, typename Repr>
	OStream & operator << (OStream & os, cyclic<N, Repr> const & g)
	{
		os << 'C' << N << "(r" << static_cast<std::size_t>(g.value()) << ")";
		return os;
	}

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

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

}
