#pragma once

#include <psemek/math/easing.hpp>
#include <psemek/math/math.hpp>

#include <vector>
#include <algorithm>

namespace psemek::math
{

	template <typename T, typename R = T>
	struct gradient
	{
		using result_type = R;

		gradient() = default;

		template <typename ... Args>
		gradient(Args const & ...  args)
		{
			init(args...);
		}

		gradient(std::vector<std::pair<T, R>> points, std::vector<easing_type> segments)
		{
			assert(points.size() == segments.size() + 1);
			points_ = std::move(points);
			segments_ = std::move(segments);
		}

		R operator()(T const & t) const
		{
			assert(!points_.empty());

			auto it = std::upper_bound(points_.begin(), points_.end(), t, [](T const & t, auto const & p){ return t < p.first; });
			if (it == points_.begin())
				return points_.front().second;
			if (it == points_.end())
				return points_.back().second;

			std::size_t i = (it - points_.begin()) - 1;

			T s = easing(segments_[i], (t - points_[i].first) / (points_[i + 1].first - points_[i].first));

			return lerp(points_[i].second, points_[i + 1].second, s);
		}

	private:
		std::vector<std::pair<T, R>> points_;
		std::vector<easing_type> segments_;

		void init(std::pair<T, R> const & p)
		{
			points_.push_back(p);
		}

		template <typename ... Args>
		void init(std::pair<T, R> const & p, easing_type t, Args const & ... args)
		{
			points_.push_back(p);
			segments_.push_back(t);
			init(args...);
		}
	};

}
