#pragma once

#include <psemek/math/vector.hpp>
#include <psemek/math/point.hpp>
#include <psemek/util/assert.hpp>

#include <vector>
#include <tuple>

namespace psemek::pcg
{

	template <typename Generator>
	struct fractal
	{
		using value_type = typename Generator::value_type;
		static constexpr std::size_t dimension = Generator::dimension;

		template <typename Arg>
		fractal(std::vector<Arg> args, std::vector<value_type> weights);

		template <typename ... Args>
		fractal(std::piecewise_construct_t, std::vector<std::tuple<Args...>> args, std::vector<value_type> weights);

		template <typename ... Args>
		value_type operator ()(Args const & ... args) const
		{
			return (*this)(math::vector{args...});
		}

		value_type operator()(math::vector<value_type, dimension> const & p) const;

		value_type operator()(math::point<value_type, dimension> const & p) const
		{
			return (*this)(p - p.zero());
		}

	private:
		std::vector<Generator> octaves;
		std::vector<float> weights;
	};

	template <typename Generator>
	template <typename Arg>
	fractal<Generator>::fractal(std::vector<Arg> args, std::vector<value_type> weights)
	{
		assert(args.size() == weights.size());
		this->weights = std::move(weights);
		octaves.reserve(args.size());
		for (std::size_t i = 0; i < args.size(); ++i)
			octaves.emplace_back(std::move(args[i]));
	}

	namespace detail
	{

		template <typename C, typename Tuple, std::size_t ... Is>
		void emplace_back_from_tuple_impl(C & c, Tuple && tuple, std::index_sequence<Is...>)
		{
			c.emplace_back(std::get<Is>(tuple)...);
		}

		template <typename C, typename Tuple>
		void emplace_back_from_tuple(C & c, Tuple && tuple)
		{
			emplace_back_from_tuple_impl(c, std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size_v<Tuple>>{});
		}

	}

	template <typename Generator>
	template <typename ... Args>
	fractal<Generator>::fractal(std::piecewise_construct_t, std::vector<std::tuple<Args...>> args, std::vector<value_type> weights)
	{
		assert(args.size() == weights.size());
		this->weights = std::move(weights);
		octaves.reserve(args.size());
		for (std::size_t i = 0; i < args.size(); ++i)
			detail::emplace_back_from_tuple(octaves, std::forward<std::tuple<Args...>>(args[i]));
	}

	template <typename Generator>
	typename fractal<Generator>::value_type fractal<Generator>::operator()(math::vector<value_type, dimension> const & p) const
	{
		value_type result = value_type{};
		for (std::size_t i = 0; i < octaves.size(); ++i)
			result += octaves[i](p) * weights[i];
		return result;
	}

}
