#pragma once

#include <psemek/math/affine_transform.hpp>

namespace psemek::math
{

	template <typename T, std::size_t N>
	struct scale
	{
		vector<T, N> s;

		scale();
		scale(T s);
		scale(vector<T, N> const & s);
		scale(scale const &) = default;

		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> v) const;
		point<T, N> operator()(point<T, N> p) const;

	private:
		template <typename Matrix>
		void fill_matrix(Matrix & m) const;
	};

	template <typename T, std::size_t N>
	scale(vector<T, N>) -> scale<T, N>;

	template <typename T, std::size_t N>
	scale<T, N>::scale()
	{
		for (std::size_t i = 0; i < N; ++i)
			s[i] = T{1};
	}

	template <typename T, std::size_t N>
	scale<T, N>::scale(T s)
	{
		for (std::size_t i = 0; i < N; ++i)
			this->s[i] = s;
	}

	template <typename T, std::size_t N>
	scale<T, N>::scale(vector<T, N> const & s)
		: s{s}
	{}

	template <typename T, std::size_t N>
	matrix<T, N, N + 1> scale<T, N>::affine_matrix() const
	{
		auto result = matrix<T, N, N + 1>::identity();
		fill_matrix(result);
		return result;
	}

	template <typename T, std::size_t N>
	matrix<T, N, N> scale<T, N>::linear_matrix() const
	{
		auto result = matrix<T, N, N>::identity();
		fill_matrix(result);
		return result;
	}

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

	template <typename T, std::size_t N>
	matrix<T, N + 1, N + 1> scale<T, N>::homogeneous_matrix() const
	{
		auto result = matrix<T, N + 1, N + 1>::identity();
		fill_matrix(result);
		return result;
	}

	template <typename T, std::size_t N>
	affine_transform<T, N, N> scale<T, N>::transform() const
	{
		return {affine_matrix()};
	}

	template <typename T, std::size_t N>
	vector<T, N> scale<T, N>::operator()(vector<T, N> v) const
	{
		for (std::size_t i = 0; i < N; ++i)
			v[i] *= s[i];
		return v;
	}

	template <typename T, std::size_t N>
	point<T, N> scale<T, N>::operator()(point<T, N> p) const
	{
		for (std::size_t i = 0; i < N; ++i)
			p[i] *= s[i];
		return p;
	}

	template <typename T, std::size_t N>
	template <typename Matrix>
	void scale<T, N>::fill_matrix(Matrix & m) const
	{
		for (std::size_t i = 0; i < N; ++i)
			m[i][i] = s[i];
	}

	template <typename T, std::size_t N>
	std::optional<scale<T, N>> inverse(scale<T, N> const & s)
	{
		vector<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			if (s.s[i] == T{0})
				return std::nullopt;
			result[i] = T{1} / s.s[i];
		}
		return scale<T, N>{result};
	}

}
