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

#pragma once

#include "CoreMinimal.h"
#include "Identifier.h"

class IEvent;
typedef TSharedPtr<IEvent> EventPtr;

/// baseclass for an event
class EVENTMANAGERDEMO_API IEvent
{
public:
	/// returns the unique type identifier of this eventtype
	virtual HashValue getHash() const = 0;
	virtual FString getName() const = 0;


	/// serializes the data
	/// \param stream	stream to that all data will be written
	void serialize(FString& out_data)
	{
		HashValue hash = getHash();
		out_data.Append(FString::FromInt(hash));		// write type hash value
		out_data.Append("#|#");
		serializeBiDirectional(out_data, true);
		out_data.Append("#|#");
	}

	/// creates a new event from binary data
	/// \param stream	stream from that the data is taken
	/// \return pointer to a new event
	static EventPtr deserialize(const FString& data)
	{
		int32 found = data.Find("#|#");
		if (found != INDEX_NONE)
		{
			FString hash_string = data.Mid(0, found);

			// read hash/ident
			HashValue hash = FCString::Atoi(*hash_string);

			// find fabric
			auto hashFabricIt = getEventFabricList().Find(hash);

			if (hashFabricIt != nullptr)
			{
				// create event and deserialize by calling the fabric function
				EventPtr pNewEvent = (*hashFabricIt)();

				FString remaining_string = data.Mid(found + 3);

				pNewEvent->serializeBiDirectional(remaining_string, false);

				return pNewEvent;
			}
			else
			{
				return EventPtr();
			}
		}
		else
		{
			return EventPtr();
		}
	}

	/// registers an event for deserialization
	static void registerEventForDeserialization(const HashValue hash, const TFunction<EventPtr()>& fabricMethod)
	{
		getEventFabricList().Add(TPair<HashValue, TFunction<EventPtr()>>(hash, fabricMethod));
	}

protected:
	IEvent() {}
	virtual ~IEvent() {}

	/// \brief bidirectional serialization (read and write)
	/// \remarks you have to overwrite this method AND call there the parent function first
	virtual void serializeBiDirectional(FString& data, bool write) = 0;

	static TMap<HashValue, TFunction<EventPtr()>>& getEventFabricList()
	{
		static TMap<HashValue, TFunction<EventPtr()>> var;
		return var;
	}
};

/// Macro for event-registration
/// \param eventName	type name of the new event
/// \remarks call this in the cpp of every new event
#define REGISTER_EVENT(eventName) \
	class _EventRegisterClass##eventName \
	{ \
	public: \
		_EventRegisterClass##eventName() \
		{ \
			eventName evt; \
            IEvent::registerEventForDeserialization(evt.getHash(), []{ return EventPtr(new eventName); }); \
		} \
	}; const _EventRegisterClass##eventName _EventRegisterStartUpObject##eventName;
