// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.

#include "OculusXRHMDRuntimeSettings.h"

//////////////////////////////////////////////////////////////////////////
// UOculusXRHMDRuntimeSettings

#include "OculusXRHMD_Settings.h"

UOculusXRHMDRuntimeSettings::UOculusXRHMDRuntimeSettings(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
	, bAutoEnabled(false)
{
#if OCULUS_HMD_SUPPORTED_PLATFORMS
	// FSettings is the sole source of truth for Oculus default settings
	OculusXRHMD::FSettings DefaultSettings;
	SystemSplashBackground = DefaultSettings.SystemSplashBackground;
	bSupportsDash = DefaultSettings.Flags.bSupportsDash;
	bCompositesDepth = DefaultSettings.Flags.bCompositeDepth;
	bHQDistortion = DefaultSettings.Flags.bHQDistortion;
	SuggestedCpuPerfLevel = DefaultSettings.SuggestedCpuPerfLevel;
	SuggestedGpuPerfLevel = DefaultSettings.SuggestedGpuPerfLevel;
	FoveatedRenderingMethod = DefaultSettings.FoveatedRenderingMethod;
	FoveatedRenderingLevel = DefaultSettings.FoveatedRenderingLevel;
	bDynamicFoveatedRendering = DefaultSettings.bDynamicFoveatedRendering;
	bSupportEyeTrackedFoveatedRendering = DefaultSettings.bSupportEyeTrackedFoveatedRendering;
	PixelDensityMin = DefaultSettings.PixelDensityMin;
	PixelDensityMax = DefaultSettings.PixelDensityMax;
	bFocusAware = DefaultSettings.Flags.bFocusAware;
	bDynamicResolution = DefaultSettings.Flags.bPixelDensityAdaptive;
	XrApi = DefaultSettings.XrApi;
	ColorSpace = DefaultSettings.ColorSpace;
	ControllerPoseAlignment = DefaultSettings.ControllerPoseAlignment;
	bRequiresSystemKeyboard = DefaultSettings.Flags.bRequiresSystemKeyboard;
	HandTrackingSupport = DefaultSettings.HandTrackingSupport;
	HandTrackingFrequency = DefaultSettings.HandTrackingFrequency;
	HandTrackingVersion = DefaultSettings.HandTrackingVersion;
	bInsightPassthroughEnabled = DefaultSettings.Flags.bInsightPassthroughEnabled;
	bBodyTrackingEnabled = DefaultSettings.Flags.bBodyTrackingEnabled;
	bEyeTrackingEnabled = DefaultSettings.Flags.bEyeTrackingEnabled;
	bFaceTrackingEnabled = DefaultSettings.Flags.bFaceTrackingEnabled;
	bSupportExperimentalFeatures = DefaultSettings.bSupportExperimentalFeatures;
	bAnchorSupportEnabled = DefaultSettings.Flags.bAnchorSupportEnabled;
	bAnchorSharingEnabled = DefaultSettings.Flags.bAnchorSharingEnabled;
	bSceneSupportEnabled = DefaultSettings.Flags.bSceneSupportEnabled;
	ProcessorFavor = DefaultSettings.ProcessorFavor;
	bTileTurnOffEnabled = DefaultSettings.Flags.bTileTurnOffEnabled;


	FaceTrackingDataSource.Empty(static_cast<int8>(EFaceTrackingDataSourceConfig::MAX));
	FaceTrackingDataSource.Append(DefaultSettings.FaceTrackingDataSource);

	// Default this to false, FSettings doesn't have a separate composite depth flag for mobile
	bCompositeDepthMobile = false;

#else
	// Some set of reasonable defaults, since blueprints are still available on non-Oculus platforms.
	SystemSplashBackground = ESystemSplashBackgroundType::Black;
	bSupportsDash = false;
	bCompositesDepth = false;
	bHQDistortion = false;
	SuggestedCpuPerfLevel = EOculusXRProcessorPerformanceLevel::SustainedLow;
	SuggestedGpuPerfLevel = EOculusXRProcessorPerformanceLevel::SustainedHigh;
	FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering;
	FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Off;
	bDynamicFoveatedRendering = false;
	bSupportEyeTrackedFoveatedRendering = false;
	PixelDensityMin = 0.8f;
	PixelDensityMax = 1.2f;
	bDynamicResolution = false;
	bCompositeDepthMobile = false;
	bFocusAware = true;
	XrApi = EOculusXRXrApi::OVRPluginOpenXR;
	bLateLatching = false;
	ColorSpace = EOculusXRColorSpace::P3;
	ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Default;
	bRequiresSystemKeyboard = false;
	HandTrackingSupport = EOculusXRHandTrackingSupport::ControllersOnly;
	HandTrackingFrequency = EOculusXRHandTrackingFrequency::Low;
	HandTrackingVersion = EOculusXRHandTrackingVersion::Default;
	bInsightPassthroughEnabled = false;
	bSupportExperimentalFeatures = false;
	bBodyTrackingEnabled = false;
	bEyeTrackingEnabled = false;
	bFaceTrackingEnabled = false;
	bAnchorSupportEnabled = false;
	bAnchorSharingEnabled = false;
	bSceneSupportEnabled = false;
	ProcessorFavor = EProcessorFavor::FavorEqually;
	bTileTurnOffEnabled = false;
#endif

	LoadFromIni();
}

#if WITH_EDITOR
bool UOculusXRHMDRuntimeSettings::CanEditChange(const FProperty* InProperty) const
{
	bool bIsEditable = Super::CanEditChange(InProperty);

	if (bIsEditable && InProperty)
	{
		const FName PropertyName = InProperty->GetFName();

		if (PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, XrApi) && !FModuleManager::Get().IsModuleLoaded("OpenXRHMD"))
		{
			bIsEditable = false;
		}

// Disable settings for marketplace release that are only compatible with the Oculus engine fork
#ifndef WITH_OCULUS_BRANCH
		if (PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingMethod) || PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportEyeTrackedFoveatedRendering) || PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bDynamicResolution))

		{
			bIsEditable = false;
		}
