/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "Base/UUID.hpp"
#include <algorithm>
#include <functional>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <variant>
#include <vector>

namespace OpenVulkano
{
	class IEventHandler
	{
	public:
		virtual ~IEventHandler() = default;
		virtual void SetInvalid() = 0;
	};

	class INamedEventProcessor
	{
		static std::vector<INamedEventProcessor*> processors;

	public:
		// Keep this in sync with all implementations! synctag: NamedEventProcessor_Parameters_SYNC_TAG
		typedef std::variant<std::string, bool, int, float, double, UUID> Parameters;

		static bool HasProcessor() { return !processors.empty(); }

		static void NotifyAll(const std::string& eventName, const std::vector<Parameters>& parameters)
		{
			for(const auto& processor : processors)
			{
				processor->Notify(eventName, parameters);
			}
		}

		INamedEventProcessor() { processors.push_back(this); }

		virtual ~INamedEventProcessor()
		{
			processors.erase(std::remove_if(processors.begin(), processors.end(), [this](auto v) { return v==this; }), processors.end());
		}

		virtual void Notify(const std::string& eventName, const std::vector<Parameters>& parameters) const = 0;
	};

	template<typename... Arguments>
	class Event final
	{
	public:
		typedef std::function<void(Arguments...)> Function;


	private:
		enum class EventHandlerType { STATIC, INSTANCED, INSTANCED_SHARED_PTR, INSTANCED_WEAK_PTR, FUNCTIONAL, NAMED };

		//region Event Handlers
		class EventHandler : public IEventHandler
		{
			const EventHandlerType type;
			bool invalid;
		protected:
			EventHandler(EventHandlerType type) : type(type), invalid(false) {}
			virtual bool ShouldDelete(EventHandler* other) const = 0;

		public:
			virtual ~EventHandler() = default;

			bool ShouldBeDelete(EventHandler* other) const
			{
				if (other == this) return true;
				if (invalid) return true;
				if (!other || other->type != type) return false;
				return ShouldDelete(other);
			}

			virtual void Notify(Arguments... args) const = 0;

			[[nodiscard]] bool IsInvalid() const { return invalid; }

			void SetInvalid() final { invalid = true; }
		};

		class StaticEventHandler final : public EventHandler
		{
		public:
			typedef void (*Method)(Arguments...);

			StaticEventHandler(Method method) : EventHandler(EventHandlerType::STATIC), method(method) {}

			void Notify(Arguments... args) const override
			{
				method(args...);
			}

		protected:
			bool ShouldDelete(EventHandler* other) const override
			{
				return static_cast<StaticEventHandler*>(other)->method == this->method;
			}

		private:
			Method method;
		};

		template<class Instance, class Method>
		class InstancedEventHandler final : public EventHandler
		{
		public:
			InstancedEventHandler(Instance* instance, Method method) : EventHandler(EventHandlerType::INSTANCED), instance(instance), method(method) {}

			void Notify(Arguments... args) const override
			{
				(instance->*method)(args...);
			}

		protected:
			bool ShouldDelete(EventHandler* other) const override
			{
				InstancedEventHandler* otherHandler = static_cast<InstancedEventHandler*>(other);
				return otherHandler->instance == instance && otherHandler->method == this->method;
			}

		private:
			Instance* instance;
			Method method;
		};

		template<class Instance, class Method>
		class InstancedSharedPtrEventHandler final : public EventHandler
		{
		public:
			InstancedSharedPtrEventHandler(std::shared_ptr<Instance> instance, Method method) : EventHandler(EventHandlerType::INSTANCED_SHARED_PTR), instance(instance), method(method) {}

			void Notify(Arguments... args) const override
			{
				(instance.get()->*method)(args...);
			}

		protected:
			bool ShouldDelete(EventHandler* other) const override
			{
				InstancedSharedPtrEventHandler* otherHandler = static_cast<InstancedSharedPtrEventHandler*>(other);
				return otherHandler->instance == instance && otherHandler->method == this->method;
			}

		private:
			std::shared_ptr<Instance> instance;
			Method method;
		};

		template<class Instance, class Method>
		class InstancedWeakPtrEventHandler final : public EventHandler
		{
		public:
			InstancedWeakPtrEventHandler(std::weak_ptr<Instance> instance, Method method) : EventHandler(EventHandlerType::INSTANCED_WEAK_PTR), instance(instance), method(method) {}

			void Notify(Arguments... args) const override
			{
				if (auto instancePtr = instance.lock())
				{
					(instancePtr.get()->*method)(args...);
				}
				else
				{
					const_cast<InstancedWeakPtrEventHandler*>(this)->SetInvalid();
				}
			}

