#include <psemek/gfx/gltf_animation.hpp>

namespace psemek::gfx
{

	namespace detail
	{

		void gltf_animation_traits<gltf_asset::animation::channel::rotation>::canonicalize(math::easing_type interpolation, std::vector<output_type> & output)
		{
			if (interpolation == math::easing_type::linear)
			{
				for (std::size_t i = 1; i < output.size(); ++i)
				{
					if (math::dot(output[i - 1].coords, output[i].coords) < 0.f)
					{
						output[i] = - output[i];
					}
				}
			}
			else if (interpolation == math::easing_type::cubic)
			{
				for (std::size_t i = 3; i < output.size(); i += 3)
				{
					if (math::dot(output[(i - 3) + 1].coords, output[i + 1].coords) < 0.f)
					{
						output[i + 0] = - output[i + 0];
						output[i + 1] = - output[i + 1];
						output[i + 2] = - output[i + 2];
					}
				}
			}
		}

	}

	template <gltf_asset::animation::channel::path_t path>
	gltf_animation_channel<path>::output_type gltf_animation_channel<path>::operator()(float time) const
	{
		auto it = std::lower_bound(input_.begin(), input_.end(), time);
		if (it == input_.begin())
		{
			if (interpolation_ == math::easing_type::cubic)
				return output_[1];
			else
				return output_.front();
		}
		if (it == input_.end())
		{
			if (interpolation_ == math::easing_type::cubic)
				return output_[output_.size() - 2];
			else
				return output_.back();
		}

		auto i = it - input_.begin();

		float td = (input_[i] - input_[i - 1]);
		float t = (time - input_[i - 1]) / td;

		switch (interpolation_)
		{
		case math::easing_type::constant_left:
			return output_[i - 1];
		case math::easing_type::linear:
			return traits::lerp(output_[i - 1], output_[i], t);
		case math::easing_type::cubic:
			{
				// see https://github.khronos.org/glTF-Tutorials/gltfTutorial/gltfTutorial_007_Animations.html#cubic-spline-interpolation
				float t2 = t * t;
				float t3 = t * t2;
				return traits::normalize(output_type::zero()
					+ output_[3 * (i - 1) + 1] * (  2.f * t3 - 3.f * t2 + 1.f)
					+ output_[3 * (i - 1) + 2] * (        t3 - 2.f * t2 + t  ) * td
					+ output_[3 * (i + 0) + 1] * (- 2.f * t3 + 3.f * t2      )
					+ output_[3 * (i + 0) + 0] * (        t3 -       t2      ) * td
					);
			}
		default:
			throw util::exception("Unknown glTF animation interpolation type");
		}
	}

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

	math::interval<float> gltf_animation::range() const
	{
		return scale_.range() | rotation_.range() | translation_.range();
	}

	math::trs<float, 3> gltf_animation::operator()(float time) const
	{
		return {translation_(time), rotation_(time), scale_(time)};
	}

}
