// Copyright Epic Games, Inc. All Rights Reserved.

#include "LuprexGameModeBase.h"
#include "PlayerControllerBase.h"
#include "LockedWrapper.h"
#include "lpx-drvutil.hpp"
#include "Misc/Paths.h"
#include "Tangible.h"
#include "TangibleManager.h"
#include "LuaCall.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/GameInstance.h"
#include "LuprexEditorSettings.h"

#include "Common.h"
#include "AnimQueue.h"
#include <string>
#include <string_view>

using namespace LpxCommonTypes;

ALuprexGameModeBase::ALuprexGameModeBase() {}
ALuprexGameModeBase::~ALuprexGameModeBase() {}


void ALuprexGameModeBase::ResetToInitialState()
{
	Playing = false;
	TickEnabled = true;

	// Stop the tick function.
	FWorldDelegates::OnWorldPostActorTick.Remove(OnWorldPostActorTickHandle);
	OnWorldPostActorTickHandle.Reset();

	FlxLockedWrapper w;

	// Release and close all sockets.
	if (Sockets != nullptr)
	{
		Sockets->ForceCloseEverything(w);
		Sockets.Reset();
	}

	// Delete the engine.
	if (w->release != nullptr)
	{
		w->release(w.Get());
	}

	// Clear the lua call assembly buffer.
	UlxEngineWrapper::GetLuaCallBuffer().clear();

	// Stop trapping log errors to the debugger.
	BreakToDebuggerLogVerbosityDevice.Reset();

	// Clear the PlayerID
	PlayerId = 0;

	// Reset the clocks.
	EngineSeconds = 0.0;
}

void ALuprexGameModeBase::UpdateConsoleOutput() {
	// Copy Luprex Stdout into the console.
	FlxLockedWrapper lockedwrap;
	if (Playing) {
		FString Text = lockedwrap.ChannelPrints();
		if (!Text.IsEmpty())
		{
			ConsoleAddOutput(Text);
		}
	}
}

void ALuprexGameModeBase::UpdateTangibles() {
	double radius = 100000.0; // Hardwired for now.
	using TanArray = UlxTangibleManager::TanArray;
	if (!Playing) return;
	UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
	TanArray alltans;
	{
		FlxLockedWrapper w;
		PlayerId = w.GetActor();
		IdView nearids = w.GetNear(PlayerId);
		TM->UpdateNearAccordingToLuprex(nearids);
		alltans = TM->GetAllTangibles();
		IdArray allids = TM->GetIds(alltans);
		StringViewVec allqueues = w.GetAnimationQueues(allids);
		for (int i = 0; i < alltans.Num(); i++) {
			alltans[i]->UpdateAnimationQueue(allqueues[i]);
		}
		// Debugging hook.
		volatile bool printaqs = false;
		if (printaqs) {
			for (int i = 0; i < alltans.Num(); i++) {
				UE_LOG(LogLuprex, Display, TEXT("\n--- AQ of %ld ---\n%s\n"), allids[i], *(alltans[i]->AnimTracker.DebugString()));
			}
		}
	}
	// This is where we run the blueprint code that updates animation
	// states.  Be aware that the blueprint code could call back
	// into the gamemode, which could in turn access the FlxLockedWrapper.
	// This is why we've released the FlxLockedWrapper before doing this.
	for (int i = 0; i < alltans.Num(); i++) {
		alltans[i]->MaybeExecuteAnimStateChanged();
	}
	TM->RecalcNearAccordingToUnreal(PlayerId, radius);
	TM->DeleteFarawayTangibles();
}

void ALuprexGameModeBase::UpdatePossessedTangible() {
	UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
	UlxTangible *ptan = TM->GetPossessedTangible();
	UlxTangible *tan = TM->GetTangible(PlayerId);
	APlayerController *ctrl = GetWorld()->GetFirstPlayerController();
	APawn *pawn = nullptr;
	if (tan != nullptr) pawn = Cast<APawn>(tan->GetActor());
	if (pawn == nullptr) {
		if (ptan != nullptr) {
			TM->SetPossessedTangible(nullptr);
			ctrl->UnPossess();
		}
	} else {
		if (ptan != tan) {
			TM->SetPossessedTangible(tan);
			ctrl->Possess(pawn);
		}
	}	
}

