using System;

namespace UnityEngine.Rendering
{
    /// <summary>
    /// Utility class for outputting to an HDR display.
    /// </summary>
    public static class HDROutputUtils
    {
        /// <summary> HDR color operations that the shader applies. </summary>
        [Flags]
        public enum Operation
        {
            /// <summary> Do not perform operations specific to HDR output. </summary>
            None = 0,
            /// <summary> Convert colors to the color space of the HDR display. </summary>
            ColorConversion = 1 << 0,
            /// <summary> Encode colors with the transfer function corresponding to the HDR display. </summary>
            ColorEncoding = 1 << 1
        }

        /// <summary>
        /// This struct Provides access to HDR display settings and information.
        /// </summary>
        public struct HDRDisplayInformation
        {
            /// <summary>
            /// Constructs HDR Display settings.
            /// </summary>
            /// <param name="maxFullFrameToneMapLuminance">Maximum input luminance at which gradation is preserved even when the entire screen is bright.</param>
            /// <param name="maxToneMapLuminance">Maximum input luminance at which gradation is preserved when 10% of the screen is bright.</param>
            /// <param name="minToneMapLuminance">Minimum input luminance at which gradation is identifiable.</param>
            /// <param name="hdrPaperWhiteNits">The base luminance of a white paper surface in nits or candela per square meter.</param>
            public HDRDisplayInformation(int maxFullFrameToneMapLuminance, int maxToneMapLuminance, int minToneMapLuminance, float hdrPaperWhiteNits)
            {
                this.maxFullFrameToneMapLuminance = maxFullFrameToneMapLuminance;
                this.maxToneMapLuminance = maxToneMapLuminance;
                this.minToneMapLuminance = minToneMapLuminance;
                this.paperWhiteNits = hdrPaperWhiteNits;
            }

            /// <summary>Maximum input luminance at which gradation is preserved even when the entire screen is bright. </summary>
            public int maxFullFrameToneMapLuminance;

            /// <summary>Maximum input luminance at which gradation is preserved when 10% of the screen is bright. </summary>
            public int maxToneMapLuminance;

            /// <summary>Minimum input luminance at which gradation is identifiable. </summary>
            public int minToneMapLuminance;

            /// <summary>The base luminance of a white paper surface in nits or candela per square meter. </summary>
            public float paperWhiteNits;
        }

        /// <summary>Shader keywords for communicating with the HDR Output shader implementation.</summary>
        public static class ShaderKeywords
        {
            /// <summary>Keyword string for converting to the correct output color space. </summary>
            public const string HDR_COLORSPACE_CONVERSION = "HDR_COLORSPACE_CONVERSION";

            /// <summary>Keyword string for applying the color encoding. </summary>
            public const string HDR_ENCODING = "HDR_ENCODING";

            /// <summary>Keyword string for converting to the correct output color space and applying the color encoding. </summary>
            public const string HDR_COLORSPACE_CONVERSION_AND_ENCODING = "HDR_COLORSPACE_CONVERSION_AND_ENCODING";

            /// <summary>Keyword string to enable when a shader must be aware the input color space is in nits HDR range. </summary>
            public const string HDR_INPUT = "HDR_INPUT";

            /// <summary>Keyword for converting to the correct output color space. </summary>
            internal static readonly ShaderKeyword HDRColorSpaceConversion = new ShaderKeyword(HDR_COLORSPACE_CONVERSION);

            /// <summary>Keyword for applying the color encoding. </summary>
            internal static readonly ShaderKeyword HDREncoding = new ShaderKeyword(HDR_ENCODING);

            /// <summary>Keyword for converting to the correct output color space and applying the color encoding. </summary>
            internal static readonly ShaderKeyword HDRColorSpaceConversionAndEncoding = new ShaderKeyword(HDR_COLORSPACE_CONVERSION_AND_ENCODING);

            /// <summary>Keyword to enable when a shader must be aware the input color space is in nits HDR range. </summary>
            internal static readonly ShaderKeyword HDRInput = new ShaderKeyword(HDR_INPUT);
        }

        static class ShaderPropertyId
        {
            public static readonly int hdrColorSpace = Shader.PropertyToID("_HDRColorspace");
            public static readonly int hdrEncoding = Shader.PropertyToID("_HDREncoding");
        }