		protected:
			bool ShouldDelete(EventHandler* other) const override
			{
				InstancedWeakPtrEventHandler* otherHandler = static_cast<InstancedWeakPtrEventHandler*>(other);
				if (otherHandler->method == this->method)
				{
					if (auto ptr = instance.lock())
					{
						return ptr == otherHandler->instance.lock();
					}
					return true;
				}
				
				return false;
			}

		private:
			std::weak_ptr<Instance> instance;
			Method method;
		};

		class FunctionalEventHandler final : public EventHandler
		{
		public:
			FunctionalEventHandler(Function function) : EventHandler(EventHandlerType::FUNCTIONAL), function(function) {}

			void Notify(Arguments... args) const override
			{
				function(args...);
			}

		protected:
			bool ShouldDelete(EventHandler* other) const override
			{
				return false;
			}

		private:
			Function function;
		};

		class NamedEventHandler final : public EventHandler
		{
			std::string m_name;

		public:
			NamedEventHandler(const std::string& name) :
					EventHandler(EventHandlerType::NAMED), m_name(name)
			{}

			void Notify(Arguments... args) const override
			{
				NotifyImpl(std::forward<Arguments>(args)...);
			}

			void NotifyImpl(Arguments... args) const
			{
				if (INamedEventProcessor::HasProcessor())
				{
					std::vector<INamedEventProcessor::Parameters> attributes;
					attributes.reserve(sizeof...(Arguments));
					if constexpr (!std::conjunction_v<std::is_enum<std::remove_reference_t<Arguments>>...>)
					{
						(attributes.emplace_back(args), ...);
					}
					else
					{
						auto fill = [&](auto&& arg) {
							if constexpr (std::is_enum_v<std::remove_reference_t<decltype(arg)>>)
								attributes.emplace_back(static_cast<int>(arg));
							else
								attributes.emplace_back(std::forward<decltype(arg)>(arg));
						};
						(fill(std::forward<Arguments>(args)), ...);
					}
					INamedEventProcessor::NotifyAll(m_name, attributes);
				}
			}

		protected:
			bool ShouldDelete(EventHandler* other) const override
			{
				return false;
			}
		};
		//endregion

		std::vector<std::unique_ptr<EventHandler>> handlers;
		mutable std::shared_mutex mutex;

		void Remove(EventHandler* referenceHandler)
		{
			std::unique_lock<std::shared_mutex> lock(mutex);
			for(size_t i = 0; i < handlers.size(); i++)
			{
				EventHandler* handler = handlers[i].get();
				if (handler->ShouldBeDelete(referenceHandler))
				{
					size_t end = handlers.size() - 1;
					if (i < end)
					{
						handlers[i] = std::move(handlers[end]);
					}
					handlers.pop_back();
				}
			}
		}

		IEventHandler* Add(EventHandler* handler)
		{
			std::unique_lock<std::shared_mutex> lock(mutex);
			handlers.emplace_back(handler);
			return handler;
		}

	public:
		Event() = default;

		Event(const std::string& name)
		{
			RegisterName(name);
		}

		Event(const Event& event) = delete;

		Event(Event&& event) = default;

		[[nodiscard]] bool HasHandlers() const
		{
			return !handlers.empty();
		}

		void NotifyAll(Arguments... args) const
		{
			std::shared_lock<std::shared_mutex> lock(mutex);
			for(auto& handler : handlers)
			{
				if (!handler->IsInvalid())
					handler->Notify(args...);
			}
		}
		
		void operator()(Arguments... args) const
		{
			NotifyAll(args...);
		}

		IEventHandler* RegisterName(const std::string& name)
		{
			return Add(new NamedEventHandler(name));
		}

		template<class Instance, class Method>
		IEventHandler* RegisterHandler(Instance* instance, Method method)
		{
			return Add(new InstancedEventHandler<Instance, Method>(instance, method));
		}

		template<class Instance, class Method>
		void UnregisterHandler(Instance* instance, Method method)
		{
			auto handler = InstancedEventHandler<Instance, Method>(instance, method);
			Remove(&handler);
		}

		template<class Instance, class Method>
		IEventHandler* operator +=(std::pair<Instance*, Method> instancedMethodPair)
		{
			return RegisterHandler(instancedMethodPair.first, instancedMethodPair.second);
		}
		
		template<class Instance, class Method>
		void operator -=(std::pair<Instance*, Method> instancedMethodPair)
		{
			UnregisterHandler(instancedMethodPair.first, instancedMethodPair.second);
		}

