#pragma once

#include <functional>

#include "Capture.hpp"

NAMESPACE_SOUP
{
	template <typename CaptureArgT, typename Ret, typename...Args>
	struct CallbackBase
	{
		using FuncT = Ret(Args...);
		using FuncWithCaptureT = Ret(Args..., CaptureArgT);

		FuncWithCaptureT* fp;
		Capture cap;

		CallbackBase() noexcept
			: fp(nullptr)
		{
		}

		CallbackBase(const CallbackBase&) = delete;

		CallbackBase(CallbackBase&& b) noexcept
			: fp(b.fp), cap(std::move(b.cap))
		{
		}

		CallbackBase(FuncT* fp) noexcept
			: fp(reinterpret_cast<FuncWithCaptureT*>(fp))
		{
		}

		CallbackBase(FuncWithCaptureT* fp) noexcept
			: fp(fp)
		{
		}

		CallbackBase(FuncWithCaptureT* fp, Capture&& cap) noexcept
			: fp(fp), cap(std::move(cap))
		{
		}

		static void redirect_to_std_function(Args... args, CaptureArgT cap)
		{
			return cap.template get<std::function<Ret(Args...)>>()(std::forward<Args>(args)...);
		}

		template <typename T, SOUP_RESTRICT(std::is_same_v<std::function<Ret(Args...)>, T>)>
		CallbackBase(T&& func) noexcept
			: CallbackBase(&redirect_to_std_function, std::move(func))
		{
		}

		void set(FuncWithCaptureT* fp, Capture&& cap = {}) noexcept
		{
			this->fp = fp;
			this->cap = std::move(cap);
		}

		void operator=(FuncWithCaptureT* fp) noexcept
		{
			this->fp = fp;
			this->cap.reset();
		}

		void operator=(CallbackBase&& b) noexcept
		{
			fp = b.fp;
			cap = std::move(b.cap);
		}

		void operator=(const CallbackBase& b) noexcept = delete;

		void operator=(FuncT* fp) noexcept
		{
			this->fp = reinterpret_cast<FuncWithCaptureT*>(fp);
			this->cap.reset();
		}

		template <typename T, SOUP_RESTRICT(std::is_same_v<std::function<Ret(Args...)>, T>)>
		void operator=(T&& func) noexcept
		{
			fp = &redirect_to_std_function;
			cap = std::move(func);
		}

		[[nodiscard]] constexpr operator bool() const noexcept
		{
			return fp != nullptr;
		}

		void reset()
		{
			fp = nullptr;
			cap.reset();
		}
	};

	template <typename Func>
	struct Callback;

	template <typename Ret, typename...Args>
	struct Callback<Ret(Args...)> : public CallbackBase<Capture&&, Ret, Args...>
	{
		using Base = CallbackBase<Capture&&, Ret, Args...>;

		using Base::Base;

		Ret operator() (Args&&...args)
		{
			return Base::fp(std::forward<Args>(args)..., std::move(Base::cap));
		}
	};

	template <typename Ret, typename...Args>
	struct Callback<Ret(Args...) noexcept>
	{
		using FuncT = Ret(Args...) noexcept;
		using FuncWithCaptureT = Ret(Args..., Capture&&) noexcept;

		FuncWithCaptureT* fp;
		Capture cap;

		Callback() noexcept
			: fp(nullptr)
		{
		}

		Callback(const Callback&) = delete;

		Callback(Callback&& b) noexcept
			: fp(b.fp), cap(std::move(b.cap))
		{
		}

		Callback(FuncT* fp) noexcept
			: fp(reinterpret_cast<FuncWithCaptureT*>(fp))
		{
		}

		Callback(FuncWithCaptureT* fp) noexcept
			: fp(fp)
		{
		}

		Callback(FuncWithCaptureT* fp, Capture&& cap) noexcept
			: fp(fp), cap(std::move(cap))
		{
		}

		void set(FuncWithCaptureT* fp, Capture&& cap = {}) noexcept
		{
			this->fp = fp;
			this->cap = std::move(cap);
		}

		void operator=(FuncWithCaptureT* fp) noexcept
		{
			this->fp = fp;
			this->cap.reset();
		}

		void operator=(Callback&& b) noexcept
		{
			fp = b.fp;
			cap = std::move(b.cap);
		}

		void operator=(const Callback& b) noexcept = delete;

		void operator=(FuncT* fp) noexcept
		{
			this->fp = reinterpret_cast<FuncWithCaptureT*>(fp);
			this->cap.reset();
		}

		[[nodiscard]] constexpr operator bool() const noexcept
		{
			return fp != nullptr;
		}

		void reset()
		{
			fp = nullptr;
			cap.reset();
		}

		Ret operator() (Args&&...args)
		{
			return fp(std::forward<Args>(args)..., std::move(cap));
		}
	};

	template <typename Func>
	struct EventHandler;

	template <typename Ret, typename...Args>
	struct EventHandler<Ret(Args...)> : public CallbackBase<const Capture&, Ret, Args...>
	{
		using Base = CallbackBase<const Capture&, Ret, Args...>;

		using Base::Base;

		Ret operator() (Args&&...args) const
		{
			return Base::fp(std::forward<Args>(args)..., Base::cap);
		}
	};
}