        /// <summary>
        /// Extracts the color space part of the ColorGamut
        /// </summary>
        /// <param name="gamut">Color gamut (a combination of color space and encoding) queried from the device.</param>
        /// <param name="colorspace">The HDRColorspace value the color gamut contains as an int.</param>
        /// <returns>Returns true if there was a valid HDRColorspace for the ColorGamut, false otherwise</returns>
        public static bool GetColorSpaceForGamut(ColorGamut gamut, out int colorspace)
        {
            WhitePoint whitePoint = ColorGamutUtility.GetWhitePoint(gamut);
            if (whitePoint != WhitePoint.D65)
            {
                Debug.LogWarningFormat("{0} white point is currently unsupported for outputting to HDR.", gamut.ToString());
                colorspace = -1;
                return false;
            }

            ColorPrimaries primaries = ColorGamutUtility.GetColorPrimaries(gamut);
            switch (primaries)
            {
                case ColorPrimaries.Rec709:
                    colorspace = (int)HDRColorspace.Rec709;
                    return true;

                case ColorPrimaries.Rec2020:
                    colorspace = (int)HDRColorspace.Rec2020;
                    return true;

                case ColorPrimaries.P3:
                    colorspace = (int)HDRColorspace.P3D65;
                    return true;

                default:
                    Debug.LogWarningFormat("{0} color space is currently unsupported for outputting to HDR.", gamut.ToString());
                    colorspace = -1;
                    return false;
            }
        }

        /// <summary>
        /// Extracts the encoding part of the ColorGamut
        /// </summary>
        /// <param name="gamut">Color gamut (a combination of color space and encoding) queried from the device.</param>
        /// <param name="encoding">The HDREncoding value the color gamut contains as an int.</param>
        /// <returns>Returns true if there was a valid HDREncoding for the ColorGamut, false otherwise</returns>
        public static bool GetColorEncodingForGamut(ColorGamut gamut, out int encoding)
        {
            TransferFunction transferFunction = ColorGamutUtility.GetTransferFunction(gamut);
            switch (transferFunction)
            {
                case TransferFunction.Linear:
                    encoding = (int)HDREncoding.Linear;
                    return true;

                case TransferFunction.PQ:
                    encoding = (int)HDREncoding.PQ;
                    return true;

                case TransferFunction.Gamma22:
                    encoding = (int)HDREncoding.Gamma22;
                    return true;

                case TransferFunction.sRGB:
                    encoding = (int)HDREncoding.sRGB;
                    return true;

                default:
                    Debug.LogWarningFormat("{0} color encoding is currently unsupported for outputting to HDR.", gamut.ToString());
                    encoding = -1;
                    return false;
            }

        }

        /// <summary>
        /// Configures the Material keywords to use HDR output parameters.
        /// </summary>
        /// <param name="material">The Material used with HDR output.</param>
        /// <param name="gamut">Color gamut (a combination of color space and encoding) queried from the device.</param>
        /// <param name="operations">HDR color operations the shader applies.</param>
        public static void ConfigureHDROutput(Material material, ColorGamut gamut, Operation operations)
        {
            int colorSpace;
            int encoding;
            if (!GetColorSpaceForGamut(gamut, out colorSpace) || !GetColorEncodingForGamut(gamut, out encoding))
                return; // only exit here if there is an error or unsupported mode

            material.SetInteger(ShaderPropertyId.hdrColorSpace, colorSpace);
            material.SetInteger(ShaderPropertyId.hdrEncoding, encoding);

            CoreUtils.SetKeyword(material, ShaderKeywords.HDRColorSpaceConversionAndEncoding.name, operations.HasFlag(Operation.ColorConversion) && operations.HasFlag(Operation.ColorEncoding));
            CoreUtils.SetKeyword(material, ShaderKeywords.HDREncoding.name, operations.HasFlag(Operation.ColorEncoding) && !operations.HasFlag(Operation.ColorConversion));
            CoreUtils.SetKeyword(material, ShaderKeywords.HDRColorSpaceConversion.name, operations.HasFlag(Operation.ColorConversion) && !operations.HasFlag(Operation.ColorEncoding));

            // Optimizing shader variants: define HDR_INPUT only if HDR_COLORSPACE_CONVERSION and HDR_ENCODING were not previously defined
            CoreUtils.SetKeyword(material, ShaderKeywords.HDRInput.name, operations == Operation.None);
        }

