// Copyright 2018-current Getnamo. All Rights Reserved


#include "SocketIOClient.h"
#include "SocketIONative.h"
#include "SIOMessageConvert.h"
#include "CULambdaRunnable.h"
#include "Runtime/Core/Public/HAL/ThreadSafeBool.h"

#define LOCTEXT_NAMESPACE "FSocketIOClientModule"

//struct 

class FSocketIOClientModule : public ISocketIOClientModule
{
public:
	//virtual TSharedPtr<FSocketIONative> NewValidNativePointer() override;
	virtual TSharedPtr<FSocketIONative> NewValidNativePointer(const bool bShouldUseTlsLibraries, const bool bShouldVerifyTLSCertificate) override;
	virtual TSharedPtr<FSocketIONative> ValidSharedNativePointer(FString SharedId, const bool bShouldUseTlsLibraries, const bool bShouldVerifyTLSCertificate) override;
	void ReleaseNativePointer(TSharedPtr<FSocketIONative> PointerToRelease) override;

	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;

private:
	FCriticalSection DeleteSection;

	//All native pointers manages by the plugin
	TArray<TSharedPtr<FSocketIONative>> PluginNativePointers;

	//Shared pointers, these will typically be alive past game world lifecycles
	TMap<FString, TSharedPtr<FSocketIONative>> SharedNativePointers;
	TSet<TSharedPtr<FSocketIONative>> AllSharedPtrs;	//reverse lookup

	FThreadSafeBool bHasActiveNativePointers;
};


void FSocketIOClientModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

	PluginNativePointers.Empty();
}

void FSocketIOClientModule::ShutdownModule()
{
	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
	// we call this function before unloading the module.

	/*
	Ensure we call release pointers, this will catch all the plugin scoped 
	connections pointers which don't get auto-released between game worlds.
	*/
	auto AllActivePointers = PluginNativePointers;
	for (auto& Pointer : AllActivePointers)
	{
		ReleaseNativePointer(Pointer);
	}
	AllActivePointers.Empty();

	//Wait for all pointers to release
	float Elapsed = 0.f;
	while (bHasActiveNativePointers)
	{
		FPlatformProcess::Sleep(0.01f);
		Elapsed += 0.01f;

		//if it takes more than 5 seconds, just quit
		if (Elapsed > 5.f)
		{
			UE_LOG(SocketIO, Warning, TEXT("FSocketIOClientModule::ShutdownModule force quit due to long wait to quit."));
			break;
		}
	}

	//Native pointers will be automatically released by uninitialize components
	PluginNativePointers.Empty();
}

TSharedPtr<FSocketIONative> FSocketIOClientModule::NewValidNativePointer(const bool bShouldUseTlsLibraries, const bool bShouldVerifyTLSCertificate)
{
	TSharedPtr<FSocketIONative> NewPointer = MakeShareable(new FSocketIONative(bShouldUseTlsLibraries, bShouldVerifyTLSCertificate));
	
	PluginNativePointers.Add(NewPointer);
	
	bHasActiveNativePointers = true;

	return NewPointer;
}

TSharedPtr<FSocketIONative> FSocketIOClientModule::ValidSharedNativePointer(FString SharedId, const bool bShouldUseTlsLibraries, const bool bShouldVerifyTLSCertificate)
{
	//Found key? return it
	if (SharedNativePointers.Contains(SharedId))
	{
		return SharedNativePointers[SharedId];
	}
	//Otherwise request a new id and return it
	else
	{
		TSharedPtr<FSocketIONative> NewNativePtr = NewValidNativePointer(bShouldUseTlsLibraries, bShouldVerifyTLSCertificate);
		SharedNativePointers.Add(SharedId, NewNativePtr);
		AllSharedPtrs.Add(NewNativePtr);
		return NewNativePtr;
	}
}

void FSocketIOClientModule::ReleaseNativePointer(TSharedPtr<FSocketIONative> PointerToRelease)
{
	//Remove shared ptr references if any
	if (AllSharedPtrs.Contains(PointerToRelease))
	{
		AllSharedPtrs.Remove(PointerToRelease);
		for (auto& Pair : SharedNativePointers)
		{
			if (Pair.Value == PointerToRelease)
			{
				SharedNativePointers.Remove(Pair.Key);
				break;
			}
		}
	}

	//Release the pointer on the background thread pool, this can take ~ 1 sec per connection
	FCULambdaRunnable::RunLambdaOnBackGroundThread([PointerToRelease, this]
	{
		if (PointerToRelease.IsValid())
		{
			//Ensure only one thread at a time removes from array 
			{
				FScopeLock Lock(&DeleteSection);
				PluginNativePointers.Remove(PointerToRelease);
			}

			//Disconnect, this can happen simultaneously
			if (PointerToRelease->bIsConnected)
			{
				PointerToRelease->SyncDisconnect();
			}

			//Update our active status
			bHasActiveNativePointers = PluginNativePointers.Num() > 0;
		}
	});
}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FSocketIOClientModule, SocketIOClient)
