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


#include "Tangible.h"
#include "TangibleManager.h"
#include "AssetLookup.h"
#include "LuprexGameModeBase.h"

#define DEFAULT_BLUEPRINT (TEXT("StaticMesh"))

UlxTangible::UlxTangible()
{
	Manager = nullptr;
	TangibleId = -1;
	CurrentActor = nullptr;
	ActorBlueprintName = TEXT("");
	NearAccordingToLuprex = false;
	NearAccordingToUnreal = false;
}

void UlxTangible::Init(UlxTangibleManager* tm, int64 id)
{
	Manager = tm;
	TangibleId = id;
}

void UlxTangible::DeleteCurrentActor()
{
	if (CurrentActor == nullptr) return;

	// Remove the tangible component. This is probably
	// unnecessary, but it makes it more likely that we'll
	// catch bugs early.
	UlxTangibleComponent* comp = CurrentActor->GetComponentByClass<UlxTangibleComponent>();
	if (comp != nullptr) {
		comp->DestroyComponent();
	}

	// Now destroy the actor itself.  According to various
	// documents I've read online, it may be necessary to take
	// further steps to delete the object.  Not clear.
	UE_LOG(LogLuprexIntegration, Display, TEXT("Deleting actor: %s"), *CurrentActor->GetName());
	CurrentActor->Rename(nullptr);
	CurrentActor->Destroy(); 

	// If this actor previously was possessed by a player controller,
	// then it's not possessed anymore, because there is no actor any more.
	if (Manager->PossessedTangible == this) {
		Manager->PossessedTangible = nullptr;
	};

	CurrentActor = nullptr;
}

void UlxTangible::SetActorBlueprint(const FString &XName) {
	FString Name = XName.ToLower();

	// If we're already of the right class, do nothing.
	if (ActorBlueprintName == Name) {
		return;
	}

	TSubclassOf<AActor> Blueprint;
	UlxAssetLookup::LoadTangibleBlueprintAsset(Blueprint, this, Name);
	if (Blueprint == nullptr)
	{
		UlxAssetLookup::LoadTangibleBlueprintAsset(Blueprint, this, DEFAULT_BLUEPRINT);
		check(Blueprint != nullptr);
	}

	// Update the blueprint name
	ActorBlueprintName = Name;

	// If there's already an actor, delete it.
	DeleteCurrentActor();

	// Now create a new actor.
	UWorld* w = Manager->GetWorld();
	FActorSpawnParameters params;

	// Give the new actor a reasonable name.
	params.Name = FName(*FString::Printf(TEXT("Tan:%s_%ld"), *Name, TangibleId));

	// Currently, the actor is spawned at (0,0,0), which is not good.
	// We should spawn at the actor's current location.  I'll get to it
	// eventually.
	FTransform transform;
	transform.SetLocation(FVector(0,0,0));
	transform.SetRotation(FQuat(FRotator(0,0,0)));

	// Create the actor at the specified location even if there's something in the way.
	params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	
	// Spawn the actor even if it collides with something.
	params.bNoFail = true;
	params.bDeferConstruction = false;

	// This pre-spawn initialization will finish setting up the actor before
	// the beginplay entry points run.  This is called by 'SpawnActor', below.
	//
	params.CustomPreSpawnInitalization = [&](AActor* A)
	{
		// Inject the tangible component into the actor.
		UActorComponent* ac = A->AddComponentByClass(UlxTangibleComponent::StaticClass(), false, FTransform::Identity, false);
		UlxTangibleComponent* tc = Cast<UlxTangibleComponent>(ac);
		check(tc != nullptr);
		tc->Tangible = this;

		// Make sure the actor label matches the actor name.
		A->SetActorLabel(params.Name.ToString());
	};

	UE_LOG(LogLuprexIntegration, Display, TEXT("Creating Actor: %s"), *params.Name.ToString());
	AActor* a = w->SpawnActor(Blueprint, &transform, params);
	check(a != nullptr);
	CurrentActor = a;
}

void UlxTangible::UpdateAnimationQueue(std::string_view aq) {
	AnimTracker.Update(aq);
}

