#pragma once

#include <psemek/util/function.hpp>

#include <vector>
#include <memory>

namespace psemek::util
{

	template <typename ... Args>
	struct signal
	{
		using subscriber = function<void(Args const & ...)>;

		using subscription_token = std::shared_ptr<void>;

		[[nodiscard]] subscription_token subscribe(subscriber callback) const
		{
			auto token = std::make_shared<subscriber>(std::move(callback));
			subscribers_.push_back(std::weak_ptr{token});
			return token;
		}

		void subscribe_forever(subscriber callback) const
		{
			tokens_.push_back(subscribe(std::move(callback)));
		}

		void operator()(Args const & ... args)
		{
			auto subscribers = std::move(subscribers_);

			auto begin = subscribers.begin();
			auto end = subscribers.end();
			auto alive_end = begin;

			for (auto it = begin; it != end;)
			{
				if (auto callback = it->lock())
				{
					(*callback)(args...);
					if (alive_end != it)
						*alive_end = std::move(*it);
					++it;
					++alive_end;
				}
				else
					++it;
			}
			subscribers.erase(alive_end, end);

			for (auto & s : subscribers_)
				subscribers.push_back(std::move(s));
			subscribers_ = std::move(subscribers);
		}

	private:
		mutable std::vector<std::weak_ptr<subscriber>> subscribers_;
		mutable std::vector<subscription_token> tokens_;
	};

}
