// Fill out your copyright notice in the Description page of Project Settings.

#include "EventSystem/Public/EventManager.h"
#include "Async/AsyncResult.h"

EventManager EventManager::oneAndOnly;

EventManager::EventManager() : queuedEvents(), eventRegistry(TMap<HashValue, TMap<Identifier, EventCallback>>())
{
}

EventManager::~EventManager()
{
}

void EventManager::Init()
{
}

void EventManager::Release()
{
	if (!queuedEvents.IsEmpty())
	{
		triggerQueuedEvents();
		m_eventManagerCritialSection.Lock();
		queuedEvents.Empty();
		m_eventManagerCritialSection.Unlock();
	}

	eventRegistry.Empty();
	eventRegistry = TMap<HashValue, TMap<Identifier, EventCallback>>();
}

void EventManager::addListener(const EventCallback& callback, const uint32 eventHash, const Identifier& listenerName)
{
	FScopeLock scopeLock(&m_eventManagerCritialSection);
	// check if the eventtype already exists
	if (!eventRegistry.Contains(eventHash))
	{
		// create new entry
		eventRegistry.Add(TPair<HashValue, TMap<Identifier, EventCallback>>(eventHash, TMap<Identifier, EventCallback>()));
	}
	else
	{
		// check if listener already exists
		auto eventRegistryEntry = eventRegistry.Find(eventHash);
		if (eventRegistryEntry->Contains(listenerName))
		{
			UE_LOG(LogTemp, Error, TEXT("hash collision occured! %s"), *(listenerName.getName()));
		}
	}

	// add callback to registry
	eventRegistry.Find(eventHash)->Add(TPair<Identifier, EventCallback>(listenerName, callback));
}

void EventManager::removeListener(const Identifier& listenerName)
{
	FScopeLock scopeLock(&m_eventManagerCritialSection);
	for (auto eventRegistryEntry : eventRegistry)
	{
		eventRegistry.Find(eventRegistryEntry.Key)->Remove(listenerName);
	}
}

void EventManager::queueEvent(const EventPtr& pEvent)
{
	m_eventManagerCritialSection.Lock();
	queuedEvents.Add(pEvent);
	m_eventManagerCritialSection.Unlock();
}

void EventManager::triggerEvent(const EventPtr& pEvent)
{
	if (eventRegistry.Contains(pEvent->getHash()))
	{
		auto eventRegistryEntry = eventRegistry.Find(pEvent->getHash());
		for (const TPair<Identifier, EventCallback>& callbackEntry : *eventRegistryEntry)
		{
			callbackEntry.Value.Execute(pEvent);
		}
	};
}

void EventManager::triggerQueuedEvents()
{
	m_eventManagerCritialSection.Lock();
	TArray<EventPtr> queuedEvents_copy(queuedEvents);
	queuedEvents.Empty();
	m_eventManagerCritialSection.Unlock();

	for (const EventPtr& queuedEvent : queuedEvents_copy)
	{
		triggerEvent(queuedEvent);
	}
}