void UlxTangible::MaybeExecuteAnimStateChanged() {
	int limit = 3;

	bool AnyChange = AnimTracker.IsChanged();

	while (AnimTracker.IsChanged())
	{
		if (limit == 0) break;
		limit -= 1;
		AnimTracker.ClearChanged();
		FString blueprint = AnimTracker.GetCurrentBlueprintName();
		if (blueprint.IsEmpty()) blueprint = DEFAULT_BLUEPRINT;
		SetActorBlueprint(blueprint);
		AActor *actor = GetActor();
		UFunction *aqchanged = actor->GetClass()->FindFunctionByName(FName(TEXT("Animation Queue Changed")));
		if (aqchanged != nullptr)
		{
			actor->ProcessEvent(aqchanged, nullptr);
		}
	}

	// Refresh the pending animation state.
	double Now = GetWorld()->GetTimeSeconds();
	if (AnyChange)
	{
		const FlxAnimationStep *Unfinished = AnimTracker.FirstUnfinished();
		if (Unfinished == nullptr)
		{
			PendingAnimationHash = 0;
			PendingAnimationTimeout = 0.0;
		}
		else if (Unfinished->Hash != PendingAnimationHash)
		{
			PendingAnimationHash = Unfinished->Hash;
			PendingAnimationTimeout = Now + 10.0;
		}
	}

	// Implement the animation timeout.
	if ((PendingAnimationHash != 0) && (Now > PendingAnimationTimeout))
	{
		const FlxAnimationStep *Step = AnimTracker.FindAnimation(PendingAnimationHash);
		if (Step != nullptr)
		{
			FString DS = UlxAnimationStepLibrary::AnimationStepDebugString(*Step);
			UE_LOG(LogLuprex, Warning, TEXT("Timeout - blueprint didn't finish animation: %s"), *DS); 
			AnimTracker.FinishedAnimation(PendingAnimationHash);
			AnyChange = true;
		}
		AutoUpdatePosition();
		PendingAnimationHash = 0;
		PendingAnimationTimeout = 0.0;
	}

	// The following code garbage collects any scripted animations that
	// were created by animation steps that are no longer in the animation
	// queue.  This is intended to handle the case that predictive reexecution
	// incorrectly predicts an animation step, and then the animation step
	// goes away during of difference transmission.
	if (AnyChange && (ScriptedAnimations != nullptr))
	{
		ScriptedAnimations->FadeGarbage(this, AnimTracker.GetHashes());
	}
}

FVector UlxTangible::GetLocation() const
{
	if (CurrentActor == nullptr) {
		return FVector(0,0,0);
	} else {
		return CurrentActor->GetActorLocation();
	}
}

void UlxTangible::Destroy()
{
	DeleteCurrentActor();
	Manager = nullptr;
	TangibleId = -1;
	CurrentActor = nullptr;
	ActorBlueprintName = "";
	AnimTracker.Clear();
	NearAccordingToLuprex = false;
	NearAccordingToUnreal = false;
}

UlxTangible *UlxTangible::GetActorTangibleQuiet(AActor *actor)
{
	if (actor == nullptr) return nullptr;
	UlxTangibleComponent* comp = actor->GetComponentByClass<UlxTangibleComponent>();
	if (comp == nullptr) return nullptr;
	return comp->Tangible.Get();
}

UlxTangible *UlxTangible::GetActorTangibleOrLog(AActor *actor)
{
	UlxTangible *tan = GetActorTangibleQuiet(actor);
	if (tan == nullptr) {
		if (actor == nullptr) {
			UE_LOG(LogBlueprint, Error, TEXT("Not a luprex tangible: passed in a null pointer"));
		} else {
			UE_LOG(LogBlueprint, Error, TEXT("Not a luprex tangible: %s"), *actor->GetName());
		}
	}
	return tan;
}

void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) {
	UlxTangible *tan = GetActorTangibleOrLog(target);
	if (tan == nullptr) {		
		step = FlxAnimationStep();
		return;
	}
	step = tan->AnimTracker.GetCurrentAnimation();
}

void UlxTangible::AutoUpdatePosition()
{
	const FlxAnimationStep *Step = AnimTracker.LastFinished();
	if (Step != nullptr)
	{
		Step->AutoUpdateXYZ(GetActor());
		Step->AutoUpdateFacing(GetActor());
		Step->AutoUpdatePlane(&(this->Plane));
	}
}

void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool AutoUpdate)
{
	UlxTangible *tan = GetActorTangibleOrLog(target);
	if (tan == nullptr) return;
	tan->AnimTracker.FinishedAnimation(step.Hash);
	if (AutoUpdate) tan->AutoUpdatePosition();
	FString DebugString = UlxAnimationStepLibrary::AnimationStepDebugString(step);
	// UE_LOG(LogLuprex, Display, TEXT("FinishedAnimation: %s"), *DebugString);
}

bool UlxTangible::AnimationStepIsFinished(AActor *target, const FlxAnimationStep &step)
{
	UlxTangible *tan = GetActorTangibleOrLog(target);
	if (tan == nullptr) return true;
	return tan->AnimTracker.IsFinished(step.Hash);
}

UlxScriptedAnimations *UlxTangible::GetScriptedAnimations(AActor *Target)
{
	UlxTangible *tan = GetActorTangibleQuiet(Target);
	if (tan == nullptr) return nullptr;
	if (tan->ScriptedAnimations == nullptr)
	{
		tan->ScriptedAnimations = NewObject<UlxScriptedAnimations>();
	}
	return tan->ScriptedAnimations;
}


FString UlxTangible::GetTangiblePlane(AActor* target) {
	UlxTangible *tan = GetActorTangibleOrLog(target);
	if (tan == nullptr) return TEXT("");
	return tan->Plane.ToString();
}

void UlxTangible::SetTangiblePlane(AActor* target, const FString& plane) {
	UlxTangible *tan = GetActorTangibleOrLog(target);
	if (tan == nullptr) return;
	tan->Plane = FName(plane);
}

bool UlxTangible::IsCurrentPlayer(AActor* target) {
	UlxTangible *tan = GetActorTangibleQuiet(target);
	if (tan == nullptr) return false;
	ALuprexGameModeBase *gamemode = ALuprexGameModeBase::FromContext(target);
	return (tan->TangibleId == gamemode->PlayerId);
}

bool UlxTangible::IsLuprexTangible(AActor* target) {
	UlxTangible *tan = GetActorTangibleQuiet(target);
	return tan != nullptr;
}

