#pragma once

#include <psemek/async/future.hpp>
#include <psemek/util/function.hpp>

#include <chrono>

namespace psemek::async
{

	using clock = std::chrono::high_resolution_clock;

	struct executor
	{
		using task = util::function<void()>;

		// Post the task for execution. Where and when will
		// it be executed is up to a concrete executor, as
		// well as exception handling policy.
		// NB: it is better to use the "dispatch" method.
		virtual void post(task t) = 0;

		// Post the task for execution at a certain time point.
		// NB: it is better to use the "dispatch_at" method.
		virtual void post_at(clock::time_point time, task t) = 0;

		// Stop the executor. No tasks will be executed
		// after this function returns.
		// NB: the executor must call stop() from destructor.
		virtual void stop() = 0;

		// Clear the pending tasks queue.
		virtual void clear() = 0;

		// Wait for all the tasks to be executed.
		// May take forever.
		virtual void wait() = 0;

		// Wait for all the tasks to be executed,
		// but no longer than period.
		virtual void wait_for(clock::duration period) = 0;

		// Wait for all the tasks to be executed,
		// but no longer than time.
		virtual void wait_until(clock::time_point time) = 0;

		// The number of tasks that were posted but havent
		// finished yet.
		// NB: since the executor may run in another thread,
		// the return value of this function might change
		// between calls.
		virtual std::size_t task_count() const = 0;

		// Post a callable for execution. Retuns a future.
		template <typename F, typename ... Args>
		auto dispatch(F && f, Args && ... args);

		// Post a callable for execution. Retuns a future.
		// The task cancels on future destruction.
		template <typename F, typename ... Args>
		auto dispatch(auto_cancel_tag, F && f, Args && ... args);

		// Post a callable for execution at a certain time point.
		// Retuns a future.
		template <typename TimePoint, typename F, typename ... Args>
		auto dispatch_at(TimePoint time, F && f, Args && ... args);

		// Post a callable for execution at a certain time point.
		// Retuns a future. The task cancels on future destruction.
		template <typename TimePoint, typename F, typename ... Args>
		auto dispatch_at(TimePoint time, auto_cancel_tag, F && f, Args && ... args);

		// Create a future that waits for all the provided futures.
		// The returned future's result_type is void if the argument
		// futures' result_type is void, and vector<result_type> otherwise.
		template <typename Iterator>
		auto wait_all(Iterator begin, Iterator end);

		virtual ~executor() {}
	};

	template <typename F, typename ... Args>
	auto executor::dispatch(F && f, Args && ... args)
	{
		using R = decltype(f());
		packaged_task<R()> t([f = std::forward<F>(f), ... args = std::forward<Args>(args)]() mutable { return std::forward<F>(f)(std::forward<Args>(args)...); });
		auto fut = t.get_future();
		post(std::move(t));
		return fut;
	}

	template <typename F, typename ... Args>
	auto executor::dispatch(auto_cancel_tag tag, F && f, Args && ... args)
	{
		using R = decltype(f());
		packaged_task<R()> t(tag, [f = std::forward<F>(f), ... args = std::forward<Args>(args)]() mutable { return std::forward<F>(f)(std::forward<Args>(args)...); });
		auto fut = t.get_future();
		post(std::move(t));
		return fut;
	}

	template <typename TimePoint, typename F, typename ... Args>
	auto executor::dispatch_at(TimePoint time, F && f, Args && ... args)
	{
		using R = decltype(f());
		packaged_task<R()> t([f = std::forward<F>(f), ... args = std::forward<Args>(args)]() mutable { return std::forward<F>(f)(std::forward<Args>(args)...); });
		auto fut = t.get_future();
		post_at(time, std::move(t));
		return fut;
	}

	template <typename TimePoint, typename F, typename ... Args>
	auto executor::dispatch_at(TimePoint time, auto_cancel_tag tag, F && f, Args && ... args)
	{
		using R = decltype(f());
		packaged_task<R()> t(tag, [f = std::forward<F>(f), ... args = std::forward<Args>(args)]() mutable { return std::forward<F>(f)(std::forward<Args>(args)...); });
		auto fur = t.get_future();
		post_at(time, std::move(t));
		return fur;
	}

	template <typename Iterator>
	auto executor::wait_all(Iterator begin, Iterator end)
	{
		using R = decltype(begin->get());
		return dispatch([begin, end]{
			if constexpr (std::is_same_v<R, void>)
			{
				for (auto it = begin; it != end; ++it)
				{
					it->get();
				}
			}
			else
			{
				std::vector<R> results;
				results.reserve(std::distance(begin, end));
				for (auto it = begin; it != end; ++it)
				{
					results.push_back(it->get());
				}
				return results;
			}
		});
	}

}
