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


#include "BP_InnoactivePortalAccessControlHandler.h"
#include "Interfaces/IPluginManager.h"
#include "../Public/picosha2.h"
#include "AccessControlLibrary/PortalAuthorization.h"
#include <vector>
#include <iterator>
#include <sstream>
#include <ios>
#include <string>

const FString pluginName = FString("InnoactivePortalAccessControl");

using namespace Innoactive::Portal::AccessControl::Launcher;

ABP_InnoactivePortalAccessControlHandler::ABP_InnoactivePortalAccessControlHandler()
{
	LogPluginVersion();
}

void ABP_InnoactivePortalAccessControlHandler::ReleaseResources()
{
	if (authThreadHandler != NULL)
	{
		authThreadHandler->Stop();
		authThreadHandler->Exit();
		delete authThreadHandler;
		authThreadHandler = NULL;
	}
}

void ABP_InnoactivePortalAccessControlHandler::Success(FString message)
{
	DebugLog(message);
	ReleaseResources();
	this->OnAuthorizationSuccessful();
}

void ABP_InnoactivePortalAccessControlHandler::Fail(FString error)
{
	DebugLog(error);
	currentLogMessage = new FString(error);
	ReleaseResources();
	this->OnAuthorizationFailed(*error);
}

void ABP_InnoactivePortalAccessControlHandler::StartAuthorization(FString clientId)
{
#if WITH_EDITOR
	Success("Authorization always succeeds in editor.");
	return;
#endif

	if (this->CheckAccessControlLibrariesIntegrity() == false)
	{
		Fail("Authorization precondition failed. Please check your credentials or contact your administrator.");
		return;
	}

	if (clientId == "")
	{
		Fail("Client id is empty");
		return;
	}

	DebugLog("Initializing Access Control Bridge...");
	PortalAuthorization::Initialize();
	DebugLog("Access Control Bridge is initialized");

	if (FPlatformProcess::SupportsMultithreading() && useMultithreading)
	{
		DebugLog("Creating new thread to call library");

		// Create new thread to perform the call to the library and receive and validate the token
		authThreadHandler = new AuthThreadHandler(this, clientId);
	}
	else
	{
		Authorize(*clientId);
	}
}

FString ABP_InnoactivePortalAccessControlHandler::GetLibraryPath(FString fileName)
{
#if WITH_EDITOR
	return *FPaths::Combine(*FPaths::ProjectDir(), FString("\\Binaries\\Win64"), fileName);
#else
	return *FPaths::Combine(FPlatformProcess::BaseDir(), fileName);
#endif
}

bool ABP_InnoactivePortalAccessControlHandler::CheckAccessControlLibrariesIntegrity()
{
	FString baseLibrarySHA256Hash = FString("9a3ac8bee4af3628e18490201b9b3d6387c8819a49df0e2ce3456a01ac3b58f0");
	bool isBaseLibraryIntact = this->CheckLibraryIntegrity(this->GetLibraryPath("Innoactive.Portal.AccessControl.Launcher.dll"), baseLibrarySHA256Hash);

	FString bridgeLibrarySHA256Hash = FString("29408dd35f4da6b7d6d3c775a57aaa26ae7c7fc79bf22e18b1c07332295bf372");
	bool isBridgeLibraryIntact = this->CheckLibraryIntegrity(this->GetLibraryPath("Innoactive.Portal.AccessControl.Launcher.CppBridge.dll"), bridgeLibrarySHA256Hash);

	return isBaseLibraryIntact && isBridgeLibraryIntact;
}

bool ABP_InnoactivePortalAccessControlHandler::CheckLibraryIntegrity(FString filePath, FString expectedHash)
{
	FString libraryHash = this->GetSHA256Hash(filePath);

	return expectedHash.Equals(libraryHash);
}

FString ABP_InnoactivePortalAccessControlHandler::GetSHA256Hash(FString filePath)
{
	std::ifstream fileStream(*filePath, std::ios::binary);
	std::vector<unsigned char> hashResult(picosha2::k_digest_size);
	picosha2::hash256(fileStream, hashResult.begin(), hashResult.end());

	std::ostringstream oss;
	picosha2::output_hex(hashResult.begin(), hashResult.end(), oss);
	std::string sha256HashString = oss.str();

	FString sha256Hash(sha256HashString.c_str());
	return sha256Hash;
}

void ABP_InnoactivePortalAccessControlHandler::Authorize(FString clientId)
{
	DebugLog("Starting authorization via Access Control Launcher Bridge ...");

	bool isAuthorized = PortalAuthorization::AuthorizeApplication(TCHAR_TO_UTF8(*clientId));

	// make sure to execute this on the main thread again, see
	// https://forums.unrealengine.com/development-discussion/c-gameplay-programming/68237-main-thread-and-thread?p=933124#post933124
	AsyncTask(ENamedThreads::GameThread, [this, isAuthorized]()
		{
			if (isAuthorized)
			{
				Success("Authorization is successful.");
			}
			else
			{
				Fail("Authorization failed. Please check your credentials or contact your administrator.");
			}
		});
}

void ABP_InnoactivePortalAccessControlHandler::LogPluginVersion()
{
	TSharedPtr<IPlugin> plugin = IPluginManager::Get().FindPlugin(pluginName);

	if (plugin.IsValid())
	{
		const FPluginDescriptor& pluginDescriptor = plugin->GetDescriptor();
		DebugLog("Loaded " + pluginName + " v" + pluginDescriptor.VersionName + ".");
	}
	else 
	{
		DebugLog("Plugin information for " + pluginName + " could not be found.");
	}
}

void ABP_InnoactivePortalAccessControlHandler::DebugLog(FString info)
{
	if (showLogs)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::White, (TEXT("[Innoactive Access Control] %s"), *info));
		UE_LOG(LogTemp, Log, TEXT("[Innoactive Access Control] %s"), *info);
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT("[Innoactive Access Control] %s"), *info);
	}
}


FString ABP_InnoactivePortalAccessControlHandler::GetCurrentLogMessage()
{
	if (currentLogMessage == NULL)
	{
		currentLogMessage = new FString("...");
	}
	return *currentLogMessage;
}


AuthThreadHandler::~AuthThreadHandler()
{
	if (authHandler != NULL)
	{
		authHandler->DebugLog("Stopping authorization thread.");
	}

	delete Thread;
	Thread = NULL;
}

uint32 AuthThreadHandler::Run()
{
	if (authHandler != NULL)
	{
		authHandler->DebugLog("New thread successfully started. Perform actual function call");
	}

	authHandler->Authorize(clientId);
	return 0;
}
