#pragma once

#include <psemek/react/value.hpp>

namespace psemek::react
{

	template <typename T>
	struct source
		: value<T>
	{
		source(source const &) = default;
		source(source &&) = default;

		source()
			: source(T{})
		{}

		source(T x)
			: value<T>(detail::internal_tag{}, std::make_shared<detail::node<T>>())
		{
			set(std::move(x));
		}

		source(T x, typename util::signal<T>::subscriber subscriber)
			: source(std::move(x))
		{
			subscription_token_ = this->subscribe(std::move(subscriber));
		}

		source & operator = (source const &) = default;
		source & operator = (source &&) = default;

		void set(T x) const
		{
			this->node_->cached_value = std::nullopt;
			this->node_->getter = [x = std::move(x)]() mutable { return std::move(x); };
			this->node_->internal_signal();
			this->node_->external_signal(this->node_->value());
		}

		template <typename F>
		void modify(F && f) const
		{
			set(f(this->node_->value()));
		}

		template <typename H, typename F>
		source<H> modifier(H value, F && f)
		{
			std::weak_ptr weak_node = this->node_;

			source<H> result(std::move(value));
			this->node_->subscriptions.push_back(result.subscribe([weak_node, f = std::forward<F>(f)](H const & value) mutable {
				if (auto node = weak_node.lock())
				{
					T x = f(node->value(), value);
					node->cached_value = std::nullopt;
					node->getter = [x = std::move(x)]() mutable { return std::move(x); };
					node->internal_signal();
					node->external_signal(node->value());
				}
			}));

			return result;
		}

	private:
		typename util::signal<T>::subscription_token subscription_token_;
	};

	template <>
	struct source<void>
		: value<void>
	{
		source(source const &) = default;
		source(source &&) = default;

		source()
			: value<void>(detail::internal_tag{}, std::make_shared<detail::node<void>>())
		{}

		source(util::signal<>::subscriber subscriber)
			: source()
		{
			subscribtion_token_ = subscribe(std::move(subscriber));
		}

		source & operator = (source const &) = default;
		source & operator = (source &&) = default;

		void set() const
		{
			this->node_->internal_signal();
			this->node_->external_signal();
		}

	private:
		util::signal<>::subscription_token subscribtion_token_;
	};

	template <typename T>
	value<T> make_value(source<T> const & s)
	{
		return s;
	}

}
