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

using System;
using System.Collections.Generic;
using System.Net;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
using UnityEngine;

namespace StylizedWater2
{
    public class AssetInfo
    {
        private const string THIS_FILE_GUID = "d5972a1c9cddd9941aec37a3343647aa";
        
        public const string ASSET_NAME = "Stylized Water 2";
        public const string ASSET_ID = "170386";
        public const string ASSET_ABRV = "SW2";

        public const string INSTALLED_VERSION = "1.7.0";
        
        public const int SHADER_GENERATOR_VERSION_MAJOR = 1;
        public const int SHADER_GENERATOR_MINOR = 2; 
        public const int SHADER_GENERATOR_PATCH = 1;
        
        public const string MIN_UNITY_VERSION = "2021.3.16f1";
        public const string MIN_URP_VERSION = "12.1.6";
        
        public const string DOC_URL = "http://staggart.xyz/unity/stylized-water-2/sws-2-docs/";
        public const string FORUM_URL = "https://forum.unity.com/threads/999132/";
        public const string EMAIL_URL = "mailto:contact@staggart.xyz?subject=Stylized Water 2";
        public const string DISCORD_INVITE_URL = "https://discord.gg/GNjEaJc8gw";
        
        public static bool supportedVersion = true;
        public static bool compatibleVersion = true;
        public static bool outdatedVersion = false;
        
        public static bool alphaVersion = false;

#if !URP //Enabled when com.unity.render-pipelines.universal is below MIN_URP_VERSION
        [InitializeOnLoad]
        sealed class PackageInstaller : Editor
        {
            [InitializeOnLoadMethod]
            public static void Initialize()
            {
                GetLatestCompatibleURPVersion();

                if (EditorUtility.DisplayDialog(ASSET_NAME + " v" + INSTALLED_VERSION, "This package requires the Universal Render Pipeline " + MIN_URP_VERSION + " or newer, would you like to install or update it now?", "OK", "Later"))
                {
					Debug.Log("Universal Render Pipeline <b>v" + lastestURPVersion + "</b> will start installing in a moment. Please refer to the URP documentation for set up instructions");
					Debug.Log("After installing and setting up URP, you must Re-import the Shaders folder!");
					
                    InstallURP();
                }
            }

            private static PackageInfo urpPackage;

            private static string lastestURPVersion;

#if SWS_DEV
            [MenuItem("SWS/Get latest URP version")]
#endif
            private static void GetLatestCompatibleURPVersion()
            {
                if(urpPackage == null) urpPackage = GetURPPackage();
                if(urpPackage == null) return;
                
                lastestURPVersion = urpPackage.versions.latestCompatible;
                
#if SWS_DEV
                Debug.Log("Latest compatible URP version: " + lastestURPVersion);
#endif
            }

            private static void InstallURP()
            {
                if(urpPackage == null) urpPackage = GetURPPackage();
                if(urpPackage == null) return;
                
                lastestURPVersion = urpPackage.versions.latestCompatible;

                AddRequest addRequest = Client.Add(URP_PACKAGE_ID + "@" + lastestURPVersion);
                
                //Update Core and Shader Graph packages as well, doesn't always happen automatically
                for (int i = 0; i < urpPackage.dependencies.Length; i++)
                {
#if SWS_DEV
                    Debug.Log("Updating URP dependency <i>" + urpPackage.dependencies[i].name + "</i> to " + urpPackage.dependencies[i].version);
#endif
                    addRequest = Client.Add(urpPackage.dependencies[i].name + "@" + urpPackage.dependencies[i].version);
                }
                
                //Wait until finished
                while(!addRequest.IsCompleted || addRequest.Status == StatusCode.InProgress) { }
                
                WaterShaderImporter.ReimportAll();
            }
        }
#endif
        
        public const string URP_PACKAGE_ID = "com.unity.render-pipelines.universal";

