#ifndef _HEADER_TEA_CALLBACK
#define _HEADER_TEA_CALLBACK

#include <vector>
#include <type_traits>

namespace TeaLib
{
	namespace Utility
	{
#define NOVOIDFUNC(FuncRetVal) template<typename Test = ReturnValue> typename std::enable_if<!std::is_void<Test>::value, FuncRetVal>::type
		template<typename ReturnValue, typename... Arguments>
		class ReturnCallback
		{
		private:
			// Typedef for comparing function pointers.
			typedef ReturnValue(ReturnCallback::*CompareHandle)(Arguments...);
			struct CallbackStructBase
			{
			public:
				virtual ~CallbackStructBase() {};
				virtual ReturnValue Call(Arguments...) = 0;
				virtual bool Compare(ReturnValue(*a_CallbackFunction)(Arguments...))
				{
					return false;
				};
				virtual bool Compare(void* a_ClassInstance, CompareHandle a_CallbackFunction)
				{
					return false;
				};
			};
			struct CallbackStruct : public CallbackStructBase
			{
			public:
				virtual ReturnValue Call(Arguments... a_Arg)
				{
					return m_CallbackFunction(a_Arg...);
				}
				virtual bool Compare(ReturnValue(*a_CallbackFunction)(Arguments...))
				{
					return (a_CallbackFunction == m_CallbackFunction);
				};
				ReturnValue(*m_CallbackFunction)(Arguments...);
			};
			template<typename ClassType> struct ClassCallbackStruct : public CallbackStructBase
			{
			public:
				virtual ReturnValue Call(Arguments... a_Arg)
				{
					return (m_ClassInstance->*m_CallbackFunction)(a_Arg...);
				}
				virtual bool Compare(void* a_ClassInstance, CompareHandle a_CallbackFunction)
				{
					CompareHandle compareHandle;
					memcpy(&compareHandle, &m_CallbackFunction, sizeof(CompareHandle));

					return ((a_ClassInstance == m_ClassInstance) && (a_CallbackFunction == compareHandle));
				};
				ClassType* m_ClassInstance;
				ReturnValue(ClassType::*m_CallbackFunction)(Arguments...);
			};
		public:
			virtual ~ReturnCallback()
			{
				for (int i = (int)m_CallbackFunctions.size() - 1; i >= 0; i--)
				{
					delete m_CallbackFunctions[i];
				}
				m_CallbackFunctions.clear();
			}
			void Call(Arguments... a_Arg)
			{
				for (auto it : m_CallbackFunctions)
				{
					it->Call(a_Arg...);
				}
			}
			NOVOIDFUNC(void) Call(std::vector<ReturnValue>* a_ReturnValues, Arguments... a_Arg)
			{
				for (auto it : m_CallbackFunctions)
				{
					a_ReturnValues->push_back(it->Call(a_Arg...));
				}
			}
			void Register(ReturnValue(*a_CallbackFunction)(Arguments...))
			{
				CallbackStruct* newCallback = new CallbackStruct();
				newCallback->m_CallbackFunction = a_CallbackFunction;
				m_CallbackFunctions.push_back(newCallback);
			}
			template<typename ClassType> void Register(ClassType* a_ClassInstance, ReturnValue(ClassType::*a_CallbackFunction)(Arguments...))
			{
				ClassCallbackStruct<ClassType>* newCallback = new ClassCallbackStruct<ClassType>();
				newCallback->m_ClassInstance = a_ClassInstance;
				newCallback->m_CallbackFunction = a_CallbackFunction;
				m_CallbackFunctions.push_back(newCallback);
			}
			// Removes the first registered callback that matches the function.
			void Deregister(ReturnValue(*a_CallbackFunction)(Arguments...))
			{
				for (unsigned int i = 0; i < m_CallbackFunctions.size(); i++)
				{
					if (m_CallbackFunctions[i]->Compare(a_CallbackFunction))
					{
						delete m_CallbackFunctions[i];
						m_CallbackFunctions.erase(m_CallbackFunctions.begin() + i);
						break;
					}
				}
			}
			// Removes the first registered callback that matches the class instance and the function.
			template<typename ClassType> void Deregister(ClassType* a_ClassInstance, ReturnValue(ClassType::*a_CallbackFunction)(Arguments...))
			{
				CompareHandle compareHandle;
				memcpy(&compareHandle, &a_CallbackFunction, sizeof(CompareHandle));
				for (unsigned int i = 0; i < m_CallbackFunctions.size(); i++)
				{
					if (m_CallbackFunctions[i]->Compare(a_ClassInstance, compareHandle))
					{
						delete m_CallbackFunctions[i];
						m_CallbackFunctions.erase(m_CallbackFunctions.begin() + i);
						break;
					}
				}
			}
		private:
			std::vector<CallbackStructBase*> m_CallbackFunctions;
		};
		// Alias template for callbacks that return void.
		template<typename... Arguments>
		using Callback = ReturnCallback<void, Arguments...>;
#undef NOVOIDFUNC
	};
};


#endif