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

#include "OculusXRAnchorManager.h"

#include <vector>

#include "OculusXRHMD.h"
#include "OculusXRAnchorsModule.h"
#include "OculusXRAnchorDelegates.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRAnchorTypesPrivate.h"

namespace OculusXRAnchors
{
	OculusXRHMD::FOculusXRHMD* GetHMD(bool& OutSuccessful)
	{
		OculusXRHMD::FOculusXRHMD* OutHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
		if (!OutHMD)
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD"));
			OutSuccessful = false;
		}

		OutSuccessful = true;

		return OutHMD;
	}

	ovrpUuid ConvertFOculusXRUUIDtoOvrpUuid(const FOculusXRUUID& UUID)
	{
		ovrpUuid Result;
		FMemory::Memcpy(Result.data, UUID.UUIDBytes);

		return Result;
	}

	ovrpSpaceQueryInfo ConvertToOVRPSpaceQueryInfo(const FOculusXRSpaceQueryInfo& UEQueryInfo)
	{
		static const int32 MaxIdsInFilter = 1024;
		static const int32 MaxComponentTypesInFilter = 16;

		ovrpSpaceQueryInfo Result;

		Result.queryType = ovrpSpaceQueryType_Action;
		Result.actionType = ovrpSpaceQueryActionType_Load;

		Result.maxQuerySpaces = UEQueryInfo.MaxQuerySpaces;
		Result.timeout = static_cast<double>(UEQueryInfo.Timeout); // Prevent compiler warnings, though there is a possible loss of data here.

		switch (UEQueryInfo.Location)
		{
			case EOculusXRSpaceStorageLocation::Invalid:
				Result.location = ovrpSpaceStorageLocation_Invalid;
				break;
			case EOculusXRSpaceStorageLocation::Local:
				Result.location = ovrpSpaceStorageLocation_Local;
				break;
			case EOculusXRSpaceStorageLocation::Cloud:
				Result.location = ovrpSpaceStorageLocation_Cloud;
				break;
		}

		switch (UEQueryInfo.FilterType)
		{
			case EOculusXRSpaceQueryFilterType::None:
				Result.filterType = ovrpSpaceQueryFilterType_None;
				break;
			case EOculusXRSpaceQueryFilterType::FilterByIds:
				Result.filterType = ovrpSpaceQueryFilterType_Ids;
				break;
			case EOculusXRSpaceQueryFilterType::FilterByComponentType:
				Result.filterType = ovrpSpaceQueryFilterType_Components;
				break;
		}


		Result.IdInfo.numIds = FMath::Min(MaxIdsInFilter, UEQueryInfo.IDFilter.Num());
		for (int i = 0; i < Result.IdInfo.numIds; ++i)
		{
			ovrpUuid OvrUuid = ConvertFOculusXRUUIDtoOvrpUuid(UEQueryInfo.IDFilter[i]);
			Result.IdInfo.ids[i] = OvrUuid;
		}

		Result.componentsInfo.numComponents = FMath::Min(MaxComponentTypesInFilter, UEQueryInfo.ComponentFilter.Num());
		for (int i = 0; i < Result.componentsInfo.numComponents; ++i)
		{
			Result.componentsInfo.components[i] = ConvertToOvrpComponentType(UEQueryInfo.ComponentFilter[i]);
		}

		return Result;
	}

	template <typename T>
	void GetEventData(ovrpEventDataBuffer& Buffer, T& OutEventData)
	{
		unsigned char* BufData = Buffer.EventData;
		BufData -= sizeof(uint64); //correct offset

		memcpy(&OutEventData, BufData, sizeof(T));
	}

	void FOculusXRAnchorManager::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
	{
		ovrpEventDataBuffer& buf = *EventDataBuffer;
		EventPollResult = true;

		switch (buf.EventType)
		{
			case ovrpEventType_SpatialAnchorCreateComplete:
			{
				ovrpEventDataSpatialAnchorCreateComplete AnchorCreateEvent;
				GetEventData(buf, AnchorCreateEvent);

				const FOculusXRUInt64 RequestId(AnchorCreateEvent.requestId);
				const FOculusXRUInt64 Space(AnchorCreateEvent.space);
				const FOculusXRUUID BPUUID(AnchorCreateEvent.uuid.data);

				FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.Broadcast(RequestId, AnchorCreateEvent.result, Space, BPUUID);

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpatialAnchorCreateComplete Request ID: %llu  --  Space: %llu  --  UUID: %s  --  Result: %d"),
					RequestId.GetValue(),
					Space.GetValue(),
					*BPUUID.ToString(),
					AnchorCreateEvent.result);

				break;
			}
			case ovrpEventType_SpaceSetComponentStatusComplete:
			{
				ovrpEventDataSpaceSetStatusComplete SetStatusEvent;
				GetEventData(buf, SetStatusEvent);

				//translate to BP types
				const FOculusXRUInt64 RequestId(SetStatusEvent.requestId);
				const FOculusXRUInt64 Space(SetStatusEvent.space);
				EOculusXRSpaceComponentType BPSpaceComponentType = ConvertToUEComponentType(SetStatusEvent.componentType);
				const FOculusXRUUID BPUUID(SetStatusEvent.uuid.data);
				const bool bEnabled = (SetStatusEvent.enabled == ovrpBool_True);

				FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.Broadcast(
					RequestId,
					SetStatusEvent.result,
					Space,
					BPUUID,
					BPSpaceComponentType,
					bEnabled);

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceSetComponentStatusComplete Request ID: %llu  --  Type: %d  --  Enabled: %d  --  Space: %llu  --  Result: %d"),
					SetStatusEvent.requestId,
					SetStatusEvent.componentType,
					SetStatusEvent.enabled,
					SetStatusEvent.space,
					SetStatusEvent.result);

				break;
			}
			case ovrpEventType_SpaceQueryResults:
			{
				ovrpEventSpaceQueryResults QueryEvent;
				GetEventData(buf, QueryEvent);

				const FOculusXRUInt64 RequestId(QueryEvent.requestId);

				FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.Broadcast(RequestId);

				ovrpUInt32 ovrpOutCapacity = 0;

				// First get capacity
				const bool bGetCapacityResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults(&QueryEvent.requestId, 0, &ovrpOutCapacity, nullptr));

				UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceQueryResults Request ID: %llu  --  Capacity: %d  --  Result: %d"), QueryEvent.requestId, ovrpOutCapacity, bGetCapacityResult);

				std::vector<ovrpSpaceQueryResult> ovrpResults(ovrpOutCapacity);

				// Get Query Data
				const bool bGetQueryDataResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults(&QueryEvent.requestId, ovrpResults.size(), &ovrpOutCapacity, ovrpResults.data()));

				for (auto queryResultElement : ovrpResults)
				{
					UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryResult Space: %llu  --  Result: %d"), queryResultElement.space, bGetQueryDataResult);

					//translate types
					FOculusXRUInt64 Space(queryResultElement.space);
					FOculusXRUUID BPUUID(queryResultElement.uuid.data);
					FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Broadcast(RequestId, Space, BPUUID);
				}

				break;
			}
			case ovrpEventType_SpaceQueryComplete:
			{
				ovrpEventSpaceQueryComplete QueryCompleteEvent;
				GetEventData(buf, QueryCompleteEvent);

				//translate to BP types
				const FOculusXRUInt64 RequestId(QueryCompleteEvent.requestId);
				const bool bSucceeded = QueryCompleteEvent.result >= 0;

				FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Broadcast(RequestId, QueryCompleteEvent.result);

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryComplete Request ID: %llu  --  Result: %d"), QueryCompleteEvent.requestId, QueryCompleteEvent.result);

				break;
			}
			case ovrpEventType_SpaceSaveComplete:
			{
				ovrpEventSpaceStorageSaveResult StorageResult;
				GetEventData(buf, StorageResult);

				//translate to BP types
				const FOculusXRUUID uuid(StorageResult.uuid.data);
				const FOculusXRUInt64 FSpace(StorageResult.space);
				const FOculusXRUInt64 FRequest(StorageResult.requestId);
				const bool bResult = StorageResult.result >= 0;

				FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.Broadcast(FRequest, FSpace, bResult, StorageResult.result, uuid);

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceSaveComplete  Request ID: %llu  --  Space: %llu  --  Result: %d"), StorageResult.requestId, StorageResult.space, StorageResult.result);

				break;
			}
			case ovrpEventType_SpaceListSaveResult:
			{
				ovrpEventSpaceListSaveResult SpaceListSaveResult;
				GetEventData(buf, SpaceListSaveResult);

				FOculusXRUInt64 RequestId(SpaceListSaveResult.requestId);

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceListSaveResult  Request ID: %llu  --  Result: %d"), SpaceListSaveResult.requestId, SpaceListSaveResult.result);
				FOculusXRAnchorEventDelegates::OculusSpaceListSaveComplete.Broadcast(RequestId, SpaceListSaveResult.result);

				break;
			}
			case ovrpEventType_SpaceEraseComplete:
			{
				ovrpEventSpaceStorageEraseResult SpaceEraseEvent;
				GetEventData(buf, SpaceEraseEvent);

				//translate to BP types
				const FOculusXRUUID uuid(SpaceEraseEvent.uuid.data);
				const FOculusXRUInt64 FRequestId(SpaceEraseEvent.requestId);
				const FOculusXRUInt64 FResult(SpaceEraseEvent.result);
				const EOculusXRSpaceStorageLocation BPLocation = (SpaceEraseEvent.location == ovrpSpaceStorageLocation_Local) ? EOculusXRSpaceStorageLocation::Local : EOculusXRSpaceStorageLocation::Invalid;

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceEraseComplete  Request ID: %llu  --  Result: %d  -- UUID: %s"), SpaceEraseEvent.requestId, SpaceEraseEvent.result, *UOculusXRAnchorBPFunctionLibrary::AnchorUUIDToString(SpaceEraseEvent.uuid.data));

				FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.Broadcast(FRequestId, FResult.Value, uuid, BPLocation);
				break;
			}
			case ovrpEventType_SpaceShareResult:
			{
				unsigned char* BufData = buf.EventData;
				ovrpUInt64 OvrpRequestId = 0;
				memcpy(&OvrpRequestId, BufData, sizeof(OvrpRequestId));

				ovrpEventSpaceShareResult SpaceShareSpaceResult;
				GetEventData(buf, SpaceShareSpaceResult);

				FOculusXRUInt64 RequestId(SpaceShareSpaceResult.requestId);

				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceShareSpaceResult  Request ID: %llu  --  Result: %d"),
					SpaceShareSpaceResult.requestId,
					SpaceShareSpaceResult.result);

				FOculusXRAnchorEventDelegates::OculusSpaceShareComplete.Broadcast(RequestId, SpaceShareSpaceResult.result);

				break;
			}
			case ovrpEventType_None:
			default:
			{
				EventPollResult = false;
				break;
			}
		}
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform)
	{
		bool bValidHMD;
		OculusXRHMD::FOculusXRHMD* HMD = GetHMD(bValidHMD);
		if (!bValidHMD)
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusAnchorManager::CreateAnchor failed to retrieve HMD."));
			return EOculusXRAnchorResult::Failure;
		}

		ovrpTrackingOrigin TrackingOriginType;
		ovrpPosef Posef;
		double Time = 0;

		const FTransform TrackingToWorld = HMD->GetLastTrackingToWorld();

		// convert to tracking space
		const FQuat TrackingSpaceOrientation = TrackingToWorld.Inverse().TransformRotation(InTransform.Rotator().Quaternion());
		const FVector TrackingSpacePosition = TrackingToWorld.Inverse().TransformPosition(InTransform.GetLocation());

		const OculusXRHMD::FPose TrackingSpacePose(TrackingSpaceOrientation, TrackingSpacePosition);