        /// <summary>
        /// Configures the Material Property Block variables to use HDR output parameters.
        /// </summary>
        /// <param name="properties">The Material Property Block used with HDR output.</param>
        /// <param name="gamut">Color gamut (a combination of color space and encoding) queried from the device.</param>
        public static void ConfigureHDROutput(MaterialPropertyBlock properties, ColorGamut gamut)
        {
            int colorSpace;
            int encoding;
            if (!GetColorSpaceForGamut(gamut, out colorSpace) || !GetColorEncodingForGamut(gamut, out encoding))
                return;

            properties.SetInteger(ShaderPropertyId.hdrColorSpace, colorSpace);
            properties.SetInteger(ShaderPropertyId.hdrEncoding, encoding);
        }

        /// <summary>
        /// Configures the Material keywords to use HDR output parameters.
        /// </summary>
        /// <param name="material">The Material used with HDR output.</param>
        /// <param name="operations">HDR color operations the shader applies.</param>
        public static void ConfigureHDROutput(Material material, Operation operations)
        {
            CoreUtils.SetKeyword(material, ShaderKeywords.HDRColorSpaceConversionAndEncoding.name, operations.HasFlag(Operation.ColorConversion) && operations.HasFlag(Operation.ColorEncoding));
            CoreUtils.SetKeyword(material, ShaderKeywords.HDREncoding.name, operations.HasFlag(Operation.ColorEncoding) && !operations.HasFlag(Operation.ColorConversion));
            CoreUtils.SetKeyword(material, ShaderKeywords.HDRColorSpaceConversion.name, operations.HasFlag(Operation.ColorConversion) && !operations.HasFlag(Operation.ColorEncoding));

            // Optimizing shader variants: define HDR_INPUT only if HDR_COLORSPACE_CONVERSION and HDR_ENCODING were not previously defined
            CoreUtils.SetKeyword(material, ShaderKeywords.HDRInput.name, operations == Operation.None);
        }

        /// <summary>
        /// Configures the compute shader keywords to use HDR output parameters.
        /// </summary>
        /// <param name="computeShader">The compute shader used with HDR output.</param>
        /// <param name="gamut">Color gamut (a combination of color space and encoding) queried from the device.</param>
        /// <param name="operations">HDR color operations the shader applies.</param>
        public static void ConfigureHDROutput(ComputeShader computeShader, ColorGamut gamut, Operation operations)
        {
            int colorSpace;
            int encoding;
            if (!GetColorSpaceForGamut(gamut, out colorSpace) || !GetColorEncodingForGamut(gamut, out encoding))
                return; // only exit here if there is an error or unsupported mode

            computeShader.SetInt(ShaderPropertyId.hdrColorSpace, colorSpace);
            computeShader.SetInt(ShaderPropertyId.hdrEncoding, encoding);

            CoreUtils.SetKeyword(computeShader, ShaderKeywords.HDRColorSpaceConversionAndEncoding.name, operations.HasFlag(Operation.ColorConversion) && operations.HasFlag(Operation.ColorEncoding));
            CoreUtils.SetKeyword(computeShader, ShaderKeywords.HDREncoding.name, operations.HasFlag(Operation.ColorEncoding) && !operations.HasFlag(Operation.ColorConversion));
            CoreUtils.SetKeyword(computeShader, ShaderKeywords.HDRColorSpaceConversion.name, operations.HasFlag(Operation.ColorConversion) && !operations.HasFlag(Operation.ColorEncoding));

            // Optimizing shader variants: define HDR_INPUT only if HDR_COLORSPACE_CONVERSION and HDR_ENCODING were not previously defined
            CoreUtils.SetKeyword(computeShader, ShaderKeywords.HDRInput.name, operations == Operation.None);
        }

        /// <summary>
        /// Returns true if the given set of keywords is valid for HDR output.
        /// </summary>
        /// <param name="shaderKeywordSet">Shader keywords combination that represents a shader variant.</param>
        /// <param name="isHDREnabled">Whether HDR output shader variants are required.</param>
        /// <returns>True if the shader variant is valid and should not be stripped.</returns>
        public static bool IsShaderVariantValid(ShaderKeywordSet shaderKeywordSet, bool isHDREnabled)
        {
            bool hasHDRKeywords = shaderKeywordSet.IsEnabled(ShaderKeywords.HDREncoding) || shaderKeywordSet.IsEnabled(ShaderKeywords.HDRColorSpaceConversion) || shaderKeywordSet.IsEnabled(ShaderKeywords.HDRColorSpaceConversionAndEncoding) || shaderKeywordSet.IsEnabled(ShaderKeywords.HDRInput);

            // If we don't plan to enable HDR, remove all HDR Output variants
            if (!isHDREnabled && hasHDRKeywords)
                return false;

            return true;
        }
    }
}