// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.Linq;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
using DebuggerDisplayAttribute = System.Diagnostics.DebuggerDisplayAttribute;

namespace UnityEditor
{
    public class ShaderData
    {
        public class Subshader
        {
            internal enum Type
            {
                Runtime,
                Serialized
            }

            internal ShaderData m_Data;
            internal int m_SubshaderIndex;
            private Func<Shader, int, ShaderTagId, ShaderTagId> m_FindTagValue;

            internal Subshader(ShaderData data, int subshaderIndex, Type type = Type.Runtime)
            {
                m_Data = data;
                m_SubshaderIndex = subshaderIndex;

                m_FindTagValue = type == Type.Serialized ? s_SerializedFindTagValue : s_RuntimeFindTagValue;
            }

            internal Shader SourceShader => m_Data.SourceShader;

            public int PassCount => ShaderUtil.GetShaderTotalPassCount(m_Data.SourceShader, m_SubshaderIndex);

            public int LevelOfDetail => ShaderUtil.GetSubshaderLOD(m_Data.SourceShader, m_SubshaderIndex);

            private static Func<Shader, int, ShaderTagId, ShaderTagId> s_SerializedFindTagValue = (Shader sourceShader, int subshaderIndex, ShaderTagId tag) =>
                new ShaderTagId { id = ShaderUtil.FindSerializedSubShaderTagValue(sourceShader, subshaderIndex, tag.id) };

            private static Func<Shader, int, ShaderTagId, ShaderTagId> s_RuntimeFindTagValue = (Shader sourceShader, int subshaderIndex, ShaderTagId tag) =>
            {
                if (subshaderIndex < 0 || subshaderIndex >= sourceShader.subshaderCount)
                {
                    Debug.LogErrorFormat("Subshader index is incorrect: {0}, shader {1} has {2} subshaders.", subshaderIndex, sourceShader, sourceShader.subshaderCount);
                    return ShaderTagId.none;
                }

                return sourceShader.FindSubshaderTagValue(subshaderIndex, tag);
            };

            public ShaderTagId FindTagValue(ShaderTagId tag) => m_FindTagValue(SourceShader, m_SubshaderIndex, tag);

            public Pass GetPass(int passIndex)
            {
                if (passIndex < 0 || passIndex >= PassCount)
                {
                    Debug.LogErrorFormat("Pass index is incorrect: {0}, shader {1} has {2} passes.", passIndex, SourceShader, PassCount);
                    return null;
                }

                return new Pass(this, passIndex);
            }
        }

        public class Pass
        {
            Subshader m_Subshader;
            int m_PassIndex;

            internal Pass(Subshader subshader, int passIndex)
            {
                m_Subshader = subshader;
                m_PassIndex = passIndex;
            }

            internal Shader SourceShader { get { return m_Subshader.m_Data.SourceShader; } }
            internal int SubshaderIndex { get { return m_Subshader.m_SubshaderIndex; } }

            public string SourceCode { get { return ShaderUtil.GetShaderPassSourceCode(SourceShader, SubshaderIndex, m_PassIndex); } }
            public string Name { get { return ShaderUtil.GetShaderPassName(SourceShader, SubshaderIndex, m_PassIndex); } }
            public bool IsGrabPass { get { return ShaderUtil.IsGrabPass(SourceShader, SubshaderIndex, m_PassIndex); } }

            public UnityEngine.Rendering.ShaderTagId FindTagValue(UnityEngine.Rendering.ShaderTagId tagName)
            {
                int id = ShaderUtil.FindPassTagValue(SourceShader, m_Subshader.m_SubshaderIndex, m_PassIndex, tagName.id);
                return new UnityEngine.Rendering.ShaderTagId { id = id };
            }

            public bool HasShaderStage(ShaderType shaderType)
            {
                return ShaderUtil.PassHasShaderStage(SourceShader, SubshaderIndex, m_PassIndex, shaderType);
            }

