#pragma once

#include <psemek/gfx/gltf_parser.hpp>
#include <psemek/math/trs.hpp>
#include <psemek/util/exception.hpp>

#include <algorithm>

namespace psemek::gfx
{

	namespace detail
	{

		template <gltf_asset::animation::channel::path_t path>
		struct gltf_animation_traits;

		template <>
		struct gltf_animation_traits<gltf_asset::animation::channel::scale>
		{
			using output_type = math::vector<float, 3>;

			static output_type default_value()
			{
				return {1.f, 1.f, 1.f};
			}

			static output_type lerp(output_type const & v1, output_type const & v2, float t)
			{
				return math::lerp(v1, v2, t);
			}

			static output_type normalize(output_type const & v)
			{
				return v;
			}

			static void canonicalize(math::easing_type, std::vector<output_type> &)
			{}
		};

		template <>
		struct gltf_animation_traits<gltf_asset::animation::channel::rotation>
		{
			using output_type = math::quaternion<float>;

			static output_type default_value()
			{
				return output_type::identity();
			}

			static output_type lerp(output_type const & v1, output_type const & v2, float t)
			{
				return math::slerp(v1, v2, t);
			}

			static output_type normalize(output_type const & v)
			{
				return math::normalized(v);
			}

			static void canonicalize(math::easing_type interpolation, std::vector<output_type> & output);
		};

		template <>
		struct gltf_animation_traits<gltf_asset::animation::channel::translation>
		{
			using output_type = math::vector<float, 3>;

			static output_type default_value()
			{
				return {0.f, 0.f, 0.f};
			}

			static output_type lerp(output_type const & v1, output_type const & v2, float t)
			{
				return math::lerp(v1, v2, t);
			}

			static output_type normalize(output_type const & v)
			{
				return v;
			}

			static void canonicalize(math::easing_type, std::vector<output_type> &)
			{}
		};

	}

	template <gltf_asset::animation::channel::path_t path>
	struct gltf_animation_channel
	{
		using traits = detail::gltf_animation_traits<path>;
		using output_type = typename traits::output_type;

		gltf_animation_channel()
			: input_{0.f}
			, output_{traits::default_value()}
			, interpolation_{math::easing_type::constant_left}
		{}

		gltf_animation_channel(std::vector<float> input, std::vector<output_type> output, math::easing_type interpolation)
			: input_(std::move(input))
			, output_(std::move(output))
			, interpolation_(interpolation)
		{
			traits::canonicalize(interpolation_, output_);
		}

		gltf_animation_channel(gltf_animation_channel &&) = default;

		gltf_animation_channel & operator = (gltf_animation_channel &&) = default;

		math::interval<float> range() const
		{
			return {input_.front(), input_.back()};
		}

		output_type operator()(float time) const;

	private:
		std::vector<float> input_;
		std::vector<output_type> output_;
		math::easing_type interpolation_;
	};

	extern template struct gltf_animation_channel<gltf_asset::animation::channel::scale>;
	extern template struct gltf_animation_channel<gltf_asset::animation::channel::rotation>;
	extern template struct gltf_animation_channel<gltf_asset::animation::channel::translation>;

	using gltf_scale_animation = gltf_animation_channel<gltf_asset::animation::channel::scale>;
	using gltf_rotation_animation = gltf_animation_channel<gltf_asset::animation::channel::rotation>;
	using gltf_translation_animation = gltf_animation_channel<gltf_asset::animation::channel::translation>;

	struct gltf_animation
	{
		gltf_animation() = default;

		gltf_animation(gltf_scale_animation scale, gltf_rotation_animation rotation, gltf_translation_animation translation)
			: scale_(std::move(scale))
			, rotation_(std::move(rotation))
			, translation_(std::move(translation))
		{}

		math::interval<float> range() const;

		math::trs<float, 3> operator()(float time) const;

		gltf_scale_animation const & scale() const { return scale_; }
		gltf_rotation_animation const & rotation() const { return rotation_; }
		gltf_translation_animation const & translation() const { return translation_; }

		gltf_scale_animation & scale() { return scale_; }
		gltf_rotation_animation & rotation() { return rotation_; }
		gltf_translation_animation & translation() { return translation_; }

	private:
		gltf_scale_animation scale_;
		gltf_rotation_animation rotation_;
		gltf_translation_animation translation_;
	};

}
