#pragma once

#include <psemek/math/point.hpp>
#include <psemek/util/exception.hpp>

#include <vector>
#include <stdexcept>

namespace psemek::math
{

	// In-place de Casteljau's algorithm
	template <typename Iterator, typename T>
	auto de_casteljau(Iterator begin, Iterator end, T const & t)
	{
		while (std::next(begin) != end)
		{
			for (auto it = begin, jt = std::next(begin); jt != end; it = jt++)
				*it = lerp(*it, *jt, t);
			--end;
		}
		return *begin;
	}

	template <typename Point>
	struct bezier
	{
		bezier() = default;

		bezier(std::vector<Point> points)
			: points_(std::move(points))
		{
			if (points_.empty())
				throw util::exception("Points array should be non-empty");
			temp_.resize(points_.size());
		}

		std::vector<Point> & points()
		{
			return points_;
		}

		template <typename T>
		auto operator()(T const & t) const
		{
			temp_ = points_;
			return de_casteljau(temp_.begin(), temp_.end(), t);
		}

		template <typename T>
		auto tangent(T const & t) const
		{
			// In-place de Casteljau's algorithm for derivative
			tangent_temp_.resize(points_.size() - 1);
			for (std::size_t k = 0; k + 1 < points_.size(); ++k)
				tangent_temp_[k] = points_[k + 1] - points_[k];
			return static_cast<T>(points_.size()) * de_casteljau(tangent_temp_.begin(), tangent_temp_.end(), t);
		}

	private:
		using vector_type = decltype(Point{} - Point{});

		std::vector<Point> points_;
		std::vector<Point> mutable temp_;
		std::vector<vector_type> mutable tangent_temp_;
	};

}