        public static PackageInfo GetURPPackage()
        {
            SearchRequest request = Client.Search(URP_PACKAGE_ID);
                
            while (request.Status == StatusCode.InProgress) { /* Waiting... */ }

            if (request.Status == StatusCode.Failure)
            {
                Debug.LogError("Failed to retrieve URP package from Package Manager...");
                return null;
            }

            return request.Result[0];
        }

        //Sorry, as much as I hate to intrude on an entire project, this is the only way in Unity to track importing or updating an asset
        public class ImportOrUpdateAsset : AssetPostprocessor
        {
            private static bool OldShadersPresent()
            {
                return Shader.Find("Universal Render Pipeline/FX/Stylized Water 2") || Shader.Find("Hidden/StylizedWater2/Deleted");
            }
            
            private void OnPreprocessAsset()
            {
                var oldShaders = false;
                //Importing/updating the Stylized Water 2 asset
                if (assetPath.EndsWith("StylizedWater2/Editor/AssetInfo.cs") || assetPath.EndsWith("sc.stylizedwater2/Editor/AssetInfo.cs"))
                {
                    oldShaders = OldShadersPresent();
                }
                //These files change every version, so will trigger when updating or importing the first time
                if (
                    //Importing the Underwater Rendering extension
                    assetPath.EndsWith("StylizedWater2/Runtime/Underwater/UnderwaterRenderer.cs"))
                    //Any further extensions...
                {
                    OnImportExtension("Underwater Rendering");
                    
                    oldShaders = OldShadersPresent();
                }

                if (assetPath.EndsWith("StylizedWater2/Runtime/DynamicEffects/WaterDynamicEffectsRenderFeature.cs"))
                {
                    OnImportExtension("Dynamic Effects");
                }
                
                if (oldShaders)
                {
                    //Old non-templated shader(s) still present.
                    if (EditorUtility.DisplayDialog(AssetInfo.ASSET_NAME, "Updating to v1.4.0+. Obsolete shader files were detected." +
                                                                          "\n\n" +
                                                                          "The water shader(s) are a now C# generated, thus has moved to a different file." +
                                                                          "\n\n" +
                                                                          "Materials in your project using the old shader must switch to it." +
                                                                          "\n\n" +
                                                                          "This process is automatic. The obsolete files will also be deleted." +
                                                                          "\n\n" +
                                                                          "Rest assured, no settings or functionality will be lost!", "OK", "Cancel"))
                    {
                        UpgradeMaterials();
                    }
                }

            }

            private void OnImportExtension(string name)
            {
                Debug.Log($"[Stylized Water 2] {name} extension installed/deleted or updated. Reimporting water shader(s) to toggle integration.");
                
                //Re-import any .watershader files, since these depend on the installation state of extensions
                WaterShaderImporter.ReimportAll();
            }
        }

        #if SWS_DEV
        [MenuItem("SWS/Upgrading/Upgrade materials to 1.4.0+")]
        #endif
        private static void UpgradeMaterials()
        {
            Shader oldShader = Shader.Find("Universal Render Pipeline/FX/Stylized Water 2");
            Shader oldShaderTess = Shader.Find("Universal Render Pipeline/FX/Stylized Water 2 (Tessellation)");
            
            Shader newShader = Shader.Find("Stylized Water 2/Standard");
            Shader newShaderTess = Shader.Find("Stylized Water 2/Standard (Tessellation)");

            int upgradedMaterialCount = 0;
            
            //Possible the script imported before the water shader even did
            //Or the water shader was already deleted, yet this function is triggered by an old underwater-rendering shader.
            if (newShader != null || oldShader != null)
            {
                string[] materials = AssetDatabase.FindAssets("t: Material");

                int totalMaterials = materials.Length;
                
                for (int i = 0; i < totalMaterials; i++)
                {
                    Material mat = AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(materials[i]));

                    EditorUtility.DisplayProgressBar(ASSET_NAME, $"Checking \"{mat.name}\" ({i}/{totalMaterials})", (float)i / (float)totalMaterials);
                    
                    if (mat.shader == oldShader)
                    {
                        int queue = mat.renderQueue;
                        mat.shader = newShader;
                        mat.renderQueue = queue;
                        EditorUtility.SetDirty(mat);
                        upgradedMaterialCount++;
                    }
                    
                    if (mat.shader == oldShaderTess)
                    {
                        int queue = mat.renderQueue;
                        mat.shader = newShaderTess;
                        mat.renderQueue = queue;
                        
                        EditorUtility.SetDirty(mat);
                        upgradedMaterialCount++;
                    }
                }
            }
            
