#pragma once

#include <psemek/react/value.hpp>

#include <vector>

namespace psemek::react
{

	namespace detail
	{

		template <typename F, typename ... Args>
		auto map_impl(F func, value<Args> ... args)
		{
			using R = std::decay_t<decltype(func(std::declval<Args const &>()...))>;

			auto node = std::make_shared<detail::node<R>>();
			auto weak_node = std::weak_ptr{node};

			auto internal_subscriber = [weak_node]{
				if (auto node = weak_node.lock())
				{
					node->cached_value = std::nullopt;
					node->internal_signal();
				}
			};

			auto external_subscriber = [weak_node](auto const &){
				if (auto node = weak_node.lock())
				{
					if (!node->cached_value)
						node->external_signal(node->value());
				}
			};

			auto internal_tokens = std::tuple{args.subscribe(detail::internal_tag{}, internal_subscriber)...};
			auto external_tokens = std::tuple{args.subscribe(external_subscriber)...};

			node->getter = [
				func = std::move(func),
				internal_tokens = std::move(internal_tokens),
				external_tokens = std::move(external_tokens),
				args...]() -> R {
				return func(*args...);
			};

			return value<R>(detail::internal_tag{}, std::move(node));
		}

		template <typename F, typename T, typename G>
		auto map_vector_impl(F func, std::vector<value<T>> args, G transform)
		{
			using H = std::decay_t<decltype(transform(std::declval<T>()))>;
			using R = std::decay_t<decltype(func(std::declval<std::vector<H>>()))>;

			auto node = std::make_shared<detail::node<R>>();
			auto weak_node = std::weak_ptr{node};

			auto internal_subscriber = [weak_node]{
				if (auto node = weak_node.lock())
				{
					node->cached_value = std::nullopt;
					node->internal_signal();
				}
			};

			auto external_subscriber = [weak_node](auto const &){
				if (auto node = weak_node.lock())
				{
					if (!node->cached_value)
						node->external_signal(node->value());
				}
			};

			std::vector<util::signal<>::subscription_token> internal_tokens;
			std::vector<typename util::signal<T>::subscription_token> external_tokens;

			internal_tokens.reserve(args.size());
			external_tokens.reserve(args.size());

			for (auto const & arg : args)
			{
				if (!arg) continue;

				internal_tokens.push_back(arg.subscribe(detail::internal_tag{}, internal_subscriber));
				external_tokens.push_back(arg.subscribe(external_subscriber));
			}

			node->getter = [
				func = std::move(func),
				internal_tokens = std::move(internal_tokens),
				external_tokens = std::move(external_tokens),
				args = std::move(args),
				transform = std::move(transform)]() -> R {
				std::vector<H> arg_values;
				arg_values.reserve(args.size());
				for (auto const & arg : args)
				{
					arg_values.push_back(transform(arg));
				}
				return func(std::move(arg_values));
			};

			return value<R>(detail::internal_tag{}, std::move(node));
		}

	}

	template <typename F, typename ... Args>
	auto map(F func, Args && ... args)
	{
		return detail::map_impl(std::move(func), make_value(std::forward<Args>(args))...);
	}

	template <typename F, typename T>
	auto map_vector(F func, std::vector<value<T>> args)
	{
		return detail::map_vector_impl(std::move(func), std::move(args), [](T const & value){ return value; });
	}

	template <typename F, typename T, typename G>
	auto map_vector(F func, std::vector<value<T>> args, G transform)
	{
		return detail::map_vector_impl(std::move(func), std::move(args), transform);
	}

	constexpr auto unpack = []<typename T>(std::vector<value<T>> args)
	{
		return map_vector([](std::vector<T> x) -> std::vector<T> { return x; }, std::move(args), [](value<T> const & arg){ return arg ? *arg : T{}; });
	};

	constexpr auto unpack_with_default = []<typename T>(T const & default_value)
	{
		return [default_value](std::vector<value<T>> args){
			return map_vector([](std::vector<T> x) -> std::vector<T> { return x; }, std::move(args), [default_value](value<T> const & arg){ return arg ? *arg : default_value; });
		};
	};

	constexpr auto unpack_with_transform = []<typename G>(G transform)
	{
		return [transform = std::move(transform)]<typename T>(std::vector<value<T>> args){
			return map_vector([]<typename H>(std::vector<H> x) -> std::vector<H> { return x; }, std::move(args), transform);
		};
	};

}
