using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

namespace Mirror
{
    [CustomEditor(typeof(NetworkManager), true)]
    [CanEditMultipleObjects]
    public class NetworkManagerEditor : Editor
    {
        SerializedProperty spawnListProperty;
        ReorderableList spawnList;
        protected NetworkManager networkManager;

        protected void Init()
        {
            if (spawnList == null)
            {
                networkManager = target as NetworkManager;
                spawnListProperty = serializedObject.FindProperty("spawnPrefabs");
                spawnList = new ReorderableList(serializedObject, spawnListProperty)
                {
                    drawHeaderCallback = DrawHeader,
                    drawElementCallback = DrawChild,
                    onReorderCallback = Changed,
                    onRemoveCallback = RemoveButton,
                    onChangedCallback = Changed,
                    onAddCallback = AddButton,
                    // this uses a 16x16 icon. other sizes make it stretch.
                    elementHeight = 16
                };
            }
        }

        public override void OnInspectorGUI()
        {
            Init();
            DrawDefaultInspector();
            EditorGUI.BeginChangeCheck();
            spawnList.DoLayoutList();
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
            }

            if (GUILayout.Button("Populate Spawnable Prefabs"))
            {
                ScanForNetworkIdentities();
            }
        }

        void ScanForNetworkIdentities()
        {
            List<GameObject> identities = new List<GameObject>();
            bool cancelled = false;
            try
            {
                string[] paths = EditorHelper.IterateOverProject("t:prefab").ToArray();
                int count = 0;
                foreach (string path in paths)
                {
                    // ignore test & example prefabs.
                    // users sometimes keep the folders in their projects.
                    if (path.Contains("Mirror/Tests/") ||
                        path.Contains("Mirror/Examples/"))
                    {
                        continue;
                    }

                    if (EditorUtility.DisplayCancelableProgressBar("Searching for NetworkIdentities..",
                            $"Scanned {count}/{paths.Length} prefabs. Found {identities.Count} new ones",
                            count / (float)paths.Length))
                    {
                        cancelled = true;
                        break;
                    }

                    count++;

                    NetworkIdentity ni = AssetDatabase.LoadAssetAtPath<NetworkIdentity>(path);
                    if (!ni)
                    {
                        continue;
                    }

                    if (!networkManager.spawnPrefabs.Contains(ni.gameObject))
                    {
                        identities.Add(ni.gameObject);
                    }

                }
            }
            finally
            {

                EditorUtility.ClearProgressBar();
                if (!cancelled)
                {
                    // RecordObject is needed for "*" to show up in Scene.
                    // however, this only saves List.Count without the entries.
                    Undo.RecordObject(networkManager, "NetworkManager: populated prefabs");

                    // add the entries
                    networkManager.spawnPrefabs.AddRange(identities);

                    // sort alphabetically for better UX
                    networkManager.spawnPrefabs = networkManager.spawnPrefabs.OrderBy(go => go.name).ToList();

                    // SetDirty is required to save the individual entries properly.
                    EditorUtility.SetDirty(target);
                }
                // Loading assets might use a lot of memory, so try to unload them after
                Resources.UnloadUnusedAssets();
            }
        }

        static void DrawHeader(Rect headerRect)
        {
            GUI.Label(headerRect, "Registered Spawnable Prefabs:");
        }

        internal void DrawChild(Rect r, int index, bool isActive, bool isFocused)
        {
            SerializedProperty prefab = spawnListProperty.GetArrayElementAtIndex(index);
            GameObject go = (GameObject)prefab.objectReferenceValue;

            GUIContent label;
            if (go == null)
            {
                label = new GUIContent("Empty", "Drag a prefab with a NetworkIdentity here");
            }
            else
            {
                NetworkIdentity identity = go.GetComponent<NetworkIdentity>();
                label = new GUIContent(go.name, identity != null ? $"AssetId: [{identity.assetId}]" : "No Network Identity");
            }

            GameObject newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false);

            if (newGameObject != go)
            {
                if (newGameObject != null && !newGameObject.GetComponent<NetworkIdentity>())
                {
                    Debug.LogError($"Prefab {newGameObject} cannot be added as spawnable as it doesn't have a NetworkIdentity.");
                    return;
                }
                prefab.objectReferenceValue = newGameObject;
            }
        }

        internal void Changed(ReorderableList list)
        {
            EditorUtility.SetDirty(target);
        }

        internal void AddButton(ReorderableList list)
        {
            spawnListProperty.arraySize += 1;
            list.index = spawnListProperty.arraySize - 1;

            SerializedProperty obj = spawnListProperty.GetArrayElementAtIndex(spawnListProperty.arraySize - 1);
            obj.objectReferenceValue = null;

            spawnList.index = spawnList.count - 1;

            Changed(list);
        }

        internal void RemoveButton(ReorderableList list)
        {
            spawnListProperty.DeleteArrayElementAtIndex(spawnList.index);
            if (list.index >= spawnListProperty.arraySize)
            {
                list.index = spawnListProperty.arraySize - 1;
            }
        }
    }
}
