// Copyright (c) Meta Platforms, Inc. and affiliates.

#include "OculusXRAnchorLatentActions.h"
#include "OculusXRAnchorsPrivate.h"
#include "OculusXRHMD.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRRoomLayoutManager.h"
#include "OculusXRAnchorDelegates.h"

//
// Create Spatial Anchor
//
void UOculusXRAsyncAction_CreateSpatialAnchor::Activate()
{
	if (!IsValid(TargetActor))
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to CreateSpatialAnchor latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::CreateSpatialAnchor(
		AnchorTransform,
		TargetActor,
		FOculusXRSpatialAnchorCreateDelegate::CreateUObject(this, &UOculusXRAsyncAction_CreateSpatialAnchor::HandleCreateComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for CreateSpatialAnchor latent action."));
		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_CreateSpatialAnchor* UOculusXRAsyncAction_CreateSpatialAnchor::OculusXRAsyncCreateSpatialAnchor(AActor* TargetActor, const FTransform& AnchorTransform)
{
	UOculusXRAsyncAction_CreateSpatialAnchor* Action = NewObject<UOculusXRAsyncAction_CreateSpatialAnchor>();
	Action->TargetActor = TargetActor;
	Action->AnchorTransform = AnchorTransform;

	if (IsValid(TargetActor))
	{
		Action->RegisterWithGameInstance(TargetActor->GetWorld());
	}
	else
	{
		Action->RegisterWithGameInstance(GWorld);
	}

	return Action;
}

void UOculusXRAsyncAction_CreateSpatialAnchor::HandleCreateComplete(EOculusXRAnchorResult::Type CreateResult, UOculusXRAnchorComponent* Anchor)
{
	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(CreateResult))
	{
		Success.Broadcast(Anchor, CreateResult);
	}
	else
	{
		Failure.Broadcast(CreateResult);
	}

	SetReadyToDestroy();
}

//
// Erase Space
//
void UOculusXRAsyncAction_EraseAnchor::Activate()
{
	if (!IsValid(TargetActor))
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to EraseSpace latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>();
	if (AnchorComponent == nullptr)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("No anchor on actor in EraseSpace latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::EraseAnchor(
		AnchorComponent,
		FOculusXRAnchorEraseDelegate::CreateUObject(this, &UOculusXRAsyncAction_EraseAnchor::HandleEraseAnchorComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for EraseSpace latent action."));

		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_EraseAnchor* UOculusXRAsyncAction_EraseAnchor::OculusXRAsyncEraseAnchor(AActor* TargetActor)
{
	UOculusXRAsyncAction_EraseAnchor* Action = NewObject<UOculusXRAsyncAction_EraseAnchor>();
	Action->TargetActor = TargetActor;

	if (IsValid(TargetActor))
	{
		Action->RegisterWithGameInstance(TargetActor->GetWorld());
	}
	else
	{
		Action->RegisterWithGameInstance(GWorld);
	}

	return Action;
}

void UOculusXRAsyncAction_EraseAnchor::HandleEraseAnchorComplete(EOculusXRAnchorResult::Type EraseResult, FOculusXRUUID UUID)
{
	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(EraseResult))
	{
		Success.Broadcast(TargetActor, UUID, EraseResult);
	}
	else
	{
		Failure.Broadcast(EraseResult);
	}

	SetReadyToDestroy();
}

//
// Save Space
//
void UOculusXRAsyncAction_SaveAnchor::Activate()
{
	if (!IsValid(TargetActor))
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to SaveSpace latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>();
	if (AnchorComponent == nullptr)
	{
		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	UE_LOG(LogOculusXRAnchors, Log, TEXT("Attempting to save anchor: %s to location %s"), IsValid(AnchorComponent) ? *AnchorComponent->GetName() : TEXT("INVALID ANCHOR"), *UEnum::GetValueAsString(StorageLocation));

	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SaveAnchor(
		AnchorComponent,
		StorageLocation,
		FOculusXRAnchorSaveDelegate::CreateUObject(this, &UOculusXRAsyncAction_SaveAnchor::HandleSaveAnchorComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SaveSpace latent action."));
		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_SaveAnchor* UOculusXRAsyncAction_SaveAnchor::OculusXRAsyncSaveAnchor(AActor* TargetActor, EOculusXRSpaceStorageLocation StorageLocation)
{
	UOculusXRAsyncAction_SaveAnchor* Action = NewObject<UOculusXRAsyncAction_SaveAnchor>();
	Action->TargetActor = TargetActor;
	Action->StorageLocation = StorageLocation;

	if (IsValid(TargetActor))
	{
		Action->RegisterWithGameInstance(TargetActor->GetWorld());
	}
	else
	{
		Action->RegisterWithGameInstance(GWorld);
	}

	return Action;
}

void UOculusXRAsyncAction_SaveAnchor::HandleSaveAnchorComplete(EOculusXRAnchorResult::Type SaveResult, UOculusXRAnchorComponent* Anchor)
{
	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SaveResult))
	{
		Success.Broadcast(Anchor, SaveResult);
	}
	else
	{
		Failure.Broadcast(SaveResult);
	}

	SetReadyToDestroy();
}

//
// Save Anchor List
//
void UOculusXRAsyncAction_SaveAnchorList::Activate()
{
	if (TargetAnchors.Num() == 0)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Actor array passed to SaveSpaces latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SaveAnchorList(
		TargetAnchors,
		StorageLocation,
		FOculusXRAnchorSaveListDelegate::CreateUObject(this, &UOculusXRAsyncAction_SaveAnchorList::HandleSaveAnchorListComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SaveSpaceList latent action."));

		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_SaveAnchorList* UOculusXRAsyncAction_SaveAnchorList::OculusXRAsyncSaveAnchorList(const TArray<AActor*>& TargetActors, EOculusXRSpaceStorageLocation StorageLocation)
{
	UOculusXRAsyncAction_SaveAnchorList* Action = NewObject<UOculusXRAsyncAction_SaveAnchorList>();

	auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });

	for (auto& it : TargetActors)
	{
		if (!IsValid(it))
		{
			continue;
		}

		UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass<UOculusXRAnchorComponent>();
		Action->TargetAnchors.Add(AnchorComponent);
	}

	Action->StorageLocation = StorageLocation;

	if (ValidActorPtr != nullptr)
	{
		Action->RegisterWithGameInstance(*ValidActorPtr);
	}

	return Action;
}

void UOculusXRAsyncAction_SaveAnchorList::HandleSaveAnchorListComplete(EOculusXRAnchorResult::Type SaveResult, const TArray<UOculusXRAnchorComponent*>& SavedSpaces)
{
	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SaveResult))
	{
		Success.Broadcast(SavedSpaces, SaveResult);
	}
	else
	{
		Failure.Broadcast(SaveResult);
	}

	SetReadyToDestroy();
}

//
// Query Spaces
//
void UOculusXRAsyncAction_QueryAnchors::Activate()
{
	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced(
		QueryInfo,
		FOculusXRAnchorQueryDelegate::CreateUObject(this, &UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for QuerySpaces latent action."));

		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchors(EOculusXRSpaceStorageLocation Location, const TArray<FOculusXRUUID>& UUIDs)
{
	FOculusXRSpaceQueryInfo QueryInfo;
	QueryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByIds;
	QueryInfo.IDFilter = UUIDs;
	QueryInfo.Location = Location;
	QueryInfo.MaxQuerySpaces = UUIDs.Num();

	UOculusXRAsyncAction_QueryAnchors* Action = NewObject<UOculusXRAsyncAction_QueryAnchors>();
	Action->QueryInfo = QueryInfo;

	return Action;
}

UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo)
{
	UOculusXRAsyncAction_QueryAnchors* Action = NewObject<UOculusXRAsyncAction_QueryAnchors>();
	Action->QueryInfo = QueryInfo;

	return Action;
}

void UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults(EOculusXRAnchorResult::Type QueryResult, const TArray<FOculusXRSpaceQueryResult>& Results)
{
	QueryResults = Results;

	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(QueryResult))
	{
		Success.Broadcast(QueryResults, QueryResult);
	}
	else
	{
		Failure.Broadcast(QueryResult);
	}

	SetReadyToDestroy();
}

//
// Set Component Status with Anchor Actor
//
void UOculusXRAsyncAction_SetAnchorComponentStatus::Activate()
{
	if (!IsValid(TargetActor))
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to SetComponentStatus latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	TargetAnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>();
	if (TargetAnchorComponent == nullptr)
	{
		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SetAnchorComponentStatus(
		TargetAnchorComponent,
		ComponentType,
		bEnabled,
		0,
		FOculusXRAnchorSetComponentStatusDelegate::CreateUObject(this, &UOculusXRAsyncAction_SetAnchorComponentStatus::HandleSetComponentStatusComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SetComponentStatus latent action."));

		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_SetAnchorComponentStatus* UOculusXRAsyncAction_SetAnchorComponentStatus::OculusXRAsyncSetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool bEnabled)
{
	UOculusXRAsyncAction_SetAnchorComponentStatus* Action = NewObject<UOculusXRAsyncAction_SetAnchorComponentStatus>();
	Action->TargetActor = TargetActor;
	Action->ComponentType = ComponentType;
	Action->bEnabled = bEnabled;

	if (IsValid(TargetActor))
	{
		Action->RegisterWithGameInstance(TargetActor->GetWorld());
	}
	else
	{
		Action->RegisterWithGameInstance(GWorld);
	}

	return Action;
}

void UOculusXRAsyncAction_SetAnchorComponentStatus::HandleSetComponentStatusComplete(EOculusXRAnchorResult::Type SetStatusResult, uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled)
{
	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SetStatusResult))
	{
		Success.Broadcast(TargetAnchorComponent, SpaceComponentType, bResultEnabled, SetStatusResult);
	}
	else
	{
		Failure.Broadcast(SetStatusResult);
	}

	SetReadyToDestroy();
}

//
// Set Component Status
//
void UOculusXRAsyncAction_SetComponentStatus::Activate()
{
	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SetComponentStatus(
		Component->GetSpace(),
		Component->GetType(),
		bEnabled,
		0,
		FOculusXRAnchorSetComponentStatusDelegate::CreateUObject(this, &UOculusXRAsyncAction_SetComponentStatus::HandleSetComponentStatusComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SetComponentStatus latent action."));

		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_SetComponentStatus* UOculusXRAsyncAction_SetComponentStatus::OculusXRAsyncSetComponentStatus(UOculusXRBaseAnchorComponent* Component, bool bEnabled)
{
	UOculusXRAsyncAction_SetComponentStatus* Action = NewObject<UOculusXRAsyncAction_SetComponentStatus>();
	Action->Component = Component;
	Action->bEnabled = bEnabled;

	Action->RegisterWithGameInstance(GWorld);

	return Action;
}

void UOculusXRAsyncAction_SetComponentStatus::HandleSetComponentStatusComplete(EOculusXRAnchorResult::Type SetStatusResult, uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled)
{
	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SetStatusResult))
	{
		Success.Broadcast(Component, SetStatusResult);
	}
	else
	{
		Failure.Broadcast(SetStatusResult);
	}

	SetReadyToDestroy();
}

//
// Share Spaces
//
void UOculusXRAsyncAction_ShareAnchors::Activate()
{
	if (TargetAnchors.Num() == 0)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Actors array passed to ShareSpaces latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	if (ToShareWithIds.Num() == 0)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Player IDs array passed to ShareSpaces latent action."));

		Failure.Broadcast(EOculusXRAnchorResult::Failure);
		return;
	}

	EOculusXRAnchorResult::Type Result;
	bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::ShareAnchors(
		TargetAnchors,
		ToShareWithIds,
		FOculusXRAnchorShareDelegate::CreateUObject(this, &UOculusXRAsyncAction_ShareAnchors::HandleShareAnchorsComplete),
		Result);

	if (!bStartedAsync)
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for ShareSpaces latent action."));

		Failure.Broadcast(Result);
	}
}

UOculusXRAsyncAction_ShareAnchors* UOculusXRAsyncAction_ShareAnchors::OculusXRAsyncShareAnchors(const TArray<AActor*>& TargetActors, const TArray<FString>& ToShareWithIds)
{
	UOculusXRAsyncAction_ShareAnchors* Action = NewObject<UOculusXRAsyncAction_ShareAnchors>();

	for (const auto& UserIDString : ToShareWithIds)
	{
		uint64 UserId = FCString::Strtoui64(*UserIDString, nullptr, 10);
		if (UserId == 0)
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("UserID provided to share anchors was invalid or unconvertable: %s"), *UserIDString);
		}

		Action->ToShareWithIds.Add(UserId);
	}

	for (auto& it : TargetActors)
	{
		if (!IsValid(it))
		{
			continue;
		}

		UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass<UOculusXRAnchorComponent>();
		Action->TargetAnchors.Add(AnchorComponent);
	}

	auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });
	if (ValidActorPtr != nullptr)
	{
		Action->RegisterWithGameInstance(*ValidActorPtr);
	}
	else
	{
		Action->RegisterWithGameInstance(GWorld);
	}

	return Action;
}

void UOculusXRAsyncAction_ShareAnchors::HandleShareAnchorsComplete(EOculusXRAnchorResult::Type ShareResult, const TArray<UOculusXRAnchorComponent*>& SharedAnchors, const TArray<uint64>& OculusUserIDs)
{
	TArray<FString> OculusUserIDStrings;
	for (const auto& it : OculusUserIDs)
	{
		OculusUserIDStrings.Add(FString::Printf(TEXT("%llu"), it));
	}

	if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(ShareResult))
	{
		Success.Broadcast(SharedAnchors, OculusUserIDStrings, ShareResult);
	}
	else
	{
		Failure.Broadcast(ShareResult);
	}

	// Unbind and mark for destruction
	SetReadyToDestroy();
}


UOculusXRAnchorLaunchCaptureFlow* UOculusXRAnchorLaunchCaptureFlow::LaunchCaptureFlowAsync(const UObject* WorldContext)
{
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
	if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
	{
		return nullptr;
	}

	// Create a new UMyDelayAsyncAction, and store function arguments in it.
	auto NewAction = NewObject<UOculusXRAnchorLaunchCaptureFlow>();
	NewAction->RegisterWithGameInstance(World->GetGameInstance());
	return NewAction;
}

void UOculusXRAnchorLaunchCaptureFlow::Activate()
{
	Request = 0;
	FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.AddUObject(this, &UOculusXRAnchorLaunchCaptureFlow::OnCaptureFinish);
	bool CaptureStarted = OculusXRAnchors::FOculusXRRoomLayoutManager::RequestSceneCapture(Request);
	if (!CaptureStarted)
	{
		FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
		Failure.Broadcast();
	}
}

void UOculusXRAnchorLaunchCaptureFlow::OnCaptureFinish(FOculusXRUInt64 RequestId, bool bSuccess)
{
	if (Request != RequestId.GetValue())
	{
		UE_LOG(LogOculusXRAnchors, Warning, TEXT("%llu request id doesn't match %llu. Ignoring request."), RequestId, Request);
		return;
	}

	FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
	Success.Broadcast();
	SetReadyToDestroy();
}
