#pragma once

#include <psemek/util/signal.hpp>

#include <optional>
#include <vector>
#include <memory>
#include <initializer_list>
#include <type_traits>
#include <string>

namespace psemek::react
{

	namespace detail
	{

		struct internal_tag
		{};

		struct forever_tag
		{};

		void on_node_destroyed(std::string const & debug_marker);

		template <typename T>
		struct node
		{
			using external_subscriber = typename util::signal<T>::subscriber;

			util::signal<> internal_signal;
			util::signal<T> external_signal;
			util::function<T()> getter;
			std::optional<T> cached_value;
			std::vector<std::shared_ptr<void>> subscriptions;

			std::string debug_marker;

			T const & value()
			{
				if (!cached_value)
					cached_value.emplace(getter());
				return *cached_value;
			}

			~node()
			{
				on_node_destroyed(debug_marker);
			}
		};

		template <>
		struct node<void>
		{
			using external_subscriber = typename util::signal<>::subscriber;

			util::signal<> internal_signal;
			util::signal<> external_signal;

			std::string debug_marker;

			void value()
			{}

			~node()
			{
				on_node_destroyed(debug_marker);
			}
		};

		template <typename T>
		struct node_value_type
		{
			using type = T const &;
		};

		template <>
		struct node_value_type<void>
		{
			using type = void;
		};

		template <typename C>
		constexpr bool is_container = requires { typename C::value_type; };

	}

	static constexpr detail::forever_tag forever;

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

		template <typename ... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...> && ((sizeof...(Args) != 1) || (!std::is_base_of_v<value<T>, std::decay_t<Args>> && ...))>>
		value(Args && ... args)
			: node_(std::make_shared<detail::node<T>>())
		{
			node_->cached_value.emplace(std::forward<Args>(args)...);
		}

		template <typename C = T, typename = typename C::value_type>
		value(std::initializer_list<typename C::value_type> list)
			: node_(std::make_shared<detail::node<T>>())
		{
			node_->cached_value.emplace(list);
		}

		value(detail::internal_tag, std::shared_ptr<detail::node<T>> node)
			: node_(node)
		{}

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

		explicit operator bool() const
		{
			return static_cast<bool>(node_);
		}

		typename detail::node_value_type<T>::type operator *() const
		{
			return node_->value();
		}

		[[nodiscard]] auto subscribe(typename detail::node<T>::external_subscriber subscriber, bool call = false) const
		{
			if (call) this->call(subscriber);
			return node_->external_signal.subscribe(std::move(subscriber));
		}

		void subscribe(typename detail::node<T>::external_subscriber subscriber, detail::forever_tag, bool call = false) const
		{
			if (call) this->call(subscriber);
			node_->external_signal.subscribe_forever(std::move(subscriber));
		}

		[[nodiscard]] auto subscribe(detail::internal_tag, std::function<void()> subscriber) const
		{
			return node_->internal_signal.subscribe(std::move(subscriber));
		}

		std::shared_ptr<detail::node<T>> node(detail::internal_tag)
		{
			return node_;
		}

	protected:
		std::shared_ptr<detail::node<T>> node_;

		void call(typename detail::node<T>::external_subscriber const & subscriber) const
		{
			if constexpr (std::is_same_v<T, void>)
				subscriber();
			else
				subscriber(node_->value());
		}
	};

	template <typename T>
	value<T> make_value(T x)
	{
		return value<T>(std::move(x));
	}

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

}
