using System;
using Unity.Mathematics;
using UnityEngine.Rendering;

#if ENABLE_VR && ENABLE_XR_MODULE
using UnityEngine.XR;
#endif

namespace UnityEngine.Experimental.Rendering
{
    internal static class XRMirrorView
    {
        static readonly MaterialPropertyBlock s_MirrorViewMaterialProperty = new MaterialPropertyBlock();
        static readonly ProfilingSampler k_MirrorViewProfilingSampler = new ProfilingSampler("XR Mirror View");

        static readonly int k_SourceTex = Shader.PropertyToID("_SourceTex");
        static readonly int k_SourceTexArraySlice = Shader.PropertyToID("_SourceTexArraySlice");
        static readonly int k_ScaleBias = Shader.PropertyToID("_ScaleBias");
        static readonly int k_ScaleBiasRt = Shader.PropertyToID("_ScaleBiasRt");
        static readonly int k_SRGBRead = Shader.PropertyToID("_SRGBRead");
        static readonly int k_SRGBWrite = Shader.PropertyToID("_SRGBWrite");
        static readonly int k_MaxNits = Shader.PropertyToID("_MaxNits");
        static readonly int k_SourceMaxNits = Shader.PropertyToID("_SourceMaxNits");
        static readonly int k_SourceHDREncoding = Shader.PropertyToID("_SourceHDREncoding");
        static readonly int k_ColorTransform = Shader.PropertyToID("_ColorTransform");

#if ENABLE_VR && ENABLE_XR_MODULE
        internal static void RenderMirrorView(CommandBuffer cmd, Camera camera, Material mat, XRDisplaySubsystem display)
        {
            // XRTODO : remove this check when the Quest plugin is fixed
            if (Application.platform == RuntimePlatform.Android && !XRGraphicsAutomatedTests.running)
                return;

            if (display == null || !display.running || mat == null)
                return;

            int mirrorBlitMode = display.GetPreferredMirrorBlitMode();
            if (display.GetMirrorViewBlitDesc(null, out var blitDesc, mirrorBlitMode))
            {
                using (new ProfilingScope(cmd, k_MirrorViewProfilingSampler))
                {
                    cmd.SetRenderTarget(camera.targetTexture != null ? camera.targetTexture : new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget));

                    if (blitDesc.nativeBlitAvailable)
                    {
                        display.AddGraphicsThreadMirrorViewBlit(cmd, blitDesc.nativeBlitInvalidStates, mirrorBlitMode);
                    }
                    else
                    {
                        for (int i = 0; i < blitDesc.blitParamsCount; ++i)
                        {
                            blitDesc.GetBlitParameter(i, out var blitParam);

                            Vector4 scaleBias = new Vector4(blitParam.srcRect.width, blitParam.srcRect.height, blitParam.srcRect.x, blitParam.srcRect.y);
                            Vector4 scaleBiasRt = new Vector4(blitParam.destRect.width, blitParam.destRect.height, blitParam.destRect.x, blitParam.destRect.y);

                            // Deal with y-flip
                            if (camera.targetTexture != null || camera.cameraType == CameraType.SceneView || camera.cameraType == CameraType.Preview)
                            {
                                scaleBias.y = -scaleBias.y;
                                scaleBias.w += blitParam.srcRect.height;
                            }

                            HDROutputSettings mainDisplayHdrSettings = HDROutputSettings.main;

                            // If we are writing to a HDR surface or reading from one we use the conversion shader to handle both
                            if (blitParam.srcHdrEncoded || mainDisplayHdrSettings.active)
                            {
                                ColorGamut mainDisplayColorGamut = mainDisplayHdrSettings.active ? mainDisplayHdrSettings.displayColorGamut
                                    : ColorGamut.sRGB;
                                ColorGamut xrDisplayColorGamut = blitParam.srcHdrEncoded ? blitParam.srcHdrColorGamut
                                    : ColorGamut.sRGB;

                                ColorPrimaries mainDisplayColorPrimaries = ColorGamutUtility.GetColorPrimaries(mainDisplayColorGamut);
                                ColorPrimaries xrDisplayColorPrimaries = ColorGamutUtility.GetColorPrimaries(xrDisplayColorGamut);

                                // Use the material? And use the passes?
                                HDROutputUtils.ConfigureHDROutput(s_MirrorViewMaterialProperty, mainDisplayColorGamut);
                                HDROutputUtils.ConfigureHDROutput(mat, HDROutputUtils.Operation.ColorConversion | HDROutputUtils.Operation.ColorEncoding);
                                int sourceHdrEncoding;
                                HDROutputUtils.GetColorEncodingForGamut(xrDisplayColorGamut, out sourceHdrEncoding);
                                s_MirrorViewMaterialProperty.SetInteger(k_SourceHDREncoding, sourceHdrEncoding);

                                float3x3 sourceToRec2020 = float3x3.identity;
                                if (xrDisplayColorPrimaries == ColorPrimaries.Rec709)
                                    sourceToRec2020 = ColorSpaceUtils.Rec709ToRec2020Mat;
                                else if (xrDisplayColorPrimaries == ColorPrimaries.P3)
                                    sourceToRec2020 = ColorSpaceUtils.P3D65ToRec2020Mat;

                                float3x3 rec2020ToDest = float3x3.identity;
                                if (mainDisplayColorPrimaries == ColorPrimaries.Rec709)
                                    rec2020ToDest = ColorSpaceUtils.Rec2020ToRec709Mat;
                                else if (mainDisplayColorPrimaries == ColorPrimaries.P3)
                                    rec2020ToDest = ColorSpaceUtils.Rec2020ToP3D65Mat;

                                // Quicker to go straight to a Matrix4x4 and multiply there? Or to store these as Matrix4x4 instead due to Unity missing a 3x3 type?
                                float3x3 colorTransform = math.mul(sourceToRec2020, rec2020ToDest);
                                Matrix4x4 m = new Matrix4x4((Vector4) new float4(colorTransform.c0, 0.0f),
                                    (Vector4) new float4(colorTransform.c1, 0.0f), (Vector4) new float4(colorTransform.c2, 0.0f),
                                    new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
                                s_MirrorViewMaterialProperty.SetMatrix(k_ColorTransform, m);

                                s_MirrorViewMaterialProperty.SetFloat(k_MaxNits, mainDisplayHdrSettings.active ? mainDisplayHdrSettings.maxToneMapLuminance : 160.0f);
                                s_MirrorViewMaterialProperty.SetFloat(k_SourceMaxNits, blitParam.srcHdrEncoded ? blitParam.srcHdrMaxLuminance : 160.0f);
                            }

                            // For 8888 formats we always gamma correct eye textures : use explicit sRGB read in shader only if the source is not using sRGB format.
                            bool manualSRGBRead = !blitParam.srcTex.sRGB &&
                                                  (blitParam.srcTex.graphicsFormat == GraphicsFormat.R8G8B8A8_UNorm ||
                                                   blitParam.srcTex.graphicsFormat == GraphicsFormat.B8G8R8A8_UNorm);
                            s_MirrorViewMaterialProperty.SetFloat(k_SRGBRead, manualSRGBRead ? 1.0f : 0.0f);

                            // Perform explicit sRGB write in shader if color space is gamma
                            s_MirrorViewMaterialProperty.SetFloat(k_SRGBWrite, (QualitySettings.activeColorSpace == ColorSpace.Linear) ? 0.0f : 1.0f);

                            s_MirrorViewMaterialProperty.SetTexture(k_SourceTex, blitParam.srcTex);
                            s_MirrorViewMaterialProperty.SetVector(k_ScaleBias, scaleBias);
                            s_MirrorViewMaterialProperty.SetVector(k_ScaleBiasRt, scaleBiasRt);
                            s_MirrorViewMaterialProperty.SetFloat(k_SourceTexArraySlice, blitParam.srcTexArraySlice);

                            if (XRSystem.foveatedRenderingCaps.HasFlag(FoveatedRenderingCaps.NonUniformRaster) && blitParam.foveatedRenderingInfo != IntPtr.Zero)
                            {
                                cmd.ConfigureFoveatedRendering(blitParam.foveatedRenderingInfo);
                                cmd.EnableShaderKeyword("_FOVEATED_RENDERING_NON_UNIFORM_RASTER");
                            }

                            if (blitParam.srcTex.dimension != TextureDimension.Tex2DArray)
                                cmd.EnableShaderKeyword("DISABLE_TEXTURE2D_X_ARRAY");

                            cmd.DrawProcedural(Matrix4x4.identity, mat, 0, MeshTopology.Quads, 4, 1, s_MirrorViewMaterialProperty);

                            // Set back the XR texture for regular XR calls
                            if (blitParam.srcTex.dimension != TextureDimension.Tex2DArray && TextureXR.useTexArray)
                                cmd.DisableShaderKeyword("DISABLE_TEXTURE2D_X_ARRAY");
                        }
                    }
                }
            }

            if (XRSystem.foveatedRenderingCaps.HasFlag(FoveatedRenderingCaps.NonUniformRaster))
            {
                cmd.DisableShaderKeyword("_FOVEATED_RENDERING_NON_UNIFORM_RASTER");
                cmd.ConfigureFoveatedRendering(IntPtr.Zero);
            }
        }

#endif
    }
}