#ifndef FZ_BOUNDED_THREAD_POOL_HPP
#define FZ_BOUNDED_THREAD_POOL_HPP

#include <libfilezilla/thread_pool.hpp>

#include "intrusive_list.hpp"

namespace fz
{

class bounded_async_task;
class bounded_thread_pool;

class bounded_async_task final
{
public:
	bounded_async_task(bounded_async_task const&) = delete;
	bounded_async_task &operator=(bounded_async_task const&) = delete;

	bounded_async_task() noexcept;
	~bounded_async_task() noexcept;

	bounded_async_task(bounded_async_task &&other) noexcept;
	bounded_async_task &operator=(bounded_async_task &&other) noexcept;

	explicit operator bool() const noexcept
	{
		return impl_ != nullptr;
	}

	bool cancel() noexcept;

	friend void swap(bounded_async_task &lhs, bounded_async_task &rhs) noexcept;

private:
	friend bounded_thread_pool;
	struct impl;
	std::unique_ptr<impl> impl_{};
};

/** \brief A thread-pool for asynchronous tasks that caps the maximum amount of allocated threads.
 *
 * If there the number of running threads reaches the allowed maximum, spawned asynchronous tasks are put in a waiting queue.
 * Once an asynchronous task finishes, a waiting tasks is dequeued and  executed.
 *
 * The timeout parameter in the constructor determines the maximum waiting time before a task is spawned.
 */
class bounded_thread_pool final
{
public:
	bounded_thread_pool(thread_pool &pool, std::size_t max_number_of_threads, duration timeout = {});
	~bounded_thread_pool();

	bounded_thread_pool(bounded_thread_pool const&) = delete;
	bounded_thread_pool& operator=(bounded_thread_pool const&) = delete;

	/// Spawns a new asynchronous task.
	/// The task is either kept in the waiting queue until a thread frees up, or it's spawned immediately.
	/// The on_timeout handler is invoked when the timeout configured at construction time expires while the task is in the queue.
	bounded_async_task spawn(std::function<void()> task, std::function<void()> on_timeout = {});

private:
	friend bounded_async_task;

	enum spawn_state {
		spawn_not_attempted,
		spawn_succeeded,
		spawn_failed
	};

	spawn_state try_to_spawn(std::function<void()> && task, async_task &handle);
	void task_spooler();

	thread_pool &pool_;

	mutex m_{false};
	bool quit_{};

	std::size_t number_of_available_threads_;
	duration timeout_;
	condition spooler_cond_;
	async_task spooler_;

	intrusive_list<bounded_async_task::impl> waiting_;
};

}

#endif // FZ_BOUNDED_THREAD_POOL_HPP
