//Stylized Water 2
//Staggart Creations (http://staggart.xyz)
//Copyright protected under Unity Asset Store EULA

#if SWS_DEV
#define ENABLE_SHADER_STRIPPING_LOG
#endif

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using Debug = UnityEngine.Debug;
using Random = UnityEngine.Random;
#if URP
using UnityEngine.Rendering.Universal;
#endif

namespace StylizedWater2
{
    public static class ShaderConfigurator
    {
        public static class Fog
        {
            public enum Assets
            {
                None,
                [InspectorName("Default Unity")]
                UnityFog,
                #if SWS_DEV
                [InspectorName("Colorful Sky")] //Unreleased
                Colorful,
                #endif
                [InspectorName("SC Post Effects (Screen-Space Fog)")]
                SCPostEffects,
                [InspectorName("COZY: Stylized Weather (v1+2+3)")]
                COZY,
                [InspectorName("Buto (2021 + 2022)")]
                Buto,
                AtmosphericHeightFog,
                [InspectorName("Enviro - Sky & Weather")]
                Enviro,
                [InspectorName("Enviro 3 - Sky & Weather")]
                Enviro3,
                [InspectorName("Azure[Sky] Dynamic Skybox")]
                Azure
            }

            [Serializable]
            public class Integration
            {
                public string name;
                public Assets asset;
                public string libraryGUID;
                public string url;
                public bool underwaterCompatible = true;
                public string thumbnailBytes;
                public Texture2D thumbnail;
                public bool installed;

                public Integration(string name, Assets asset, bool uwc, string guid, string url, string thumbData)
                {
                    this.name = name;
                    this.asset = asset;
                    this.underwaterCompatible = uwc;
                    this.libraryGUID = guid;
                    this.url = url;
                    this.thumbnailBytes = thumbData;
                    if (thumbData == string.Empty) this.thumbnail = EditorGUIUtility.IconContent("d_UnityLogo").image as Texture2D;
                    else this.thumbnail = UI.CreateIcon(thumbData) as Texture2D;
                }
            }

            private static Integration[] _Integrations;
            public static Integration[] Integrations
            {
                get
                {
                    if (_Integrations == null) _Integrations = GetAvailableIntegrations();
                    return _Integrations;
                }
            }

            private static Integration[] GetAvailableIntegrations()
            {
                Integration[] integrationsArray = new[]
                {
                    new Integration("None", Assets.None, true, "", "", ""),
                    new Integration("Default Unity", Assets.UnityFog, true, "", "", ""),
                    #if SWS_DEV
                    new Integration("Colorful Sky", Assets.Colorful, true, "4a93911cc8864a3ba9f264d522fdfa3a", "", "[encoded data, 39208 characters]"),
                    #endif
                    new Integration("SC Post Effects (Screen-Space Fog)", Assets.SCPostEffects, true, "a66e4b0e5c776404e9a091531ccac3f2", "https://assetstore.unity.com/packages/slug/108753",
                        "[encoded data, 10220 characters]"),
                    new Integration("COZY: Stylized Weather (v1/2/3)", Assets.COZY, false, "e9bc566e199a97947a3e600f2563fe85", "https://assetstore.unity.com/packages/slug/230502",
                        "[encoded data, 26592 characters]"),
                    new Integration("Buto 2021 & 2022", Assets.Buto, true, "ada48e5159bb1a5469288f9c75ca4629", "https://assetstore.unity.com/packages/slug/258881",
                        "[encoded data, 25452 characters]"),
                    new Integration("Atmospheric Height Fog", Assets.AtmosphericHeightFog, false, "8db8edf9bba0e9d48998019ca6c2f9ff", "https://assetstore.unity.com/packages/slug/143825",
                        "[encoded data, 50292 characters]"),
                    new Integration("Enviro - Sky & Weather", Assets.Enviro, true, "221026906cc779e4b9ab486bbbd30032", "https://assetstore.unity.com/packages/slug/236601",
                        "[encoded data, 31560 characters]"),
                    new Integration("Enviro 3 - Sky & Weather", Assets.Enviro3, true, "8db9bd7b531f93d46ae2cb21180a00a8", "https://assetstore.unity.com/packages/slug/236601",
                        "[encoded data, 31560 characters]"),
                    new Integration("Azure[Sky] Dynamic Skybox", Assets.Azure, true, "5da6209b193d6eb49b25c88c861e52fa", "https://assetstore.unity.com/packages/slug/36050",
                        "[encoded data, 26036 characters]"),
                };

                for (int i = 0; i < integrationsArray.Length; i++)
                {
                    integrationsArray[i].installed = IsFogLibraryPresent(integrationsArray[i]);
                }

                return integrationsArray;
            }

