/*
 * Copyright 2019,2020,2021 Sony Corporation
 */

#pragma once

#include "xr_runtime.h"
#include "srd_xr_api_defs.h"

#include "Blueprint/SRDisplayManager.h"
#include "Compositor/StereoTexture.h"
#include "Compositor/DisplayProjector.h"

#include <Engine.h>
#include <HeadMountedDisplayBase.h>
#include <SceneViewExtension.h>

DECLARE_LOG_CATEGORY_EXTERN(LogSRDisplay, Log, All);

namespace sr_display
{
	class FRenderTargetManager;

	class SRDISPLAYMODULE_API FSRDisplaySystem : public FHeadMountedDisplayBase, public FSceneViewExtensionBase
	{

	public:

		FSRDisplaySystem(const FAutoRegister&);
		~FSRDisplaySystem();

		bool IsInitialized() const;

		// Begin IXRSystemIdentifier
		virtual FName GetSystemName() const override
		{
			static const FName Name(TEXT("SRDisplay"));
			return Name;
		}
		// End IXRSystemIdentifier

		// Begin IXRTrackingSystem
		virtual FString GetVersionString() const override;
#if ENGINE_MINOR_VERSION >= 26
		virtual int32 GetXRSystemFlags() const override;
#endif
		virtual bool DoesSupportPositionalTracking() const override { return true; };
		virtual bool EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type = EXRTrackedDeviceType::Any) override;
		virtual bool GetCurrentPose(int32 DeviceId, FQuat& OutOrientation, FVector& OutPosition) override;
		virtual void ResetOrientationAndPosition(float Yaw = 0.f) override {};
		virtual class IHeadMountedDisplay* GetHMDDevice() override { return this; }
		virtual class TSharedPtr< class IStereoRendering, ESPMode::ThreadSafe > GetStereoRenderingDevice() override { return SharedThis(this); }
		virtual bool IsHeadTrackingAllowed() const override;
		virtual void OnBeginPlay(FWorldContext& InWorldContext) override;
		virtual void OnEndPlay(FWorldContext& InWorldContext) override;
		virtual bool OnStartGameFrame(FWorldContext& WorldContext) override;
		virtual void OnBeginRendering_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily) override;
		// End IXRTrackingSystem

		// Begin FXRTrackingSystemBase
		virtual float GetWorldToMetersScale() const override;
		// End FXRTrackingSystemBase

		// Begin IHeadMountedDisplay
		virtual bool IsHMDConnected() override;
		virtual bool IsHMDEnabled() const override { return true; };
		virtual void EnableHMD(bool bEnable = true) override {};
		virtual bool GetHMDMonitorInfo(MonitorInfo&) override;
		virtual void GetFieldOfView(float& InOutHFOVInDegrees, float& InOutVFOVInDegrees) const override {};
		virtual void SetInterpupillaryDistance(float NewInterpupillaryDistance) override {};
		virtual float GetInterpupillaryDistance() const override { return 0.f; };
		virtual FIntPoint GetIdealRenderTargetSize() const override;
		virtual bool IsChromaAbCorrectionEnabled() const override { return false; };
		// End IHeadMountedDisplay

		// Begin IStereoRendering
		virtual bool IsStereoEnabled() const override;
		virtual bool EnableStereo(bool stereo = true) override;
		virtual void AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override;
		virtual void CalculateStereoViewOffset(const enum EStereoscopicPass StereoPassType, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation) override;
		virtual FMatrix GetStereoProjectionMatrix(const enum EStereoscopicPass StereoPassType) const override;
		virtual void RenderTexture_RenderThread(class FRHICommandListImmediate& RHICmdList, class FRHITexture2D* BackBuffer, class FRHITexture2D* SrcTexture, FVector2D WindowSize) const override;
		virtual IStereoRenderTargetManager* GetRenderTargetManager() override;
		// End IStereoRendering

		// Begin ISceneViewExtension
		virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override;
		virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override;
		virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override;
		virtual void PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override;
		virtual void PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) override;
		virtual bool IsActiveThisFrame(class FViewport* InViewport) const override;
		// End ISceneViewExtension

	public:
		// Begin SR Display APIs.
		bool GetMousePosition(float& LocationX, float& LocationY);
		bool DeprojectScreenToWorld(APlayerController const* Player, const FVector2D& ScreenPosition, FVector& WorldPosition, FVector& WorldDirection, FVector& CameraPosition);
		void SetStereoImages(FTexture2DRHIRef left, FTexture2DRHIRef right);
		void ShowStereoImages(bool bShow);
		bool IsStereoImagesShown();
		void GetDisplaySpec(float& Width, float& Height, float& Tilt);
		// End SR Display APIs.

	public:
		static const float DEFAULT_SRD_VIEW_SPACE_SCALE;
		static const float DEFAULT_SRD_FAR_CLIP;

	private:
		bool InitializeXrRuntime();
		bool FinalizeXrRuntime();

		bool StartXrRuntime();
		bool StopXrRuntime();

		// Whether or not stereo rendering is allowed
		bool IsStereoRenderingAllowed(FSceneViewport** OutSceneViewport);

		bool IsProjectPlayModeSupported();
		bool IsProjectGraphicsAPISupported();
		SrdXrGraphicsAPI GetCurrentGraphicsAPI();

		ASRDisplayManager* GetSRDisplayManagerActor();
		void UpdateSRDisplayManagerState();
		void UpdateSRDisplayPosition();

		// Get projection matrix and clipping distance, and calculate stereo projection matrix.
		void UpdateProjectionMatrix();
		FMatrix GetSpatialClippedProjectionMatrix(FVector ClipPlanePositionInWorldCoord, FVector ClipPlaneNormalVecInWorldCoord, FMatrix ViewMatrix, FMatrix ProjectionMatrix);
		FMatrix GetNearClipPlaneConvertedProjectionMatrix(FMatrix SrcProjMat, FVector4 PlaneInViewSpace);
		FMatrix GetDefaultProjectionMatrix(enum EStereoscopicPass StereoPassType) const;
		bool GetViewMatrix(FMatrix& LeftViewMatrix, FMatrix& RightViewMatrix);

		void CreateWindowOnSRDisplay();
		void DestroyWindowOnSRDisplay();

		void CompositeStereoImages(FRHICommandListImmediate& RHICmdList, FRHITexture2D* BackBuffer, FRHITexture2D* SrcLeftTexture, FRHITexture2D* SrcRightTexture, uint32 SourceWidth, uint32 SourceHeight) const;

		bool GetDisplayCornersPosition(FVector4& LeftBottomPosition, FVector4& LeftTopPosition, FVector4& RightBottomPosition, FVector4& RightTopPosition) const;

		void ConvertPoseFromXrRuntimeToGameWorld(SrdXrPosef SrcPose, FQuat& DstOrientation, FVector& DstPosition) const;
		bool HandleXRRuntimeResult(SrdXrResult Result);
		bool HandleXRRuntimeResult_LocateViews(SrdXrResult Result);
		bool HandleXRRuntimeResult_GetProjectionMatrix(SrdXrResult Result);

	private:
		FRenderTargetManager* RenderTargetManager = nullptr;

		// For application life time management.
		bool bQuitApplication = false;

		// For XR Runtime management.
		SrdXrSessionHandle RuntimeSessionHandle = nullptr;
		bool bIsSessionBegun = false;
		bool bStereoEnabled = false;

		// SR Display's specs.
		SrdXrSrdData XrdPlatformSpecificData;
		FVector2D DisplayResolution;

		// View projection matrix used in game thread.
		FMatrix LeftViewProjectionMatrix;
		FMatrix RightViewProjectionMatrix;

		// View projection matrix used in rendering thread.
		FMatrix LeftViewProjectionMatrix_RenderingThread;
		FMatrix RightViewProjectionMatrix_RenderingThread;

		// Actor "SRDisplayManager" and it's params.
		ASRDisplayManager* SRDisplayManager = nullptr;
		float SRDisplayViewSpaceScale = DEFAULT_SRD_VIEW_SPACE_SCALE;
		FTransform SRDisplayTransform = FTransform::Identity;
		bool ShowCameraWindow = false;
		float SRDisplayFarClip = DEFAULT_SRD_FAR_CLIP;
		bool SpatialClipping = false;

		// Display Projection
		FDisplayProjector* DisplayProjector = nullptr;

		// Showing 2D image on SR Display.
		FStereoTexture StereoTexture;
		bool bShowStereoImages = false;

		// Critical sections.
		FCriticalSection CSGetCurrentPose;

		bool isWithoutSRDisplayMode = false;

	}; // class FARHeadMountedDisplay

} // namespace sr_display