#endif // WITH_OCULUS_BRANCH
	}

	return bIsEditable;
}

void UOculusXRHMDRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);

	if (PropertyChangedEvent.Property != nullptr)
	{
		// Automatically switch to Fixed Foveated Rendering when removing Eye Tracked Foveated rendering support
		if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportEyeTrackedFoveatedRendering) && !bSupportEyeTrackedFoveatedRendering)
		{
			FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering;
			UpdateSinglePropertyInConfigFile(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingMethod)), GetDefaultConfigFilename());
		}
		// Automatically enable support for eye tracked foveated rendering when selecting the Eye Tracked Foveated Rendering method
		if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingMethod) && FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering)
		{
			bSupportEyeTrackedFoveatedRendering = true;
			UpdateSinglePropertyInConfigFile(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportEyeTrackedFoveatedRendering)), GetDefaultConfigFilename());
		}

		if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, SupportedDevices))
		{
			if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd)
			{
				// Get a list of all available devices
				TArray<EOculusXRSupportedDevices> deviceList;
#define OCULUS_DEVICE_LOOP(device) deviceList.Add(device);
				FOREACH_ENUM_EOCULUSXRSUPPORTEDDEVICES(OCULUS_DEVICE_LOOP);
#undef OCULUS_DEVICE_LOOP
				// Add last device that isn't already in the list
				for (int i = deviceList.Num() - 1; i >= 0; --i)
				{
					if (!SupportedDevices.Contains(deviceList[i]))
					{
						SupportedDevices.Last() = deviceList[i];
						break;
					}
					// Just add another copy of the first device if nothing was available
					SupportedDevices.Last() = deviceList[deviceList.Num() - 1];
				}
			}
		}
	}
}
#endif // WITH_EDITOR

void UOculusXRHMDRuntimeSettings::PostInitProperties()
{
	Super::PostInitProperties();
	RenameProperties();

	const TCHAR* OculusSettings = TEXT("/Script/OculusXRHMD.OculusXRHMDRuntimeSettings");
	if (!FModuleManager::Get().IsModuleLoaded("OpenXRHMD"))
	{
		XrApi = EOculusXRXrApi::OVRPluginOpenXR;
		UpdateSinglePropertyInConfigFile(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, XrApi)), GetDefaultConfigFilename());
	}
}

void UOculusXRHMDRuntimeSettings::LoadFromIni()
{
	const TCHAR* OculusSettings = TEXT("Oculus.Settings");
	bool v;
	float f;
	FVector vec;

	if (GConfig->GetFloat(OculusSettings, TEXT("PixelDensityMax"), f, GEngineIni))
	{
		check(!FMath::IsNaN(f));
		PixelDensityMax = f;
	}
	if (GConfig->GetFloat(OculusSettings, TEXT("PixelDensityMin"), f, GEngineIni))
	{
		check(!FMath::IsNaN(f));
		PixelDensityMin = f;
	}
	if (GConfig->GetBool(OculusSettings, TEXT("bHQDistortion"), v, GEngineIni))
	{
		bHQDistortion = v;
	}
	if (GConfig->GetBool(OculusSettings, TEXT("bCompositeDepth"), v, GEngineIni))
	{
		bCompositesDepth = v;
	}
}