void ALuprexGameModeBase::UpdateLuaSourceCode() {
	FlxLockedWrapper lockedwrap;
	if (lockedwrap->get_rescan_lua_source(lockedwrap.Get()) || ReloadSource)
	{
		ReloadSource = false;
		drvutil::ostringstream srcpak;
		FString LuprexRoot = FPaths::Combine(FPaths::ProjectDir(), TEXT("luprex"));
		std::string srcpakerr = drvutil::package_lua_source(TCHAR_TO_UTF8(*LuprexRoot), &srcpak);
		if (!srcpakerr.empty())
		{
			FString FMessage((const UTF8CHAR *)(srcpakerr.c_str()));
			UE_LOG(LogLuprexIntegration, Error, TEXT("Trying to read Lua source: %s"), *FMessage);
		}
		else
		{
			std::string_view srcpakv = srcpak.view();
			lockedwrap->play_access(lockedwrap.Get(), AccessKind::INVOKE_LUA_SOURCE, 0, srcpakv.size(), srcpakv.data(), nullptr, nullptr);
		}
	}
}

void ALuprexGameModeBase::UpdateSocketsAndLua()
{
	FlxLockedWrapper lockedwrap;
	Sockets->Update(lockedwrap);
	lockedwrap->play_update(lockedwrap.Get(), EngineSeconds);
	Sockets->Update(lockedwrap);
}

void ALuprexGameModeBase::OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds)
{
	if(Playing && TickEnabled && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All))
	{
		EngineSeconds += deltaseconds;
		UpdateLuaSourceCode();
		UpdateSocketsAndLua();
		UpdateConsoleOutput();
		UpdateTangibles();
		UpdatePossessedTangible();
		AlxPlayerControllerBase *PC = Cast<AlxPlayerControllerBase>(GetWorld()->GetFirstPlayerController());
		if (PC != nullptr)
		{
			PC->UpdateLookAt();
			PC->UpdateInputMode();
		}
	}
}

void ALuprexGameModeBase::BeginPlay()
{
	ResetToInitialState();
	InitializeGlobalState();
	Super::BeginPlay();
}

void ALuprexGameModeBase::InitializeGlobalState()
{
	FlxLockedWrapper w;

	// Sanity checks.  Make sure everything is clean.
	checkf(w->engine == nullptr, TEXT("There should be no engine here."));

	// If we failed to initialize the wrapper, print an error message.
	if (w->play_initialize == nullptr)
	{
		UE_LOG(LogLuprexIntegration, Error, TEXT("Luprex wrapper initialization failed"));
	}

	// If wrapper is initialized, try to initialize the luprex engine.
	if (w->play_initialize != nullptr)
	{
		w->play_initialize(w.Get(), "lpxclient", "");
		if (w->error[0])
		{
			FString FMessage((const UTF8CHAR *)w->error);
			UE_LOG(LogLuprexIntegration, Error, TEXT("Calling Luprex play_initialize: %s"), *FMessage);
		}
		if (w->engine != nullptr) {
			UE_LOG(LogLuprexIntegration, Verbose, TEXT("Luprex initialization success."));
			Playing = true;
		}
	}

	// Possibly tell the engine to connect to a server.
	if (Playing) {
		UlxServerSelection* Server = GetDefault<UlxEditorSettings>()->ActiveServer.LoadSynchronous();
		if (Server) {
			FString Host = Server->Host.TrimStartAndEnd();
			if (!Host.IsEmpty()) {
				FTCHARToUTF8 utf8server(*Server->Host);
				w->play_access(w.Get(), AccessKind::CONNECT_TO_SERVER, 0, utf8server.Length(), utf8server.Get(), nullptr, nullptr);
			}
		}
	}

	// If we successfully created a luprex engine, create a socket system.
	if (Playing) {
		Sockets.Reset(FlxSockets::Create(w));
		std::string error = Sockets->GetError();
		check(error.empty());
	}

	if (Playing) {
		OnWorldPostActorTickHandle = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &ALuprexGameModeBase::OnWorldPostActorTick);
	}

	// If somebody generates a log message that's severe enough, break to debugger.
	BreakToDebuggerLogVerbosityDevice.Reset(
		new FlxBreakToDebuggerOutputDevice());
}

void ALuprexGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	ResetToInitialState();
	Super::EndPlay(EndPlayReason);
}

int64 ALuprexGameModeBase::GetPlayerId() {
	return PlayerId;
}

ALuprexGameModeBase *ALuprexGameModeBase::FromContext(const UObject *context) {
	ALuprexGameModeBase *result = context->GetWorld()->GetAuthGameMode<ALuprexGameModeBase>();
	if (result == nullptr) {
		UE_LOG(LogBlueprint, Fatal, TEXT("Not currently using a Luprex Game Mode."));
	}
	return result;
}

void ALuprexGameModeBase::TriggerReloadSource(const UObject *WorldContextObject) {
	ALuprexGameModeBase *GameMode = FromContext(WorldContextObject);
	GameMode->ReloadSource = true;
}