            EditorUtility.ClearProgressBar();

            List<string> deletedFiles = new List<string>();
            //Delete old files
            {
                void DeleteFile(string path)
                {
                    if (path != string.Empty)
                    {
                        deletedFiles.Add(path);
                        AssetDatabase.DeleteAsset(path);
                    }
                }

                DeleteFile(AssetDatabase.GetAssetPath(oldShader));
                DeleteFile(AssetDatabase.GetAssetPath(oldShaderTess));
                
                //UnderwaterMask.shader
                DeleteFile(AssetDatabase.GUIDToAssetPath("85caab243c23a1e489cd0bdcaf742560"));
                
                //UnderwaterShading.shader
                DeleteFile(AssetDatabase.GUIDToAssetPath("33652c9d694d8004bb649164aafc9ecc"));
                
                //Waterline.shader
                DeleteFile(AssetDatabase.GUIDToAssetPath("aef8d53c098c94044b72ea6bda9bb6bc"));
                
                //UnderwaterPost.shader
                DeleteFile(AssetDatabase.GUIDToAssetPath("7b53b35c69174ce092d2712e3db77f0e"));
            }

            if (upgradedMaterialCount > 0 || deletedFiles.Count > 0)
            {
                if (EditorUtility.DisplayDialog(AssetInfo.ASSET_NAME, $"Converted {upgradedMaterialCount} materials to use the new water shader." + 
                                                                      $"\n\nObsolete shader files ({deletedFiles.Count}) deleted:\n\n" +
                                                                      String.Join(Environment.NewLine, deletedFiles), 
                    "OK")) { }
            }
            
            AssetDatabase.SaveAssets();

            Debug.Log("<b>[Stylized Water 2]</b> Upgrade of materials and project complete!");
        }
        
        public static bool MeetsMinimumVersion(string versionMinimum)
        {
            Version curVersion = new Version(INSTALLED_VERSION);
            Version minVersion = new Version(versionMinimum);

            return curVersion >= minVersion;
        }

        public static void OpenAssetStore(string url = null)
        {
            if (url == string.Empty) url = "https://assetstore.unity.com/packages/slug/" + ASSET_ID;
            
            Application.OpenURL(url + "?aid=1011l7Uk8&pubref=sw2editor");
        }

        public static void OpenReviewsPage()
        {
            Application.OpenURL($"https://assetstore.unity.com/packages/slug/{ASSET_ID}?aid=1011l7Uk8&pubref=sw2editor#reviews");
        }
        
        public static void OpenInPackageManager()
        {
            Application.OpenURL("com.unity3d.kharma:content/" + ASSET_ID);
        }
        
        public static void OpenForumPage()
        {
            Application.OpenURL(FORUM_URL + "/page-999");
        }
        
        public static string GetRootFolder()
        {
            //Get script path
            string scriptFilePath = AssetDatabase.GUIDToAssetPath(THIS_FILE_GUID);

            //Truncate to get relative path
            string rootFolder = scriptFilePath.Replace("Editor/AssetInfo.cs", string.Empty);

#if SWS_DEV
            //Debug.Log("<b>Package root</b> " + rootFolder);
#endif

            return rootFolder;
        }

        public static class VersionChecking
        {
            [InitializeOnLoadMethod]
            static void Initialize()
            {
                if (CHECK_PERFORMED == false)
                {
                    CheckForUpdate(false);
                    CHECK_PERFORMED = true;
                }
            }
            
            private static bool CHECK_PERFORMED
            {
                get => SessionState.GetBool("SW2_VERSION_CHECK_PERFORMED", false);
                set => SessionState.SetBool("SW2_VERSION_CHECK_PERFORMED", value);
            }
            