            internal static GraphicsTier kNoGraphicsTier = (GraphicsTier)(-1);

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget)
            {
                return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, false);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, bool forExternalTool)
            {
                var platformKeywords = ShaderUtil.GetShaderPlatformKeywordsForBuildTarget(shaderCompilerPlatform, buildTarget, kNoGraphicsTier);
                return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier, forExternalTool);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier)
            {
                return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, tier, false);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier, bool forExternalTool)
            {
                var platformKeywords = ShaderUtil.GetShaderPlatformKeywordsForBuildTarget(shaderCompilerPlatform, buildTarget, tier);
                return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier, forExternalTool);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords)
            {
                return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, platformKeywords, false);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, bool forExternalTool)
            {
                return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier, forExternalTool);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, GraphicsTier tier)
            {
                return CompileVariant(shaderType, keywords, shaderCompilerPlatform, buildTarget, platformKeywords, tier, false);
            }

            public VariantCompileInfo CompileVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, GraphicsTier tier, bool forExternalTool)
            {
                return ShaderUtil.CompileShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier, forExternalTool);
            }

            public PreprocessedVariant PreprocessVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, bool stripLineDirectives)
            {
                var platformKeywords = ShaderUtil.GetShaderPlatformKeywordsForBuildTarget(shaderCompilerPlatform, buildTarget, kNoGraphicsTier);
                return ShaderUtil.PreprocessShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier, stripLineDirectives);
            }

            public PreprocessedVariant PreprocessVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, GraphicsTier tier, bool stripLineDirectives)
            {
                var platformKeywords = ShaderUtil.GetShaderPlatformKeywordsForBuildTarget(shaderCompilerPlatform, buildTarget, tier);
                return ShaderUtil.PreprocessShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier, stripLineDirectives);
            }

            public PreprocessedVariant PreprocessVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, bool stripLineDirectives)
            {
                return ShaderUtil.PreprocessShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, kNoGraphicsTier, stripLineDirectives);
            }

            public PreprocessedVariant PreprocessVariant(ShaderType shaderType, string[] keywords,
                ShaderCompilerPlatform shaderCompilerPlatform, BuildTarget buildTarget, BuiltinShaderDefine[] platformKeywords, GraphicsTier tier, bool stripLineDirectives)
            {
                return ShaderUtil.PreprocessShaderVariant(SourceShader, SubshaderIndex, m_PassIndex, shaderType, platformKeywords, keywords, shaderCompilerPlatform, buildTarget, tier, stripLineDirectives);
            }
        }

        public int ActiveSubshaderIndex => ShaderUtil.GetShaderActiveSubshaderIndex(SourceShader);
        public int SubshaderCount => ShaderUtil.GetShaderSubshaderCount(SourceShader);
        public int SerializedSubshaderCount => ShaderUtil.GetShaderSerializedSubshaderCount(SourceShader);

        public Shader SourceShader { get; private set; }

        public Subshader ActiveSubshader
        {
            get
            {
                var index = ActiveSubshaderIndex;
                if (index < 0 || index >= SubshaderCount)
                    return null;

                return new Subshader(this, index);
            }
        }

        internal ShaderData(Shader sourceShader)
        {
            Assert.IsNotNull(sourceShader);
            this.SourceShader = sourceShader;
        }

        public Subshader GetSubshader(int index)
        {
            if (index < 0 || index >= SubshaderCount)
            {
                Debug.LogErrorFormat("Subshader index is incorrect: {0}, shader {1} has {2} subshaders.", index, SourceShader, SubshaderCount);
                return null;
            }

            return new Subshader(this, index);
        }

        public Subshader GetSerializedSubshader(int index)
        {
            if (index < 0 || index >= SerializedSubshaderCount)
            {
                Debug.LogErrorFormat("Serialized Subshader index is incorrect: {0}, shader {1} has {2} serialized subshaders.", index, SourceShader, SerializedSubshaderCount);
                return null;
            }

            return new Subshader(this, index, Subshader.Type.Serialized);
        }

        public struct PreprocessedVariant
        {
            public bool Success { get; }
            public ShaderMessage[] Messages { get; }
            public string PreprocessedCode { get; }
        }

        //
        // Experimental reflection information and raw compiled data access.  Used for Tiny shader export.
        //
        public struct VariantCompileInfo
        {
            public bool Success { get; }
            public ShaderMessage[] Messages { get; }

            public byte[] ShaderData { get; }

            public VertexAttribute[] Attributes { get; }
            public ConstantBufferInfo[] ConstantBuffers { get; }
            public TextureBindingInfo[] TextureBindings { get; }
        }

        [DebuggerDisplay("cbuffer {Name} ({Size} bytes)")]
        public struct ConstantBufferInfo
        {
            public string Name { get; }
            public int Size { get; }
            public ConstantInfo[] Fields { get; }
        }

        [DebuggerDisplay("{ConstantType} {Name} ({DataType} {Columns}x{Rows})")]
        public struct ConstantInfo
        {
            public string Name { get; }
            public int Index { get; }
            public ShaderConstantType ConstantType { get; }
            public ShaderParamType DataType { get; }
            public int Rows { get; }
            public int Columns { get; }
            public int ArraySize { get; }

            // only relevant if ConstantType == Struct
            public int StructSize { get; }
            public ConstantInfo[] StructFields { get; }
        }

        [DebuggerDisplay("{Dim} {Name}")]
        public struct TextureBindingInfo
        {
            public string Name { get; }
            public int Index { get; }
            public int SamplerIndex { get; }
            public bool Multisampled { get; }
            public int ArraySize { get; }
            public TextureDimension Dim { get; }
        }
    }

    public partial class ShaderUtil
    {
        public static ShaderData GetShaderData(Shader shader)
        {
            return new ShaderData(shader);
        }

        // GetShaderMessageCount includes warnings, this function filters them out
        public static bool ShaderHasError(Shader shader)
        {
            FetchCachedMessages(shader);
            var errors = GetShaderMessages(shader);
            return errors.Any(x => x.severity == ShaderCompilerMessageSeverity.Error);
        }

        public static bool ShaderHasWarnings(Shader shader)
        {
            FetchCachedMessages(shader);
            var errors = GetShaderMessages(shader);
            return errors.Any(x => x.severity == ShaderCompilerMessageSeverity.Warning);
        }

        internal static extern bool PassHasShaderStage(Shader s, int subshaderIndex, int passIndex, ShaderType shaderType);

        internal static bool MaterialsUseInstancingShader(SerializedProperty materialsArray)
        {
            if (materialsArray.hasMultipleDifferentValues)
                return false;
            for (int i = 0; i < materialsArray.arraySize; ++i)
            {
                var material = materialsArray.GetArrayElementAtIndex(i).objectReferenceValue as Material;
                if (material != null && material.enableInstancing && material.shader != null && HasInstancing(material.shader))
                    return true;
            }
            return false;
        }
    }
}
