////////////////////////////////////////////////////////////
//
// LockedWrapper.h
//
// Mutex-guarded access to the EngineWrapper.
//
////////////////////////////////////////////////////////////

#pragma once

#include "CoreMinimal.h"
#include "lpx-enginewrapper.hpp"
#include "Common.h"
#include "StreamBuffer.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "LockedWrapper.generated.h"

////////////////////////////////////////////////////////////
//
// UlxEngineWrapper
//
// GameInstanceSubsystem that owns the EngineWrapper and
// a Mutex to lock it. To access the EngineWrapper,
// construct a FlxLockedWrapper.
//
////////////////////////////////////////////////////////////

UCLASS()
class INTEGRATION_API UlxEngineWrapper : public UGameInstanceSubsystem {
	GENERATED_BODY()

private:
	static UlxEngineWrapper* Instance;

	// Called by luprex to output debugging messages.
	// A thin wrapper around UE_LOG.
	//
	static void DPrintHook(const char *Msg, size_t Size);

	FCriticalSection Mutex;
	EngineWrapper Wrapper;

	// The Lua Call Assembly Buffer. Used to build up a call
	// across multiple UFUNCTION invocations.  This is not protected
	// by the mutex, so it should only be used by the game thread.
	//
	FlxStreamBuffer LuaCallBuffer;

public:
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

	// Get the Lua Call Assembly Buffer.
	// Only called from the game thread.
	//
	static FlxStreamBuffer& GetLuaCallBuffer() { return Instance->LuaCallBuffer; }

	friend class FlxLockedWrapper;
};

////////////////////////////////////////////////////////////
//
// FlxLockedWrapper
//
// RAII lock guard. The constructor claims the mutex,
// the destructor releases it. Use operator-> to
// access the EngineWrapper.
//
////////////////////////////////////////////////////////////

class FlxLockedWrapper {
private:
	UlxEngineWrapper& Lockable;

public:
	// Import these types into our namespace.
	//
	using IdArray       = LpxCommonTypes::IdArray;
	using IdView        = LpxCommonTypes::IdView;
	using StringViewVec = LpxCommonTypes::StringViewVec;

public:
	// The constructor claims the mutex.
	//
	FlxLockedWrapper() : Lockable(*UlxEngineWrapper::Instance) {
		Lockable.Mutex.Lock();
	}

	// The destructor releases the mutex.
	//
	~FlxLockedWrapper() {
		Lockable.Mutex.Unlock();
	}

	// Operator-> accesses the EngineWrapper.
	//
	EngineWrapper* operator ->() {
		return &Lockable.Wrapper;
	}

	// Get a pointer to the EngineWrapper. Not very
	// safe because you could keep the pointer after
	// the LockedWrapper is destroyed. Don't do that.
	//
	EngineWrapper* Get() {
		return &Lockable.Wrapper;
	}

	// Fetch Stdout as a string.
	//
	FString ChannelPrints();

	// Get the current Actor ID.
	//
	int64 GetActor();

	// Get the list of tangibles near the actor.
	//
	// This function is fast but not free. You should
	// fetch this once per frame and then store the
	// IdView somewhere (like in the TangibleManager).
	//
	IdView GetNear(int64 id);

	// Get animation queues.
	//
	// The array returned by this is valid until the
	// next time you call this.
	//
	StringViewVec GetAnimationQueues(IdView ids);

	// Call a Lua function. The datapk contains the
	// serialized class name, function name, and
	// arguments. If place_id is 0, defaults to the
	// current actor. The OnResult callback receives
	// the raw return data while the lock is held;
	// use it to copy the data before it goes away.
	//
	void ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction<void(std::string_view)> OnResult);

	// Invoke a Lua function (fire-and-forget, no
	// return values).
	//
	void InvokeLuaFunction(std::string_view datapk, int64 place_id);

	// Validate a Lua expression. Returns a syntax
	// classification and an error message. The error
	// message is empty if the code is valid.
	//
	ElxLuaSyntaxCheck ValidateLuaExpr(const FString &Code, FString &ErrorMessage);

	// Execute a Lua expression.
	//
	void InvokeLuaExpr(const FString &Code);
};