#if WITH_EDITOR
		// Link only head space position update
		FVector OutHeadPosition;
		FQuat OutHeadOrientation;
		const bool bGetPose = HMD->GetCurrentPose(HMD->HMDDeviceId, OutHeadOrientation, OutHeadPosition);
		if (!bGetPose)
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusAnchorManager::CreateAnchor failed to get current headset pose."));
			return EOculusXRAnchorResult::Failure;
		}

		OculusXRHMD::FPose HeadPose(OutHeadOrientation, OutHeadPosition);

		OculusXRHMD::FPose MainCameraPose(CameraTransform.GetRotation(), CameraTransform.GetLocation());
		OculusXRHMD::FPose PoseInHeadSpace = MainCameraPose.Inverse() * TrackingSpacePose;

		// To world space pose
		OculusXRHMD::FPose WorldPose = HeadPose * PoseInHeadSpace;

		const bool bConverted = HMD->ConvertPose(WorldPose, Posef);
#else
		const bool bConverted = HMD->ConvertPose(TrackingSpacePose, Posef);
#endif

		if (!bConverted)
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusAnchorManager::CreateAnchor failed to convert pose."));
			return EOculusXRAnchorResult::Failure;
		}

		FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&TrackingOriginType);
		FOculusXRHMDModule::GetPluginWrapper().GetTimeInSeconds(&Time);

		const ovrpSpatialAnchorCreateInfo SpatialAnchorCreateInfo = {
			TrackingOriginType,
			Posef,
			Time
		};

		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().CreateSpatialAnchor(&SpatialAnchorCreateInfo, &OutRequestId);
		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("CreateAnchor Request ID: %llu"), OutRequestId);

		if (OVRP_FAILURE(Result))
		{
			UE_LOG(LogOculusXRAnchors, Error, TEXT("FOculusAnchorManager::CreateAnchor failed. Result: %d"), Result);
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::DestroySpace(uint64 Space)
	{
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DestroySpace(static_cast<ovrpSpace*>(&Space));

		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("DestroySpace  Space ID: %llu"), Space);

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::SetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, uint64& OutRequestId)
	{
		ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(SpaceComponentType);
		ovrpUInt64 OvrpOutRequestId = 0;

		const ovrpUInt64 OVRPSpace = Space;

		// validate existing status
		ovrpBool isEnabled = false;
		ovrpBool changePending = false;
		const ovrpResult getComponentStatusResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus(&OVRPSpace, ovrpType, &isEnabled, &changePending);

		bool isStatusChangingOrSame = (static_cast<bool>(isEnabled) == Enable && !changePending) || (static_cast<bool>(isEnabled) != Enable && changePending);
		if (OVRP_SUCCESS(getComponentStatusResult) && isStatusChangingOrSame)
		{
			return EOculusXRAnchorResult::Success;
		}

		// set status
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SetSpaceComponentStatus(
			&OVRPSpace,
			ovrpType,
			Enable,
			Timeout,
			&OvrpOutRequestId);

		memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));

		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("SetSpaceComponentStatus  Request ID: %llu"), OutRequestId);

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending)
	{
		const ovrpUInt64 OVRPSpace = Space;
		ovrpBool OutOvrpEnabled = ovrpBool_False;
		ovrpBool OutOvrpChangePending = ovrpBool_False;

		ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(SpaceComponentType);

		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus(
			&OVRPSpace,
			ovrpType,
			&OutOvrpEnabled,
			&OutOvrpChangePending);

		OutEnabled = (OutOvrpEnabled == ovrpBool_True);
		OutChangePending = (OutOvrpChangePending == ovrpBool_True);

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSupportedAnchorComponents(uint64 Handle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes)
	{
		if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
		{
			return EOculusXRAnchorResult::Failure;
		}

		ovrpSpace ovrSpace = Handle;
		TArray<ovrpSpaceComponentType> ovrComponentTypes;
		ovrpUInt32 input = 0;
		ovrpUInt32 output = 0;

		ovrpResult enumerateResult = FOculusXRHMDModule::GetPluginWrapper().EnumerateSpaceSupportedComponents(&ovrSpace, input, &output, nullptr);
		if (!OVRP_SUCCESS(enumerateResult))
		{
			return static_cast<EOculusXRAnchorResult::Type>(enumerateResult);
		}

		input = output;
		ovrComponentTypes.SetNumZeroed(output);

		enumerateResult = FOculusXRHMDModule::GetPluginWrapper().EnumerateSpaceSupportedComponents(&ovrSpace, input, &output, ovrComponentTypes.GetData());
		if (!OVRP_SUCCESS(enumerateResult))
		{
			return static_cast<EOculusXRAnchorResult::Type>(enumerateResult);
		}

		OutSupportedTypes.SetNumZeroed(ovrComponentTypes.Num());
		for (int i = 0; i < ovrComponentTypes.Num(); ++i)
		{
			OutSupportedTypes[i] = ConvertToUEComponentType(ovrComponentTypes[i]);
		}

		return static_cast<EOculusXRAnchorResult::Type>(enumerateResult);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchor(uint64 Space,
		EOculusXRSpaceStorageLocation StorageLocation,
		EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId)
	{
		ovrpSpaceStorageLocation OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
		switch (StorageLocation)
		{
			case EOculusXRSpaceStorageLocation::Invalid:
				OvrpStorageLocation = ovrpSpaceStorageLocation_Invalid;
				break;
			case EOculusXRSpaceStorageLocation::Local:
				OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
				break;
			case EOculusXRSpaceStorageLocation::Cloud:
				OvrpStorageLocation = ovrpSpaceStorageLocation_Cloud;
				break;
			default:
				break;
		}

		ovrpSpaceStoragePersistenceMode OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Invalid;
		switch (StoragePersistenceMode)
		{
			case EOculusXRSpaceStoragePersistenceMode::Invalid:
				OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Invalid;
				break;
			case EOculusXRSpaceStoragePersistenceMode::Indefinite:
				OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Indefinite;
				break;
			default:
				break;
		}

		ovrpUInt64 OvrpOutRequestId = 0;
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpace(&Space, OvrpStorageLocation, OvrpStoragePersistenceMode, &OvrpOutRequestId);

		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Saving space with: SpaceID: %llu  --  Location: %d  --  Persistence: %d  --  OutID: %llu"), Space, OvrpStorageLocation, OvrpStoragePersistenceMode, OvrpOutRequestId);

		memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));

		if (OVRP_FAILURE(Result))
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRHMD::SaveAnchor failed with: SpaceID: %llu  --  Location: %d  --  Persistence: %d"), Space, OvrpStorageLocation, OvrpStoragePersistenceMode);
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchorList(const TArray<uint64>& Spaces, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
	{
		ovrpSpaceStorageLocation OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
		switch (StorageLocation)
		{
			case EOculusXRSpaceStorageLocation::Invalid:
				OvrpStorageLocation = ovrpSpaceStorageLocation_Invalid;
				break;
			case EOculusXRSpaceStorageLocation::Local:
				OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
				break;
			case EOculusXRSpaceStorageLocation::Cloud:
				OvrpStorageLocation = ovrpSpaceStorageLocation_Cloud;
				break;
			default:
				break;
		}

		ovrpUInt64 OvrpOutRequestId = 0;
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpaceList(Spaces.GetData(), Spaces.Num(), OvrpStorageLocation, &OvrpOutRequestId);

		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Saving space list: Location: %d --  OutID: %llu"), OvrpStorageLocation, OvrpOutRequestId);
		for (auto& it : Spaces)
		{
			UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tSpaceID: %llu"), it);
		}

		memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));

		if (OVRP_FAILURE(Result))
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("SaveSpaceList failed -- Result: %d"), Result);
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::EraseAnchor(uint64 AnchorHandle,
		EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
	{
		ovrpSpaceStorageLocation ovrpStorageLocation = ovrpSpaceStorageLocation_Local;
		switch (StorageLocation)
		{
			case EOculusXRSpaceStorageLocation::Invalid:
				ovrpStorageLocation = ovrpSpaceStorageLocation_Invalid;
				break;
			case EOculusXRSpaceStorageLocation::Local:
				ovrpStorageLocation = ovrpSpaceStorageLocation_Local;
				break;
			default:;
		}

		ovrpUInt64 OvrpOutRequestId = 0;
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().EraseSpace(&AnchorHandle, ovrpStorageLocation, &OvrpOutRequestId);
		memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));

		UE_LOG(LogOculusXRAnchors, Log, TEXT("Erasing anchor -- Handle: %llu  --  Location: %d  --  OutID: %llu"), AnchorHandle, ovrpStorageLocation, OvrpOutRequestId);

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::QuerySpaces(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId)
	{
		ovrpUInt64 OvrpOutRequestId = 0;
		ovrpResult QuerySpacesResult = ovrpFailure;
		ovrpSpaceQueryInfo ovrQueryInfo = ConvertToOVRPSpaceQueryInfo(QueryInfo);
		QuerySpacesResult = FOculusXRHMDModule::GetPluginWrapper().QuerySpaces(&ovrQueryInfo, &OvrpOutRequestId);

		memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));

		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query Spaces\n ovrpSpaceQueryInfo:\n\tQueryType: %d\n\tMaxQuerySpaces: %d\n\tTimeout: %f\n\tLocation: %d\n\tActionType: %d\n\tFilterType: %d\n\n\tRequest ID: %llu"),
			ovrQueryInfo.queryType, ovrQueryInfo.maxQuerySpaces, (float)ovrQueryInfo.timeout, ovrQueryInfo.location, ovrQueryInfo.actionType, ovrQueryInfo.filterType, OutRequestId);

		if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByIds)
		{
			UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query contains %d UUIDs"), QueryInfo.IDFilter.Num());
			for (auto& it : QueryInfo.IDFilter)
			{
				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("UUID: %s"), *it.ToString());
			}
		}
		else if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByComponentType)
		{
			UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query contains %d Component Types"), QueryInfo.ComponentFilter.Num());
			for (auto& it : QueryInfo.ComponentFilter)
			{
				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ComponentType: %s"), *UEnum::GetValueAsString(it));
			}
		}

		return static_cast<EOculusXRAnchorResult::Type>(QuerySpacesResult);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::ShareSpaces(const TArray<uint64>& Spaces, const TArray<uint64>& UserIds, uint64& OutRequestId)
	{
		TArray<const char*> stringStorage;
		TArray<ovrpUser> OvrpUsers;
		for (const auto& UserId : UserIds)
		{
			ovrpUser OvrUser;
			ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().CreateSpaceUser(&UserId, &OvrUser);
			if (OVRP_FAILURE(Result))
			{
				UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to create space user from ID  -  %llu"), UserId);
				continue;
			}

			OvrpUsers.Add(OvrUser);
		}

		ovrpUInt64 OvrpOutRequestId = 0;
		const ovrpResult ShareSpacesResult = FOculusXRHMDModule::GetPluginWrapper().ShareSpaces(Spaces.GetData(), Spaces.Num(), OvrpUsers.GetData(), OvrpUsers.Num(), &OvrpOutRequestId);

		UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Sharing space list  --  OutID: %llu"), OvrpOutRequestId);
		for (auto& User : OvrpUsers)
		{
			UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tOvrpUser: %llu"), User);
			ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DestroySpaceUser(&User);
			if (OVRP_FAILURE(Result))
			{
				UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Failed to destroy space user: %llu"), User);
				continue;
			}
		}

		for (auto& it : Spaces)
		{
			UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tSpaceID: %llu"), it);
		}

		memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));

		return static_cast<EOculusXRAnchorResult::Type>(ShareSpacesResult);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceContainerUUIDs(uint64 Space, TArray<FOculusXRUUID>& OutUUIDs)
	{
		TArray<ovrpUuid> ovrUuidArray;

		// Get the number of elements in the container
		ovrpSpaceContainer ovrSpaceContainer;
		ovrSpaceContainer.uuidCapacityInput = 0;
		ovrSpaceContainer.uuidCountOutput = 0;
		ovrSpaceContainer.uuids = nullptr;
		ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &ovrSpaceContainer);
		if (OVRP_FAILURE(result))
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space container %d"), result);
			return static_cast<EOculusXRAnchorResult::Type>(result);
		}

		// Retrieve the actual array of UUIDs
		ovrUuidArray.SetNum(ovrSpaceContainer.uuidCountOutput);
		ovrSpaceContainer.uuidCapacityInput = ovrSpaceContainer.uuidCountOutput;
		ovrSpaceContainer.uuids = ovrUuidArray.GetData();

		result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &ovrSpaceContainer);
		if (OVRP_FAILURE(result))
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space container %d"), result);
			return static_cast<EOculusXRAnchorResult::Type>(result);
		}

		// Write out the remaining UUIDs
		OutUUIDs.Reserve(ovrUuidArray.Num());
		for (auto& it : ovrUuidArray)
		{
			OutUUIDs.Add(FOculusXRUUID(it.data));
		}

		return EOculusXRAnchorResult::Success;
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize)
	{
		OutPos.X = OutPos.Y = OutPos.Z = 0.f;
		OutSize.X = OutSize.Y = OutSize.Z = 0.f;

		ovrpRectf rect;
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox2D(&Space, &rect);

		if (OVRP_SUCCESS(Result))
		{
			// Convert to UE4's coordinates system
			OutPos.Y = rect.Pos.x;
			OutPos.Z = rect.Pos.y;
			OutSize.Y = rect.Size.w;
			OutSize.Z = rect.Size.h;
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize)
	{
		OutPos.X = OutPos.Y = OutPos.Z = 0.f;
		OutSize.X = OutSize.Y = OutSize.Z = 0.f;

		ovrpBoundsf bounds;
		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox3D(&Space, &bounds);

		if (OVRP_SUCCESS(Result))
		{
			// Convert from OpenXR's right-handed to Unreal's left-handed coordinate system.
			//    OpenXR             Unreal
			//       | y            | z
			//       |              |
			//z <----+              +----> x
			//      /              /
			//    x/             y/
			//
			OutPos.X = -bounds.Pos.z;
			OutPos.Y = bounds.Pos.x;
			OutPos.Z = bounds.Pos.y;

			// The position represents the corner of the volume which has the lowest value
			// of each axis. Since we flipped the sign of one of the axes we need to adjust
			// the position to the other side of the volume
			OutPos.X -= bounds.Size.d;

			// We keep the size positive for all dimensions
			OutSize.X = bounds.Size.d;
			OutSize.Y = bounds.Size.w;
			OutSize.Z = bounds.Size.h;
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceSemanticClassification(uint64 Space, TArray<FString>& OutSemanticClassifications)
	{
		OutSemanticClassifications.Empty();

		const int32 maxByteSize = 1024;
		char labelsChars[maxByteSize];

		ovrpSemanticLabels labels;
		labels.byteCapacityInput = maxByteSize;
		labels.labels = labelsChars;

		const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceSemanticLabels(&Space, &labels);

		if (OVRP_SUCCESS(Result))
		{
			FString labelsStr(labels.byteCountOutput, labels.labels);
			labelsStr.ParseIntoArray(OutSemanticClassifications, TEXT(","));
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceContainer(uint64 Space, TArray<FOculusXRUUID>& OutContainerUuids)
	{
		OutContainerUuids.Empty();

		ovrpSpaceContainer container;

		ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &container);

		if (OVRP_SUCCESS(Result))
		{
			TArray<ovrpUuid> uuids;
			size_t size = container.uuidCountOutput;
			uuids.InsertZeroed(0, size);
			container.uuidCapacityInput = size;
			container.uuids = uuids.GetData();
			Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &container);
			if (OVRP_SUCCESS(Result))
			{
				OutContainerUuids.InsertZeroed(0, size);
				for (size_t i = 0; i < size; i++)
				{
					OutContainerUuids[i] = FOculusXRUUID(uuids[i].data);
				}
			}
		}

		return static_cast<EOculusXRAnchorResult::Type>(Result);
	}

	EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceBoundary2D(uint64 Space, TArray<FVector2f>& OutVertices)
	{
		TArray<ovrpVector2f> vertices;

		// Get the number of elements in the container
		ovrpBoundary2D boundary;
		boundary.vertexCapacityInput = 0;
		boundary.vertexCountOutput = 0;
		boundary.vertices = nullptr;

		ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundary2D(&Space, &boundary);
		if (OVRP_FAILURE(result))
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space boundary 2d %d"), result);
			return static_cast<EOculusXRAnchorResult::Type>(result);
		}

		// Retrieve the actual array of vertices
		vertices.SetNum(boundary.vertexCountOutput);
		boundary.vertexCapacityInput = boundary.vertexCountOutput;
		boundary.vertices = vertices.GetData();

		result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundary2D(&Space, &boundary);
		if (OVRP_FAILURE(result))
		{
			UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space boundary 2d %d"), result);
			return static_cast<EOculusXRAnchorResult::Type>(result);
		}

		// Write out the vertices
		OutVertices.Reserve(vertices.Num());
		for (const auto& it : vertices)
		{
			OutVertices.Add(FVector2f(it.x, it.y));
		}

		return EOculusXRAnchorResult::Success;
	}


} // namespace OculusXRAnchors
