#pragma once

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

#include <psemek/math/affine_transform.hpp>
#include <psemek/math/quaternion.hpp>

#include <psemek/util/assert.hpp>

namespace psemek::math
{

	template <typename T, std::size_t N>
	struct symmetry
	{
		matrix<T, N, N> m;

		symmetry();
		symmetry(symmetry const &) = default;

		template <std::size_t D, typename Repr>
		symmetry(group::cyclic<D, Repr> const & g);

		template <std::size_t D, typename Repr>
		symmetry(group::cyclic<D, Repr> const & g, std::size_t i, std::size_t j);

		template <std::size_t D, typename Repr>
		symmetry(group::dihedral<D, Repr> const & g);

		template <std::size_t D, typename Repr>
		symmetry(group::dihedral<D, Repr> const & g, std::size_t i, std::size_t j);

		matrix<T, N, N + 1> affine_matrix() const;
		matrix<T, N, N> linear_matrix() const;
		vector<T, N> translation_vector() const;
		matrix<T, N + 1, N + 1> homogeneous_matrix() const;

		affine_transform<T, N, N> transform() const;

		vector<T, N> operator()(vector<T, N> const & v) const;
		point<T, N> operator()(point<T, N> const & p) const;
	};

	template <typename T, std::size_t N>
	symmetry<T, N>::symmetry()
	{
		m = m.identity();
	}

	template <typename T, std::size_t N>
	template <std::size_t D, typename Repr>
	symmetry<T, N>::symmetry(group::cyclic<D, Repr> const & g)
		: symmetry(g, 0, 1)
	{}

	template <typename T, std::size_t N>
	template <std::size_t D, typename Repr>
	symmetry<T, N>::symmetry(group::cyclic<D, Repr> const & g, std::size_t i, std::size_t j)
	{
		m = m.identity();

		T angle = (T{2} * math::pi * static_cast<T>(g.value())) / static_cast<T>(D);

		m[i][i] =  std::cos(angle);
		m[i][j] = -std::sin(angle);
		m[j][i] =  std::sin(angle);
		m[j][j] =  std::cos(angle);
	}

	template <typename T, std::size_t N>
	template <std::size_t D, typename Repr>
	symmetry<T, N>::symmetry(group::dihedral<D, Repr> const & g)
		: symmetry(g, 0, 1)
	{}

	template <typename T, std::size_t N>
	template <std::size_t D, typename Repr>
	symmetry<T, N>::symmetry(group::dihedral<D, Repr> const & g, std::size_t i, std::size_t j)
	{
		m = m.identity();

		T angle = (T{2} * math::pi * static_cast<T>(g.value())) / static_cast<T>(D);

		// See https://en.wikipedia.org/wiki/Dihedral_group#Matrix_representation

		if (g.is_rotation())
		{
			m[i][i] =  std::cos(angle);
			m[i][j] = -std::sin(angle);
			m[j][i] =  std::sin(angle);
			m[j][j] =  std::cos(angle);
		}
		else
		{
			m[i][i] =  std::cos(angle);
			m[i][j] =  std::sin(angle);
			m[j][i] =  std::sin(angle);
			m[j][j] = -std::cos(angle);
		}
	}

	template <typename T, std::size_t N>
	matrix<T, N, N + 1> symmetry<T, N>::affine_matrix() const
	{
		matrix<T, N, N + 1> result;
		for (std::size_t i = 0; i < N; ++i)
			for (std::size_t j = 0; j < N; ++j)
				result[i][j] = m[i][j];
		for (std::size_t i = 0; i < N; ++i)
			result[i][N] = T{0};
		return result;
	}

	template <typename T, std::size_t N>
	matrix<T, N, N> symmetry<T, N>::linear_matrix() const
	{
		return m;
	}

	template <typename T, std::size_t N>
	vector<T, N> symmetry<T, N>::translation_vector() const
	{
		return vector<T, N>::zero();
	}

	template <typename T, std::size_t N>
	matrix<T, N + 1, N + 1> symmetry<T, N>::homogeneous_matrix() const
	{
		auto result = matrix<T, N + 1, N + 1>::identity();
		for (std::size_t i = 0; i < N; ++i)
			for (std::size_t j = 0; j < N; ++j)
				result[i][j] = m[i][j];
		return result;
	}

	template <typename T, std::size_t N>
	affine_transform<T, N, N> symmetry<T, N>::transform() const
	{
		return {m, math::vector<T, N>::zero()};
	}

	template <typename T, std::size_t N>
	vector<T, N> symmetry<T, N>::operator()(vector<T, N> const & v) const
	{
		return m * v;
	}

	template <typename T, std::size_t N>
	point<T, N> symmetry<T, N>::operator()(point<T, N> const & p) const
	{
		return p.zero() + m * (p - p.zero());
	}

}
