/*
 * 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/.
 */

#include "NamedEventProcessor.h"
#include "Base/Logger.hpp"
#include <map>
#include <typeindex>

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

namespace OpenVulkano
{
	namespace
	{
		NamedEventProcessor appleNotificationNamedEventProcessor;
	
		constexpr int LONGEST_TYPE_NAME = std::max({ sizeof(@encode(NSString*)), sizeof(@encode(BOOL)), sizeof(@encode(int)), sizeof(@encode(float)), sizeof(@encode(double)) });
	
		std::map<std::type_index, std::string> TYPE_NAME_MAP = {
				{ std::type_index(typeid(std::string)), @encode(NSString*) },
				{ std::type_index(typeid(bool)), @encode(BOOL) },
				{ std::type_index(typeid(int)), @encode(int) },
				{ std::type_index(typeid(float)), @encode(float) },
				{ std::type_index(typeid(double)), @encode(double) },
				{ std::type_index(typeid(UUID)), @encode(NSUUID*) }
		};

		// Keep this in sync with INamedEventProcessor! synctag: NamedEventProcessor_Parameters_SYNC_TAG
		union ParameterValue
		{
			NSString* string;
			BOOL boolean;
			int integer;
			float fp32;
			double fp64;
			NSUUID* uuid;
		};

		std::string BuildTypeName(const std::vector<INamedEventProcessor::Parameters>& parameters)
		{
			std::string typeName("@:");
			typeName.reserve(2 + parameters.size() * (LONGEST_TYPE_NAME + 2));
			for(const auto& parameter : parameters)
			{
				typeName.append(std::visit([](auto&& arg) {
					return TYPE_NAME_MAP.find(typeid(std::decay_t<decltype(arg)>))->second;
				}, parameter));
			}
			return typeName;
		}

		template <typename T>
		ParameterValue CreateValue(const T& parameter)
		{
			ParameterValue value;
			reinterpret_cast<T&>(value) = parameter;
			return value;
		}
	
		ParameterValue CreateValue(const std::string& parameter)
		{
			ParameterValue value;
			value.string = [NSString stringWithUTF8String:parameter.c_str()];
			return value;
		}
	
		ParameterValue CreateValue(const UUID& parameter)
		{
			ParameterValue value;
			auto uuidData = parameter.binary();
			value.uuid = [[NSUUID alloc] initWithUUIDBytes:uuidData.data()];
			return value;
		}

		std::string SelectorType(Method m)
		{
			std::string sig;
			for (auto i = 0u, count = method_getNumberOfArguments(m); i < count; ++i)
			{
				auto argType = method_copyArgumentType(m, i);
				sig += std::string(argType);
				free(argType);
			}
			return sig;
		}

		SEL GetInitForClass(Class c, const std::string &initType)
		{
			SEL init = nullptr;
			for (auto clazz = c; !init && clazz; clazz = class_getSuperclass(clazz))
			{
				if (Method *methods = class_copyMethodList(clazz, nullptr))
				{
					for (int i = 0; methods[i]; i++)
					{
						const std::string name = sel_getName(method_getName(methods[i]));
						const std::string type = SelectorType(methods[i]);
						if (0 == name.find("init") && type == initType)
						{
							init = method_getName(methods[i]);
							break;
						}
					}
					free(methods);
				}
			}
			return init;
		}

		Class FindGenericEventClass(const std::vector<std::string>& prefixes,
									const std::vector<std::string>& suffixes,
									const std::string& eventName)
		{ // Searching for class with all registered prefix/suffix combinations
			for (const std::string& suffix : suffixes)
			{
				for (const std::string& prefix: prefixes)
				{
					NSString* eventClassName = [NSString stringWithUTF8String:(prefix + eventName + suffix).c_str()];
					if (Class eventClass = NSClassFromString(eventClassName))
					{
						return eventClass;
					}
				}
			}
			return nullptr;
		}
	}

	void NamedEventProcessor::Notify(const std::string& eventName, const std::vector<Parameters>& parameters) const
	{
		@autoreleasepool
		{
			if (Class eventClass = FindGenericEventClass(m_classNamePrefixes, m_classNameSuffixes, eventName))
			{
				if (SEL init = GetInitForClass(eventClass, BuildTypeName(parameters)))
				{
					NSObject* event = [eventClass alloc];
					auto inv = [NSInvocation invocationWithMethodSignature:[event methodSignatureForSelector:init]];
					[inv setTarget:event];
					[inv setSelector:init];
					for (int i = 0; i < parameters.size(); i++)
					{
						auto arg = std::visit([](auto&& arg) { return CreateValue(arg); }, parameters[i]);
						[inv setArgument:&arg atIndex:(i + 2)];
					}
					[inv invoke];
					for (Class clazz = eventClass; clazz && clazz != NSObject.class; clazz = class_getSuperclass(clazz))
					{
						NSString* notificationName;
						if (m_useClassNameAsEventName) notificationName = NSStringFromClass(clazz);
						else notificationName = [NSString stringWithUTF8String:eventName.c_str()];
						[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:event userInfo:nil];
					}
					[event release];
				}
				else
				{
					Logger::MANAGER->error("Cannot find constructor for named event {}", eventName);
				}
			}
			else
			{
				Logger::MANAGER->debug("Cannot find implementation for named event {}", eventName);
			}
		}
	}
}