            public static string LATEST_VERSION
            {
                get => SessionState.GetString("SW2_LATEST_VERSION", INSTALLED_VERSION);
                set => SessionState.SetString("SW2_LATEST_VERSION", value);
            }
            public static bool UPDATE_AVAILABLE => new Version(LATEST_VERSION) > new Version(INSTALLED_VERSION);
            
            public static string GetUnityVersion()
            {
                string version = UnityEditorInternal.InternalEditorUtility.GetFullUnityVersion();
                
                //Remove GUID in parenthesis 
                return version.Substring(0, version.LastIndexOf(" ("));
            }
            
            public static void CheckUnityVersion()
            {
                outdatedVersion = false;
                supportedVersion = true;
                compatibleVersion = true;
                
                #if !UNITY_2020_3_OR_NEWER
                compatibleVersion = false;
                #endif
                
                #if !UNITY_2021_3_OR_NEWER
                outdatedVersion = true;
                #endif
                
                #if UNITY_6000_0_OR_NEWER
                supportedVersion = false;
                #endif

                string versionName = GetUnityVersion();
                alphaVersion = versionName.Contains("f") == false;
            }

            public static string apiResult;
            private static bool showPopup;

            public enum VersionStatus
            {
                UpToDate,
                Outdated
            }

            public enum QueryStatus
            {
                Fetching,
                Completed,
                Failed
            }
            public static QueryStatus queryStatus = QueryStatus.Completed;

#if SWS_DEV
            [MenuItem("SWS/Check for update")]
#endif
            public static void GetLatestVersionPopup()
            {
                CheckForUpdate(true);
            }

            private static int VersionStringToInt(string input)
            {
                //Remove all non-alphanumeric characters from version 
                input = input.Replace(".", string.Empty);
                input = input.Replace(" BETA", string.Empty);
                return int.Parse(input, System.Globalization.NumberStyles.Any);
            }

            public static void CheckForUpdate(bool showPopup = false)
            {
                VersionChecking.showPopup = showPopup;

                queryStatus = QueryStatus.Fetching;

                var url = $"https://api.assetstore.unity3d.com/package/latest-version/{ASSET_ID}";

                using (System.Net.WebClient webClient = new System.Net.WebClient())
                {
                    webClient.DownloadStringCompleted += OnRetrievedAPIContent;
                    webClient.DownloadStringAsync(new System.Uri(url), apiResult);
                }
            }
            
            private class AssetStoreItem
            {
                public string name;
                public string version;
            }

            private static void OnRetrievedAPIContent(object sender, DownloadStringCompletedEventArgs e)
            {
                if (e.Error == null && !e.Cancelled)
                {
                    string result = e.Result;

                    AssetStoreItem asset = (AssetStoreItem)JsonUtility.FromJson(result, typeof(AssetStoreItem));

                    LATEST_VERSION = asset.version;
                    
#if SWS_DEV
                    Debug.Log("<b>PackageVersionCheck</b> Update available = " + UPDATE_AVAILABLE + " (Installed:" + INSTALLED_VERSION + ") (Remote:" + LATEST_VERSION + ")");
#endif

                    queryStatus = QueryStatus.Completed;

                    if (VersionChecking.showPopup)
                    {
                        if (UPDATE_AVAILABLE)
                        {
                            if (EditorUtility.DisplayDialog(ASSET_NAME + ", version " + INSTALLED_VERSION, "An updated version is available: " + LATEST_VERSION, "Open Package Manager", "Close"))
                            {
                                OpenInPackageManager();
                            }
                        }
                        else
                        {
                            if (EditorUtility.DisplayDialog(ASSET_NAME + ", version " + INSTALLED_VERSION, "Installed version is up-to-date!", "Close")) { }
                        }
                    }
                }
                else
                {
                    Debug.LogWarning("[" + ASSET_NAME + "] Contacting update server failed: " + e.Error.Message);
                    queryStatus = QueryStatus.Failed;
                }
            }
        }
    }
}