
#include "LockedWrapper.h"
#include "lpx-drvutil.hpp"
#include "Misc/Paths.h"

using namespace LpxCommonTypes;

UlxEngineWrapper* UlxEngineWrapper::Instance = nullptr;

void UlxEngineWrapper::Initialize(FSubsystemCollectionBase& Collection) {
	Super::Initialize(Collection);
	Instance = this;

	// Open the DLL and hook up all the function pointers.
#if PLATFORM_LINUX
	FString dll = FPaths::Combine(FPaths::ProjectDir(), TEXT("luprex/build/Linux/luprexlib.so"));
#elif PLATFORM_WINDOWS
	FString dll = FPaths::Combine(FPaths::ProjectDir(), TEXT("luprex/build/Windows/luprexlib.dll"));
#else
	#error "Unsupported platform"
#endif
	UE_LOG(LogLuprex, Verbose, TEXT("Luprex DLL Path: %s"), *dll);
	void* DLL = FPlatformProcess::GetDllHandle(*dll);
	if (DLL != nullptr) {
		using InitFn = void (*)(EngineWrapper*);
		InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper"));
		if (init != nullptr) {
			init(&Wrapper);
			Wrapper.hook_dprint(DPrintHook);
		}
	}
}

void UlxEngineWrapper::Deinitialize() {
	Instance = nullptr;
	Super::Deinitialize();
}


void UlxEngineWrapper::DPrintHook(const char *Msg, size_t Size)
{
	FString FMessage(Size, (const UTF8CHAR *)Msg);
	UE_LOG(LogLuprex, Error, TEXT("%s"), *FMessage);
}

FString FlxLockedWrapper::ChannelPrints() {
	if (Lockable.Wrapper.get_have_prints(Get()))
	{
		uint32_t ndata; const char* data;
		Lockable.Wrapper.play_access(Get(), AccessKind::CHANNEL_PRINTS, 0, 0, "", &ndata, &data);
		if (ndata > 0)
		{
			FString result(ndata, (const UTF8CHAR *)data);
			return result;
		}
	}
	return TEXT("");
}

int64 FlxLockedWrapper::GetActor() {
	return Lockable.Wrapper.get_actor_id(Get());
}

IdView FlxLockedWrapper::GetNear(int64 id) {
	uint32 size;
	int64* data;
	Lockable.Wrapper.get_tangibles_near(Get(), id, &size, (int64_t**)&data);
	return IdView(data, size);
}

StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) {
	// This is only called from the game thread, so
	// these static buffers are safe without locking.
	static TArray<uint32> AQLenBuffer;
	static TArray<const char*> AQStrBuffer;

	// How many animation queues are we fetching?
	int num = ids.Num();

	// Enlarge the buffers if necessary.
	// Add a little extra space so we don't have to enlarge
	// the buffers every time we get a new tangible.
	if (num > AQLenBuffer.Num()) {
		AQLenBuffer.SetNum(num + 1000);
		AQStrBuffer.SetNum(num + 1000);
	}

	// Get pointers to the buffers.
	uint32* LenBuf = AQLenBuffer.GetData();
	const char** StrBuf = AQStrBuffer.GetData();

	// Get the animation queues into the buffers.
	Lockable.Wrapper.get_animation_queues(Get(), num, (const int64_t *)ids.GetData(), LenBuf, StrBuf);

	// Transfer data from static buffers into an array of string_view
	StringViewVec result;
	result.SetNum(num);
	for (int i = 0; i < num; i++) {
		result[i] = std::string_view(StrBuf[i], LenBuf[i]);
	}
	return result;
}

ElxLuaSyntaxCheck FlxLockedWrapper::ValidateLuaExpr(const FString &Code, FString &ErrorMessage) {
	FTCHARToUTF8 UCode(*Code);
	uint32_t retpklen;
	const char *retpk;
	Lockable.Wrapper.play_access(Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
	ErrorMessage = FString(retpklen, (const UTF8CHAR*)retpk);
	if (ErrorMessage.IsEmpty())
		return ElxLuaSyntaxCheck::ValidLua;
	if (ErrorMessage == TEXT("slash command"))
		return ElxLuaSyntaxCheck::SlashCommand;
	if (ErrorMessage == TEXT("white space"))
		return ElxLuaSyntaxCheck::Whitespace;
	if (ErrorMessage == TEXT("truncated lua"))
		return ElxLuaSyntaxCheck::TruncatedLua;
	return ElxLuaSyntaxCheck::InvalidLua;
}

void FlxLockedWrapper::ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction<void(std::string_view)> OnResult) {
	if (place_id == 0) place_id = GetActor();
	uint32_t retpklen;
	const char *retpk;
	Lockable.Wrapper.play_access(Get(), AccessKind::PROBE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
	OnResult(std::string_view(retpk, retpklen));
}

void FlxLockedWrapper::InvokeLuaFunction(std::string_view datapk, int64 place_id) {
	if (place_id == 0) place_id = GetActor();
	uint32_t retpklen;
	const char *retpk;
	Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
}

void FlxLockedWrapper::InvokeLuaExpr(const FString &Code) {
	FTCHARToUTF8 UCode(*Code);
	uint32_t retpklen;
	const char *retpk;
	Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
}