            public static Integration GetIntegration(Assets asset)
            {
                for (int i = 0; i < Integrations.Length; i++)
                {
                    if (Integrations[i].asset == asset) return Integrations[i];
                }

                return null;
            }

            public static bool IsFogLibraryPresent(Integration integration)
            {
                if (integration.asset == Assets.None || integration.asset == Assets.UnityFog) return true;

                string path = AssetDatabase.GUIDToAssetPath(integration.libraryGUID);
                
                return path != string.Empty && AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset));
            }

            public static Integration GetFirstInstalled()
            {
                for (int i = 0; i < Integrations.Length; i++)
                {
                    //Always installed anyway
                    if (Integrations[i].asset == Assets.None || Integrations[i].asset == Assets.UnityFog) continue;
                    
                    #if SWS_DEV
                    //Gets in the way of testing and using default Unity fog
                    if(Integrations[i].asset == Assets.SCPostEffects || Integrations[i].asset == Assets.Colorful) continue;
                    #endif
                    
                    if (IsFogLibraryPresent(Integrations[i]))
                    {
                        return Integrations[i];
                    }
                }

                //No third-party assets installed, default to Unity fog
                return GetIntegration(Assets.UnityFog);
            }
        }

        public class Keywords
        {
            public const string UNDERWATER_ENABLED = "UNDERWATER_ENABLED";
            public const string DYNAMIC_EFFECTS_ENABLED = "DYNAMIC_EFFECTS_ENABLED";
        }

        public static class TemplateParser
        {
            //Converts relative include paths such as (../../Libraries/File.hlsl) to an absolute path
            //Supports the source file being part of a package
            public static string RelativeToAbsoluteIncludePath(string filePath, string relativePath)
            {
                string fileDir = Path.GetDirectoryName(filePath);

                //Count how many folders should be traversed up
                int levels = relativePath.Split(new[]
                {
                    ".."
                }, StringSplitOptions.None).Length - 1;

                string traveledPath = fileDir;
                if (levels > 0)
                {
                    for (int i = 0; i < levels; i++)
                    {
                        //Remove the number of needed sub-directories needed to reach the destination
                        int strimStart = traveledPath.LastIndexOf(Path.DirectorySeparatorChar);
                        traveledPath = traveledPath.Remove(strimStart);
                    }
                }

                //The directory without the "up" navigators
                string relativeFolder = relativePath.Replace("../", string.Empty);

                //Concatenate them together
                string absolutePath = traveledPath + "/" + relativeFolder;

                //Convert back- to forward slashes
                absolutePath = absolutePath.Replace("\\", "/");

                return absolutePath;
            }

            public const string TESSELLATION_NAME_SUFFIX = " (Tessellation)";

            public static string CreateShaderCode(string templatePath, WaterShaderImporter importer, bool tessellation = false)
            {
                string[] lines = File.ReadAllLines(templatePath);

                CreateShaderCode(templatePath, ref lines, importer, tessellation);

                string shaderlab = String.Join(Environment.NewLine, lines);

                return shaderlab;
            }

            public static string CreateShaderCode(string templatePath, ref string[] lines, WaterShaderImporter importer, bool tessellation = false)
            {
                //Extension installation states
                var underwaterInstalled = StylizedWaterEditor.UnderwaterRenderingInstalled();
                var dynamicEffectsInstalled = StylizedWaterEditor.DynamicEffectsInstalled();

                Fog.Integration fogIntegration = importer.settings.autoIntegration ? Fog.GetFirstInstalled() : Fog.GetIntegration(importer.settings.fogIntegration);

                AssetInfo.VersionChecking.CheckUnityVersion();
                
                //Shader name
                string prefix = importer.settings.hidden ? "Hidden/" : string.Empty;
                string suffix = tessellation ? TESSELLATION_NAME_SUFFIX : string.Empty;
                string shaderName = $"{prefix}{AssetInfo.ASSET_NAME}/{importer.settings.shaderName}";

                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < lines.Length; i++)
                {
                    //Ignore blank lines and comments for analysis
                    if (string.IsNullOrWhiteSpace(lines[i]) || lines[i].StartsWith("//"))
                    {
                        sb.AppendLine(lines[i]);
                        continue;
                    }

                    //First non-space character
                    int indent = System.Text.RegularExpressions.Regex.Match(lines[i], "[^-\\s]").Index;

                    string whitespace = lines[i].Replace(lines[i].Substring(indent), "");

                    //AppendLine using previous line's white spacing
                    void AddLine(string source)
                    {
                        sb.AppendLine(source.Insert(0, whitespace));
                    }

                    //Remove whitespaces
                    string line = lines[i].Remove(0, indent);

                    bool Matches(string source) { return string.CompareOrdinal(source, line) == 0; }

                    if (Matches("%asset_version%"))
                    {
                        AddLine($"//Asset version {AssetInfo.INSTALLED_VERSION}");
                        continue;
                    }

                    if (Matches("%compiler_version%"))
                    {
                        AddLine($"//Shader generator version: {new Version(AssetInfo.SHADER_GENERATOR_VERSION_MAJOR, AssetInfo.SHADER_GENERATOR_MINOR, AssetInfo.SHADER_GENERATOR_PATCH)}");
                        continue;
                    }
                    
                    if (Matches("%unity_version%"))
                    {
                        AddLine($"//Unity version: {AssetInfo.VersionChecking.GetUnityVersion()}");
                        continue;
                    }

                    if (Matches("%fog_integration%"))
                    {
                        AddLine($"//Fog integration: {fogIntegration.asset}");
                        continue;
                    }
                    
                    if (Matches("%shader_name%"))
                    {
                        AddLine($"Shader \"{shaderName}{suffix}\"");
                        continue;
                    }

                    if (Matches("%custom_directives%"))
                    {
                        foreach (WaterShaderImporter.Directive directive in importer.settings.customIncludeDirectives)
                        {
                            if(directive.enabled == false) continue;
                            
                            string directivePrefix = string.Empty;

                            switch (directive.type)
                            {
                                case WaterShaderImporter.Directive.Type.define:
                                    directivePrefix = "#define ";
                                    break;
                                case WaterShaderImporter.Directive.Type.include:
                                    directivePrefix = "#include ";
                                    break;
                                case WaterShaderImporter.Directive.Type.pragma:
                                    directivePrefix = "#pragma ";
                                    break;
                            }

                            if (directive.value != string.Empty) AddLine($"{directivePrefix}{directive.value}");
                        }
                        continue;
                    }

                    if (Matches("%pragma_target%"))
                    {
                        if (tessellation)
                        {
                            AddLine("#pragma target 4.6");
                        }
                        else
                        {
                            AddLine("#pragma target 3.0");
                        }

                        continue;
                    }

                    if (Matches("%pragma_renderers%"))
                    {
                        if (tessellation)
                        {
                            AddLine("#pragma exclude_renderers gles");
                        }
                        
                        continue;
                    }

                    if (line.StartsWith("Fallback"))
                    {
                        if (tessellation)
                        {
                            //Fallback to non-tessellation variant (with without suffix)
                            AddLine($"Fallback \"{shaderName}\"");
                            //Test, disable fallback
                            //AddLine(line);
                        }
                        else
                        {
                            //Leave as is
                            AddLine(line);
                        }

                        continue;
                    }

                    if (importer.settings.type == WaterShaderImporter.WaterShaderSettings.ShaderType.WaterSurface)
                    {
                        if (Matches("%tessellation_properties%"))
                        {
                            if (tessellation)
                            {
                                AddLine("_TessValue(\"Max subdivisions\", Range(1, 64)) = 16");
                                AddLine("_TessMin(\"Start Distance\", Float) = 0");
                                AddLine("_TessMax(\"End Distance\", Float) = 15");
                            }

                            continue;
                        }

                        if (Matches("%tessellation_directives%"))
                        {
                            if (tessellation)
                            {
                                AddLine("#define TESSELLATION_ON");
                                AddLine("#pragma require tessellation tessHW");
                                AddLine("#pragma hull Hull");
                                AddLine("#pragma domain Domain");
                            }

                            continue;
                        }
                        
                        if (line.Contains("%render_queue_offset%"))
                        {
                            int offset = 0;

                            switch (fogIntegration.asset)
                            {
                                case Fog.Assets.COZY: offset = 2;
                                    break;
                                //case Fog.Assets.AtmosphericHeightFog : offset = 2; //Should actually render after the fog sphere, but asset inherently relies on double fog shading it seems?
                                    //break;
                                default: offset = 0;
                                    break;
                            }
                            
                            line = line.Replace("%render_queue_offset%", offset.ToString());
                            AddLine(line);

                            continue;
                        }

                        if (Matches("%stencil%"))
                        {
                            if (importer.settings.fogIntegration == Fog.Assets.COZY)
                            {
                                AddLine("Stencil { Ref 221 Comp Always Pass Replace }");
                            }

                            continue;
                        }
                        
                        if (Matches("%multi_compile_light_cookies%"))
                        {
                            if (importer.settings.lightCookies)
                            {
                                #if UNITY_2021_2_OR_NEWER
                                AddLine("#pragma multi_compile_fragment _ _LIGHT_COOKIES");
                                #endif
                            }
                            
                            continue;
                        }
                    }
                    
                    if (Matches("%multi_compile_shadows%"))
                    {
                        #if !UNITY_2021_1_OR_NEWER
                        AddLine("#pragma multi_compile _ _MAIN_LIGHT_SHADOWS");
                        AddLine("#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE");
                        #else
                        AddLine("#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN");
                        #endif

                        continue;
                    }

                    

                    if (Matches("%multi_compile underwater rendering%"))
                    {
                        if (underwaterInstalled)
                        {
                            AddLine($"#pragma multi_compile_fragment _ {Keywords.UNDERWATER_ENABLED}");
                        }

                        continue;
                    }
                    
                    if (Matches("%multi_compile dynamic effects%"))
                    {
                        if (dynamicEffectsInstalled)
                        {
                            AddLine($"#pragma multi_compile _ {Keywords.DYNAMIC_EFFECTS_ENABLED}");
                        }

                        continue;
                    }
                    
                    //Legacy, strip line
                    if (Matches("%multi_compile surface modifiers%")) continue;
                    if (Matches("%multi_compile wave sim%")) continue;

                    if (line.StartsWith("#include "))
                    {
                        string includePath = line.Replace("#include ", string.Empty);
                        //Remove parenthesis
                        includePath = includePath.Replace("\"", string.Empty);

                        importer.RegisterDependency(includePath);
                    }

                    if (importer.settings.type == WaterShaderImporter.WaterShaderSettings.ShaderType.WaterSurface)
                    {
                        if (Matches("%define_fog_integration%"))
                        {
                            AddLine($"#define {fogIntegration.asset.ToString()}");
                            
                            if (fogIntegration.asset == Fog.Assets.UnityFog)
                            {
                                AddLine("#pragma multi_compile_fog");
                            }
                            
                            continue;
                        }

                        /* include FogLibrary */
                        if (Matches("%include_fog_integration_library%"))
                        {
                            //Default until otherwise valid
                            line = string.Empty;

                            if (fogIntegration.asset != Fog.Assets.None && fogIntegration.asset != Fog.Assets.UnityFog)
                            {
                                string includePath = AssetDatabase.GUIDToAssetPath(fogIntegration.libraryGUID);

                                importer.RegisterDependency(includePath);

                                //Not found error
                                if (includePath == string.Empty)
                                {
                                    if (EditorUtility.DisplayDialog(AssetInfo.ASSET_NAME,
                                        fogIntegration.name + " fog shader library could not be found with the GUID \"" + fogIntegration.libraryGUID + "\".\n\n" +
                                        "This means it was changed by the author (rare!), you deleted the \".meta\" file at some point, or the asset simply isn't installed.", "Ok"))
                                    {

                                    }
                                }
                                else
                                {
                                    line = $"#include_with_pragmas \"{includePath}\"";

                                    AddLine(line);
                                    continue;
                                }
                            }
                        }
                    }

                    //Shaders created using "ShaderUtil.CreateShaderAsset" don't exist in a literal sense. Hence any relative file paths are invalid
                    //Convert them to absolute file paths
                    //Bonus: moving a shader file (or its folder) triggers it to re-import, thus always keeping the file path up-to-date
                    if (line.StartsWith("#include_library"))
                    {
                        string relativePath = line.Replace("#include_library ", string.Empty);
                        //Remove parenthesis
                        relativePath = relativePath.Replace("\"", string.Empty);

                        string includePath = RelativeToAbsoluteIncludePath(templatePath, relativePath);

                        #if UNITY_2023_2_OR_NEWER
                        line = $"#include_with_pragmas \"{includePath}\"";
                        #else
                        line = $"#include \"{includePath}\"";
                        #endif

                        importer.RegisterDependency(includePath);

                        AddLine(line);
                        continue;
                    }

                    //Insert whitespace back in
                    line = line.Insert(0, whitespace);

                    //Nothing special, keep whatever line this is
                    sb.AppendLine(line);
                }

                //Convert to separate lines again
                lines = sb.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);

                //Convert to single string, respecting line breaks and spacing.
                return String.Join(Environment.NewLine, lines);
            }
        }
        
        public static ShaderMessage[] GetErrorMessages(Shader shader)
        {
            ShaderMessage[] messages = GetWarningsAndErrors(shader);

            if (messages != null)
            {
                //Filter for errors
                messages = messages.Where(x => x.severity == ShaderCompilerMessageSeverity.Error).ToArray();
            }
            
            return messages;
        }
        
        public static ShaderMessage[] GetWarningsAndErrors(Shader shader)
        {
            ShaderMessage[] messages = null;

            if (shader)
            {
                int n = ShaderUtil.GetShaderMessageCount(shader);

                if (n < 1) return messages;

                messages = ShaderUtil.GetShaderMessages(shader);

                //Filter for errors
                //messages = messages.Where(x => x.severity == ShaderCompilerMessageSeverity.Error).ToArray();
            }
            
            return messages;
        }

        #if URP
        //Strips keywords from the shader for features belonging to newer URP versions.
        private class KeywordStripper : IPreprocessShaders
        {
            private const string LOG_FILEPATH = "Logs/Stylized Water 2 Compilation.log";

		    #if ENABLE_SHADER_STRIPPING_LOG
            private System.Diagnostics.Stopwatch m_stripTimer = new System.Diagnostics.Stopwatch();
            #endif
            
            private List<ShaderKeyword> StrippedKeywords = new List<ShaderKeyword>();

            //URP 10+ (2020.3)
            private readonly ShaderKeyword _ADDITIONAL_LIGHT_SHADOWS = new ShaderKeyword("_ADDITIONAL_LIGHT_SHADOWS");

            //URP 12+ (2021.2)
            private readonly ShaderKeyword _REFLECTION_PROBE_BLENDING = new ShaderKeyword("_REFLECTION_PROBE_BLENDING");
            private readonly ShaderKeyword _REFLECTION_PROBE_BOX_PROJECTION = new ShaderKeyword("_REFLECTION_PROBE_BOX_PROJECTION");
            private readonly ShaderKeyword DYNAMICLIGHTMAP_ON = new ShaderKeyword("DYNAMICLIGHTMAP_ON");
            private readonly ShaderKeyword DEBUG_DISPLAY = new ShaderKeyword("DEBUG_DISPLAY");
            private readonly ShaderKeyword _CLUSTERED_RENDERING = new ShaderKeyword("_CLUSTERED_RENDERING");
            private readonly ShaderKeyword _LIGHT_LAYERS = new ShaderKeyword("_LIGHT_LAYERS");
            private readonly ShaderKeyword _LIGHT_COOKIES = new ShaderKeyword("_LIGHT_COOKIES");

            //URP 14+ (2022.2)
            private readonly ShaderKeyword _FORWARD_PLUS = new ShaderKeyword("_FORWARD_PLUS");
            private readonly ShaderKeyword _WRITE_RENDERING_LAYERS = new ShaderKeyword("_WRITE_RENDERING_LAYERS");
            
            //URP 15+ (2023.1)
            private readonly ShaderKeyword EVALUATE_SH_MIXED = new ShaderKeyword("EVALUATE_SH_MIXED");
            private readonly ShaderKeyword EVALUATE_SH_VERTEX = new ShaderKeyword("EVALUATE_SH_VERTEX");

            //Keep in sync with actual pass name in shader!
            private const string DISPLACEMENT_PASS_NAME = "Depth or Displacement";
            private readonly bool displacementPrePassEnabled;

            #if SWS_DEV
            [MenuItem("SWS/Debug/DisplacementPrePassEnabled")]
            #endif
            private static bool DisplacementPrePassEnabled()
            {
                bool state = false;
                
                //Check if the displacement pre-pass is enabled anywhere
                for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++)
                {
                    UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i];

                    ScriptableRendererData[] renderers = PipelineUtilities.GetRenderDataList(pipeline);

                    for (int j = 0; j < renderers.Length; j++)
                    {
                        StylizedWaterRenderFeature renderFeature = (StylizedWaterRenderFeature)PipelineUtilities.GetRenderFeature<StylizedWaterRenderFeature>(renderers[j]);

                        if (renderFeature)
                        {
                            state |= renderFeature.displacementPrePassSettings.enable;
                            
                            #if SWS_DEV
                            //Debug.Log($"{renderers[j].name}. Enable?:{renderFeature.displacementPrePassSettings.enable}");
                            #endif
                        }
                    }
                }

                #if SWS_DEV
                //Debug.Log("Displacement pre-pass enabled somewhere: " + state);
                #endif

                return state;
            }
            
            //Note: Constructor is called once, when building starts
            public KeywordStripper()
            {
                if (PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget)[0] == GraphicsDeviceType.OpenGLES3)
                {
                    if (UniversalRenderPipeline.asset.msaaSampleCount > 1)
                    {
                        Debug.LogWarning("[Stylized Water 2] You are deploying a build using the OpenGLES 3.0 graphics API with MSAA enabled (in your URP pipeline asset). Due to a bug in some graphics chips, transparent materials (including the water) will not render. " +
                                         "Disable MSAA, or use the Vulkan graphics API", UniversalRenderPipeline.asset);
                    }
                }

                //Check if the displacement pre-pass is enabled anywhere
                displacementPrePassEnabled = DisplacementPrePassEnabled();
                
                StrippedKeywords.Clear();

                if (!displacementPrePassEnabled)
                {
                    StrippedKeywords.Add(new ShaderKeyword(DisplacementPrePass.KEYWORD));
                }
                
                //Note: Keywords for extensions are only injected through the shader generator. Hence they don't need to be stripped
                
                #if !UNITY_2020_2_OR_NEWER //URP 10+
                StrippedKeywords.Add(_ADDITIONAL_LIGHT_SHADOWS);
                #endif

                #if !UNITY_2021_2_OR_NEWER //URP 12+
                StrippedKeywords.Add(_REFLECTION_PROBE_BLENDING);
                StrippedKeywords.Add(_REFLECTION_PROBE_BOX_PROJECTION);
                StrippedKeywords.Add(DYNAMICLIGHTMAP_ON);
                StrippedKeywords.Add(DEBUG_DISPLAY);
                StrippedKeywords.Add(_CLUSTERED_RENDERING);
                StrippedKeywords.Add(_LIGHT_LAYERS);
                StrippedKeywords.Add(_LIGHT_COOKIES);
                #endif

                #if !UNITY_2022_2_OR_NEWER //URP 14+
                StrippedKeywords.Add(_FORWARD_PLUS);
                StrippedKeywords.Add(_WRITE_RENDERING_LAYERS);

                StrippedKeywords.Add(EVALUATE_SH_MIXED);
                StrippedKeywords.Add(EVALUATE_SH_VERTEX);
                #endif

                LogInitialization();
            }
            
            public int callbackOrder => 0;

            public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> compilerDataList)
            {
			    #if URP
                if (UniversalRenderPipeline.asset == null || compilerDataList == null || compilerDataList.Count == 0) return;

                //Only run for specific shaders
                if (shader.name.Contains("Stylized Water 2") == false) return;

                LogStart(shader, snippet, compilerDataList);

                var inputShaderVariantCount = compilerDataList.Count;
                for (int i = 0; i < inputShaderVariantCount;)
                {
                    //If any of the excluded keywords are enabled in this variant, strip it
                    bool removeInput = StripUnused(shader, compilerDataList[i], snippet);

                    // Remove at swap back
                    if (removeInput)
                        compilerDataList[i] = compilerDataList[--inputShaderVariantCount];
                    else
                        ++i;
                }

                if (compilerDataList is List<ShaderCompilerData> inputDataList)
                {
                    inputDataList.RemoveRange(inputShaderVariantCount, inputDataList.Count - inputShaderVariantCount);
                }
                else
                {
                    for (int i = compilerDataList.Count - 1; i >= inputShaderVariantCount; --i)
                        compilerDataList.RemoveAt(i);
                }

                LogStrippingEnd(compilerDataList.Count);
			    #endif
            }

            private string GetKeywordName(Shader shader, ShaderKeyword keyword)
            {
                #if UNITY_2021_2_OR_NEWER
                return keyword.name;
			    #else
                return ShaderKeyword.GetKeywordName(shader, keyword);
			    #endif
            }

            private bool StripUnused(Shader shader, ShaderCompilerData compilerData, ShaderSnippetData snippet)
            {
                if (StripPass(shader, snippet))
                {
                    return true;
                }
                
                foreach (var keyword in StrippedKeywords)
                {
                    if (StripKeyword(shader, keyword, compilerData, snippet))
                    {
                        return true;
                    }
                }

                return false;
            }

            private bool StripPass(Shader shader, ShaderSnippetData snippet)
            {
                if (displacementPrePassEnabled == false && snippet.passName == DISPLACEMENT_PASS_NAME)
                {
                    Log($"- Stripped Pass {snippet.passName} ({shader.name}) (Stage: {snippet.shaderType})");
                    
                    return true;
                }

                return false;
            }
            
            private bool StripKeyword(Shader shader, ShaderKeyword keyword, ShaderCompilerData compilerData, ShaderSnippetData snippet)
            {
                if (compilerData.shaderKeywordSet.IsEnabled(keyword))
                {
                    LogStripping(shader, keyword, snippet);
                    return true;
                }

                return false;
            }

            #region Logging
            struct StrippingLog
            {
                public Shader shader;
                public ShaderKeyword keyword;
                public string passName;
                public ShaderType shaderType;
            }

            private void LogInitialization()
            {
                #if ENABLE_SHADER_STRIPPING_LOG
			    //Clear log file first
			    File.WriteAllLines(LOG_FILEPATH, new string[] {});
                
                Log("KeywordStripper initialized...", true);
                
                Log(string.Empty);

                Log($"Displacement Pre-pass enabled in build: {displacementPrePassEnabled}", true);

                Log(string.Empty);
                
                #if UNITY_2021_2_OR_NEWER
                for (int i = 0; i < StrippedKeywords.Count; i++)
                {
                    Log($"• {StrippedKeywords[i].name} keyword to be stripped");
                }
                #endif
                
                Log($"{StrippedKeywords.Count} total keywords to be stripped");
                #endif
            }

            private void LogStart(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> compilerDataList)
            {
                #if ENABLE_SHADER_STRIPPING_LOG
                m_stripTimer.Start();
                
                var text = $"OnProcessShader running for {shader.name}. (Pass: {snippet.passName}) (Stage: {snippet.shaderType}). Num variants: {compilerDataList.Count}";

                Log(text, true);
                #endif
            }

            StrippingLog prevLog;

            private void LogStripping(Shader shader, ShaderKeyword keyword, ShaderSnippetData snippet)
            {
                #if ENABLE_SHADER_STRIPPING_LOG
                
                //Try to avoid spamming the log with duplicates, this otherwise slows down compilation to a crawl
                if (prevLog.keyword.index == keyword.index && prevLog.shader == shader && prevLog.passName == snippet.passName && prevLog.shaderType == snippet.shaderType)
                {
                    //File.AppendAllText(LOG_FILEPATH, "- Skipping log!\n" );
                    return;
                }

                prevLog.shader = shader;
                prevLog.keyword = keyword;
                prevLog.passName = snippet.passName;
                prevLog.shaderType = snippet.shaderType;
                
                var text = $"- Stripped {GetKeywordName(shader, keyword)} ({shader.name}) variant. (Pass {snippet.passName}) (Stage: {snippet.shaderType})";

			    Log(text);
                #endif
            }

            private void LogStrippingEnd(int count)
            {
                #if ENABLE_SHADER_STRIPPING_LOG
                m_stripTimer.Stop();
                System.TimeSpan stripTimespan = m_stripTimer.Elapsed;
                
                var text = $"Stripping took {stripTimespan.Minutes}m{stripTimespan.Seconds}s ({stripTimespan.Milliseconds}ms). Remaining variants to compile: {count}";
                
			    Log(text);

                m_stripTimer.Reset();
                #endif
            }

            private void Log(string text, bool newLine = false)
            {
			    #if ENABLE_SHADER_STRIPPING_LOG
                File.AppendAllText(LOG_FILEPATH, (newLine ? "\n" : "") + text + "\n");
			    #endif
            }
            #endregion
        }
        #endif
    }
}