		template<class Instance, class Method>
		IEventHandler* RegisterHandler(const std::shared_ptr<Instance>& instance, Method method)
		{
			return Add(new InstancedSharedPtrEventHandler<Instance, Method>(instance, method));
		}

		template<class Instance, class Method>
		void UnregisterHandler(const std::shared_ptr<Instance>& instance, Method method)
		{
			auto handler = InstancedSharedPtrEventHandler<Instance, Method>(instance, method);
			Remove(&handler);
		}

		template<class Instance, class Method>
		IEventHandler* operator +=(std::pair<std::shared_ptr<Instance>, Method> instancedMethodPair)
		{
			return RegisterHandler(instancedMethodPair.first, instancedMethodPair.second);
		}

		template<class Instance, class Method>
		void operator -=(std::pair<std::shared_ptr<Instance>, Method> instancedMethodPair)
		{
			UnregisterHandler(instancedMethodPair.first, instancedMethodPair.second);
		}

		template<class Instance, class Method>
		IEventHandler* RegisterHandler(const std::weak_ptr<Instance>& instance, Method method)
		{
			return Add(new InstancedWeakPtrEventHandler<Instance, Method>(instance, method));
		}

		template<class Instance, class Method>
		void UnregisterHandler(const std::weak_ptr<Instance>& instance, Method method)
		{
			auto handler = InstancedWeakPtrEventHandler<Instance, Method>(instance, method);
			Remove(&handler);
		}

		template<class Instance, class Method>
		IEventHandler* RegisterHandlerWeak(const std::weak_ptr<Instance>& instance, Method method)
		{
			return Add(new InstancedWeakPtrEventHandler<Instance, Method>(instance, method));
		}

		template<class Instance, class Method>
		void UnregisterHandlerWeak(const std::weak_ptr<Instance>& instance, Method method)
		{
			auto handler = InstancedWeakPtrEventHandler<Instance, Method>(instance, method);
			Remove(&handler);
		}

		template<class Instance, class Method>
		IEventHandler* operator +=(std::pair<std::weak_ptr<Instance>, Method> instancedMethodPair)
		{
			return RegisterHandlerWeak(instancedMethodPair.first, instancedMethodPair.second);
		}

		template<class Instance, class Method>
		void operator -=(std::pair<std::weak_ptr<Instance>, Method> instancedMethodPair)
		{
			UnregisterHandlerWeak(instancedMethodPair.first, instancedMethodPair.second);
		}

		IEventHandler* RegisterHandler(void (*method)(Arguments...))
		{
			return Add(new StaticEventHandler(method));
		}

		void UnregisterHandler(void (*method)(Arguments...))
		{
			auto handler = StaticEventHandler(method);
			Remove(&handler);
		}

		IEventHandler* operator +=(void (*method)(Arguments...))
		{
			return RegisterHandler(method);
		}
		
		void operator -=(void (*method)(Arguments...))
		{
			UnregisterHandler(method);
		}

		IEventHandler* RegisterHandler(Function function)
		{
			return Add(new FunctionalEventHandler(function));
		}

		void UnregisterHandler(EventHandler* handler)
		{
			Remove(handler);
		}

		IEventHandler* operator +=(Function function)
		{
			return RegisterHandler(function);
		}
		
		void operator -=(IEventHandler* handler)
		{
			if (EventHandler* handlerRef = dynamic_cast<EventHandler*>(handler))
			{
				Remove(handlerRef);
			}
		}
	};

	template<class Instance, class Method>
	constexpr std::pair<Instance*, Method> EventHandler(Instance* instance, Method method)
	{
		return std::pair<Instance*, Method>(instance, method);
	}

	template<class Instance, class Method>
	constexpr std::pair<std::shared_ptr<Instance>, Method> EventHandler(const std::shared_ptr<Instance>& instance, Method method)
	{
		return std::pair<std::shared_ptr<Instance>, Method>(instance, method);
	}

	template<class Instance, class Method>
	constexpr std::pair<std::weak_ptr<Instance>, Method> EventHandler(const std::weak_ptr<Instance>& instance, Method method)
	{
		return std::pair<std::weak_ptr<Instance>, Method>(instance, method);
	}

	template<class Instance, class Method>
	constexpr std::pair<std::weak_ptr<Instance>, Method> EventHandlerWeak(const std::weak_ptr<Instance>& instance, Method method)
	{
		return std::pair<std::weak_ptr<Instance>, Method>(instance, method);
	}
}
