// ----------------------------------------------------------------------------
// <copyright file="PhotonEditorUtils.cs" company="Exit Games GmbH">
//   PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
// </copyright>
// <summary>
//   Unity Editor Utils
// </summary>
// <author>developer@exitgames.com</author>
// ----------------------------------------------------------------------------

#pragma warning disable 618 // Deprecation warnings


#if UNITY_2017_4_OR_NEWER
#define SUPPORTED_UNITY
#endif


#if UNITY_EDITOR

namespace Photon.Realtime
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using UnityEditor;
    using UnityEngine;

    using System.IO;
    using System.Text;
    using UnityEngine.Networking;

    #if UNITY_2021_3_OR_NEWER
    using UnityEditor.Build;
    #endif

    [InitializeOnLoad]
    public static class PhotonEditorUtils
    {
        /// <summary>Stores a flag which tells Editor scripts if the PhotonEditor.OnProjectChanged got called since initialization.</summary>
        /// <remarks>If not, the AssetDatabase is likely not usable yet and instances of ScriptableObject can't be loaded.</remarks>
        [Obsolete("Directly check EditorApplication.isUpdating to figure out if assets are being imported at the given time.")]
        public static bool ProjectChangedWasCalled 
        {
            get
            {
                return UnityEditor.EditorApplication.isUpdating;
            }
        } 


        /// <summary>True if the ChatClient of the Photon Chat API is available. If so, the editor may (e.g.) show additional options in settings.</summary>
        public static bool HasChat;

        /// <summary>True if the VoiceClient of the Photon Voice API is available. If so, the editor may (e.g.) show additional options in settings.</summary>
        public static bool HasVoice;

        /// <summary>True if PUN is in the project.</summary>
        public static bool HasPun;

        /// <summary>True if Photon Fusion is available in the project (and enabled).</summary>
        public static bool HasFusion;

        /// <summary>True if the PhotonEditorUtils checked the available products / APIs. If so, the editor may (e.g.) show additional options in settings.</summary>
        public static bool HasCheckedProducts;

        static PhotonEditorUtils()
        {
            HasVoice = Type.GetType("Photon.Voice.VoiceClient, Assembly-CSharp") != null || Type.GetType("Photon.Voice.VoiceClient, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Voice.VoiceClient, PhotonVoice.API") != null;
            HasChat = Type.GetType("Photon.Chat.ChatClient, Assembly-CSharp") != null || Type.GetType("Photon.Chat.ChatClient, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Chat.ChatClient, PhotonChat") != null;
            HasPun = Type.GetType("Photon.Pun.PhotonNetwork, Assembly-CSharp") != null || Type.GetType("Photon.Pun.PhotonNetwork, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Pun.PhotonNetwork, PhotonUnityNetworking") != null;
            #if FUSION_WEAVER
            HasFusion = true;
            #endif
            PhotonEditorUtils.HasCheckedProducts = true;

            if (EditorPrefs.HasKey("DisablePun") && EditorPrefs.GetBool("DisablePun"))
            {
                HasPun = false;
            }

            if (HasPun)
            {
                // MOUNTING SYMBOLS
                #if !PHOTON_UNITY_NETWORKING
                AddScriptingDefineSymbolToAllBuildTargetGroups("PHOTON_UNITY_NETWORKING");
                #endif

                #if !PUN_2_0_OR_NEWER
                AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_0_OR_NEWER");
                #endif

                #if !PUN_2_OR_NEWER
                AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_OR_NEWER");
                #endif

                #if !PUN_2_19_OR_NEWER
                AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_19_OR_NEWER");
                #endif
            }
        }

        /// <summary>
        /// Adds a given scripting define symbol to all build target groups
        /// You can see all scripting define symbols ( not the internal ones, only the one for this project), in the PlayerSettings inspector
        /// </summary>
        /// <param name="defineSymbol">Define symbol.</param>
        public static void AddScriptingDefineSymbolToAllBuildTargetGroups(string defineSymbol)
        {
            foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget)))
            {
                BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);

                if (group == BuildTargetGroup.Unknown)
                {
                    continue;
                }

                var defineSymbols = GetScriptingDefines(group)
                                   .Split(';')
                                   .Select(d => d.Trim())
                                   .ToList();

                if (!defineSymbols.Contains(defineSymbol))
                {
                    defineSymbols.Add(defineSymbol);

                    try
                    {
                        SetScriptingDefines(group, string.Join(";", defineSymbols.ToArray()));
                    }
                    catch (Exception e)
                    {
                        Debug.Log("Could not set Photon " + defineSymbol + " defines for build target: " + target + " group: " + group + " " + e);
                    }
                }
            }
        }


        private static string GetScriptingDefines(BuildTargetGroup group)
        {
            #if UNITY_2021_3_OR_NEWER
            var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(group);
            var defineSymbolsString = PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget);
            #else
            var defineSymbolsString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
            #endif

            return defineSymbolsString;
        }

        private static void SetScriptingDefines(BuildTargetGroup group, string defines)
        {
            #if UNITY_2021_3_OR_NEWER
            var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(group);
            PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, defines);
            #else
            PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
            #endif
        }

        /// <summary>
        /// Removes PUN2's Script Define Symbols from project
        /// </summary>
        public static void CleanUpPunDefineSymbols()
        {
            foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget)))
            {
                BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);
                if (group == BuildTargetGroup.Unknown)
                {
                    continue;
                }

                var defineSymbols = GetScriptingDefines(group)
                                   .Split(';')
                                   .Select(d => d.Trim())
                                   .ToList();

                List<string> newDefineSymbols = new List<string>();
                foreach (var symbol in defineSymbols)
                {
                    if ("PHOTON_UNITY_NETWORKING".Equals(symbol) || symbol.StartsWith("PUN_2_"))
                    {
                        continue;
                    }

                    newDefineSymbols.Add(symbol);
                }

                try
                {
                    SetScriptingDefines(group, string.Join(";", newDefineSymbols.ToArray()));
                }
                catch (Exception e)
                {
                    Debug.LogErrorFormat("Could not set clean up PUN2's define symbols for build target: {0} group: {1}, {2}", target, group, e);
                }
            }
        }


        /// <summary>
        /// Gets the parent directory of a path. Recursive Function, will return null if parentName not found
        /// </summary>
        /// <returns>The parent directory</returns>
        /// <param name="path">Path.</param>
        /// <param name="parentName">Parent name.</param>
        public static string GetParent(string path, string parentName)
        {
            var dir = new DirectoryInfo(path);

            if (dir.Parent == null)
            {
                return null;
            }

            if (string.IsNullOrEmpty(parentName))
            {
                return dir.Parent.FullName;
            }

            if (dir.Parent.Name == parentName)
            {
                return dir.Parent.FullName;
            }

            return GetParent(dir.Parent.FullName, parentName);
        }

		/// <summary>
		/// Check if a GameObject is a prefab asset or part of a prefab asset, as opposed to an instance in the scene hierarchy
		/// </summary>
		/// <returns><c>true</c>, if a prefab asset or part of it, <c>false</c> otherwise.</returns>
		/// <param name="go">The GameObject to check</param>
		public static bool IsPrefab(GameObject go)
		{
            #if UNITY_2021_2_OR_NEWER
            return UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(go) != null || EditorUtility.IsPersistent(go);
            #elif UNITY_2018_3_OR_NEWER
            return UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(go) != null || EditorUtility.IsPersistent(go);
            #else
            return EditorUtility.IsPersistent(go);
			#endif
		}

        //https://forum.unity.com/threads/using-unitywebrequest-in-editor-tools.397466/#post-4485181
        public static void StartCoroutine(System.Collections.IEnumerator update)
        {
            EditorApplication.CallbackFunction closureCallback = null;

            closureCallback = () =>
            {
                try
                {
                    if (update.MoveNext() == false)
                    {
                        EditorApplication.update -= closureCallback;
                    }
                }
                catch (Exception ex)
                {
                    Debug.LogException(ex);
                    EditorApplication.update -= closureCallback;
                }
            };

            EditorApplication.update += closureCallback;
        }

        public static System.Collections.IEnumerator HttpPost(string url, Dictionary<string, string> headers, byte[] payload, Action<string> successCallback, Action<string> errorCallback)
        {
            using (UnityWebRequest w = new UnityWebRequest(url, "POST"))
            {
                if (payload != null)
                {
                    w.uploadHandler = new UploadHandlerRaw(payload);
                }
                w.downloadHandler = new DownloadHandlerBuffer();
                if (headers != null)
                {
                    foreach (var header in headers)
                    {
                        w.SetRequestHeader(header.Key, header.Value);
                    }
                }

                #if UNITY_2017_2_OR_NEWER
                yield return w.SendWebRequest();
                #else
                yield return w.Send();
                #endif

                while (w.isDone == false)
                    yield return null;

                #if UNITY_2020_2_OR_NEWER
                if (w.result == UnityWebRequest.Result.ProtocolError || w.result == UnityWebRequest.Result.ConnectionError || w.result == UnityWebRequest.Result.DataProcessingError)
                #elif UNITY_2017_1_OR_NEWER
                if (w.isNetworkError || w.isHttpError)
                #endif
                {
                    if (errorCallback != null)
                    {
                        errorCallback(w.error);
                    }
                }
                else
                {
                    if (successCallback != null)
                    {
                        successCallback(w.downloadHandler.text);
                    }
                }
            }
        }
        /// <summary>
        /// Creates a Foldout using a toggle with (GUIStyle)"Foldout") and a separate label. This is a workaround for 2019.3 foldout arrows not working.
        /// </summary>
        /// <param name="isExpanded"></param>
        /// <param name="label"></param>
        /// <returns>Returns the new isExpanded value.</returns>
        public static bool Foldout(this SerializedProperty isExpanded, GUIContent label)
        {
            var rect = EditorGUILayout.GetControlRect();
            bool newvalue = EditorGUI.Toggle(new Rect(rect) { xMin = rect.xMin + 2 }, GUIContent.none, isExpanded.boolValue, (GUIStyle)"Foldout");
            EditorGUI.LabelField(new Rect(rect) { xMin = rect.xMin + 15 }, label);
            if (newvalue != isExpanded.boolValue)
            {
                isExpanded.boolValue = newvalue;
                isExpanded.serializedObject.ApplyModifiedProperties();
            }
            return newvalue;
        }

        /// <summary>
        /// Creates a Foldout using a toggle with (GUIStyle)"Foldout") and a separate label. This is a workaround for 2019.3 foldout arrows not working.
        /// </summary>
        /// <param name="isExpanded"></param>
        /// <param name="label"></param>
        /// <returns>Returns the new isExpanded value.</returns>
        public static bool Foldout(this bool isExpanded, GUIContent label)
        {
            var rect = EditorGUILayout.GetControlRect();
            bool newvalue = EditorGUI.Toggle(new Rect(rect) { xMin = rect.xMin + 2 }, GUIContent.none, isExpanded, (GUIStyle)"Foldout");
            EditorGUI.LabelField(new Rect(rect) { xMin = rect.xMin + 15 }, label);
            return newvalue;
        }
    }


    public class CleanUpDefinesOnPunDelete : UnityEditor.AssetModificationProcessor
    {
        public static AssetDeleteResult OnWillDeleteAsset(string assetPath, RemoveAssetOptions rao)
        {
            if ("Assets/Photon/PhotonUnityNetworking".Equals(assetPath))
            {
                PhotonEditorUtils.CleanUpPunDefineSymbols();
            }

            return AssetDeleteResult.DidNotDelete;
        }
    }
}
#endif