/** This essentially acts like redirects for plugin settings saved in the engine config.
	Anything added here should check for the current setting in the config so that if the dev changes the setting manually, we don't overwrite it with the old setting.
	Note: Do not use UpdateSinglePropertyInConfigFile() here, since that uses a temp config to save the single property,
	it'll get overwritten when GConfig->RemoveKey() marks the main config as dirty and it gets saved again **/
void UOculusXRHMDRuntimeSettings::RenameProperties()
{
	const TCHAR* OculusSettings = TEXT("/Script/OculusXRHMD.OculusXRHMDRuntimeSettings");
	bool v = false;
	FString str;

	// FFRLevel was renamed to FoveatedRenderingLevel
	if (!GConfig->GetString(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingLevel), str, GetDefaultConfigFilename()) && GConfig->GetString(OculusSettings, TEXT("FFRLevel"), str, GetDefaultConfigFilename()))
	{
		if (str.Equals(TEXT("FFR_Off")))
		{
			FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Off;
		}
		else if (str.Equals(TEXT("FFR_Low")))
		{
			FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Low;
		}
		else if (str.Equals(TEXT("FFR_Medium")))
		{
			FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Medium;
		}
		else if (str.Equals(TEXT("FFR_High")))
		{
			FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::High;
		}
		else if (str.Equals(TEXT("FFR_HighTop")))
		{
			FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::HighTop;
		}
		// Use GetNameStringByValue() here because GetValueAsString() includes the type name as well
		GConfig->SetString(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingLevel), *StaticEnum<EOculusXRFoveatedRenderingLevel>()->GetNameStringByValue((int64)FoveatedRenderingLevel), GetDefaultConfigFilename());
		GConfig->RemoveKey(OculusSettings, TEXT("FFRLevel"), GetDefaultConfigFilename());
	}

	// FFRDynamic was renamed to bDynamicFoveatedRendering
	if (!GConfig->GetString(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, bDynamicFoveatedRendering), str, GetDefaultConfigFilename()) && GConfig->GetBool(OculusSettings, TEXT("FFRDynamic"), v, GetDefaultConfigFilename()))
	{
		bDynamicFoveatedRendering = v;
		GConfig->SetBool(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, bDynamicFoveatedRendering), bDynamicFoveatedRendering, GetDefaultConfigFilename());
		GConfig->RemoveKey(OculusSettings, TEXT("FFRDynamic"), GetDefaultConfigFilename());
	}

	const FString Quest = TEXT("Quest");

#ifndef WITH_OCULUS_BRANCH
	const TCHAR* AndroidSettings = TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings");
	TArray<FString> PackageList;
	const TCHAR* PackageForMobileKey = TEXT("+PackageForOculusMobile");
	if (GConfig->GetArray(AndroidSettings, PackageForMobileKey, PackageList, GetDefaultConfigFilename()))
	{
		const FString Quest2 = TEXT("Quest2");
		if (PackageList.Contains(Quest))
		{
			PackageList.Remove(Quest);
			if (!PackageList.Contains(Quest2))
			{
				PackageList.Add(Quest2);
			}
			GConfig->SetArray(AndroidSettings, PackageForMobileKey, PackageList, GetDefaultConfigFilename());
		}
	}
#endif // WITH_OCULUS_BRANCH

	TArray<FString> DeviceList;
	const TCHAR* SupportedDevicesKey = *FString("+").Append(GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, SupportedDevices));
	if (GConfig->GetArray(OculusSettings, SupportedDevicesKey, DeviceList, GetDefaultConfigFilename()))
	{
		const EOculusXRSupportedDevices LastSupportedDevice = EOculusXRSupportedDevices::Quest2;
		const FString LastSupportedDeviceString = StaticEnum<EOculusXRSupportedDevices>()->GetNameStringByValue((int64)LastSupportedDevice);
		if (DeviceList.Contains(Quest))
		{
			DeviceList.Remove(Quest);
			if (!DeviceList.Contains(LastSupportedDeviceString))
			{
				DeviceList.Add(LastSupportedDeviceString);
			}
			GConfig->SetArray(OculusSettings, SupportedDevicesKey, DeviceList, GetDefaultConfigFilename());

			// Reflect the config changes in the Project Settings UI
			SupportedDevices.Remove((EOculusXRSupportedDevices)0); // Enums that don't exist just have a value of 0
			if (!SupportedDevices.Contains(LastSupportedDevice))
			{
				SupportedDevices.Add(LastSupportedDevice);
			}
		}
	}
}
