using XGame.Framework.ObjectCollection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using System.Reflection;

namespace XGame.Editor.ObjectCollection
{
    [CustomPropertyDrawer(typeof(ObjectCollector))]
    public partial class ObjectCollectorEditor : PropertyDrawer
    {
        private static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        private ReorderableList collectList;
        private ReorderableList DrawsearchList;
        private List<string> searchList = new List<string>();
        private SerializedProperty keys;
        private SerializedProperty values;
        private SerializedProperty bindTypes;

        private ObjectCollector objectCollector;

        //搜索框
        private string m_InputSearchText;
        private GUIStyle TextFieldRoundEdge;
        private GUIStyle TextFieldRoundEdgeCancelButton;
        private GUIStyle TextFieldRoundEdgeCancelButtonEmpty;
        private GUIStyle TransparentTextField;

        private void SetData(SerializedProperty property)
        {
            var ob = property.serializedObject.targetObject;
            var selfFields = ob.GetType().GetFields(flags);
            var baseFields = ob.GetType().BaseType.GetFields(flags);
            var fields = selfFields.Concat(baseFields).ToArray();

            foreach (var field in fields)
            {
                if (field.FieldType == typeof(ObjectCollector))
                {
                    objectCollector = field.GetValue(ob) as ObjectCollector;
                    break;
                }
            }
        }

        private void OnInit(SerializedProperty property)
        {
            SetData(property);
            collectList.headerHeight = 0;
            DrawsearchList.headerHeight = 0;
            // 绘制
            collectList.drawElementCallback = (Rect rect, int index, bool selected, bool focused) =>
            {
                var key = keys.GetArrayElementAtIndex(index);
                var value = values.GetArrayElementAtIndex(index);
                var bindType = bindTypes.GetArrayElementAtIndex(index);

                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(10);
                // 3等分，中间间隔10像素
                var copyRect = new Rect(rect.x, rect.y + 2, (rect.width - 20) / 3, EditorGUIUtility.singleLineHeight);
                if (GUI.Button(copyRect, "", "HelpBox"))
                {
                    TextEditor textEditor = new TextEditor();
                    textEditor.text = key.stringValue;
                    textEditor.OnFocus();
                    textEditor.Copy();
                    Debug.Log($"序列化器Key复制成功！Key：{key.stringValue}");
                };
                EditorGUI.BeginDisabledGroup(true);
                //key
                var keyRect = copyRect;
                EditorGUI.TextField(keyRect, key.stringValue);
                EditorGUI.EndDisabledGroup();
                //value
                var valueRect = keyRect;
                valueRect.x += keyRect.width + 10;
                Type type = objectCollector.collectorType;
                var tmp = value.objectReferenceValue;
                value.objectReferenceValue = EditorGUI.ObjectField(valueRect, value.objectReferenceValue, type, true);
                if (tmp != value.objectReferenceValue)
                {
                    if (value.objectReferenceValue != null)
                    {
                        if (CheckForSameValue(value.objectReferenceValue, index))
                        {
                            value.objectReferenceValue = null;
                            key.stringValue = "";
                            bindType.stringValue = "";
                        }
                        else
                        {
                            string name = value.objectReferenceValue.name.Replace(" ", "");
                            if (!CheckForReName(name, index, value.objectReferenceValue))
                            {
                                key.stringValue = name;
                                bindType.stringValue = GetTypeName(value.objectReferenceValue);
                            }
                            else
                            {
                                value.objectReferenceValue = null;
                                key.stringValue = "";
                                bindType.stringValue = "";
                            }
                        }
                    }
                    else
                    {
                        key.stringValue = string.Empty;
                        bindType.stringValue = string.Empty;
                    }
                }
                else
                {
                    if (value.objectReferenceValue != null)
                    {
                        string name = value.objectReferenceValue.name.Replace(" ", "");
                        key.stringValue = name;
                    }
                    else
                    {
                        key.stringValue = string.Empty;
                        bindType.stringValue = string.Empty;
                    }
                }
                //type
                if (value.objectReferenceValue != null)
                {
                    var bindTypeVal = bindType.stringValue;
                    var typeRect = valueRect;
                    typeRect.x += valueRect.width + 10;
                    var options = GetObjectTypes(value.objectReferenceValue);
                    var selectIdx = 0;
                    if (!string.IsNullOrEmpty(bindTypeVal))
                    {
                        selectIdx = Array.FindIndex(options, (a) => bindTypeVal.Equals(a, StringComparison.OrdinalIgnoreCase));
                        if (selectIdx < 0)
                            selectIdx = 0;
                    }
                    selectIdx = EditorGUI.Popup(typeRect, selectIdx, options);
                    bindType.stringValue = options[selectIdx];
                }
                EditorGUILayout.EndHorizontal();
            };

            collectList.onAddCallback = list =>
            {
                values.arraySize++;
                var size = values.arraySize;
                keys.arraySize = size;
                bindTypes.arraySize = size;
                values.GetArrayElementAtIndex(size - 1).objectReferenceValue = null;
                keys.GetArrayElementAtIndex(size - 1).stringValue = "";
                bindTypes.GetArrayElementAtIndex(size - 1).stringValue = "";
            };

            collectList.onRemoveCallback = list =>
            {
                var index = list.index;
                if (list.index == -1)
                {
                    index = values.arraySize - 1;
                }
                values.GetArrayElementAtIndex(index).objectReferenceValue = null;
                keys.GetArrayElementAtIndex(index).stringValue = null;
                bindTypes.GetArrayElementAtIndex(index).stringValue = null;
                values.DeleteArrayElementAtIndex(index);
                keys.DeleteArrayElementAtIndex(index);
                bindTypes.DeleteArrayElementAtIndex(index);
            };

            DrawsearchList.drawElementCallback = (Rect rect, int index, bool selected, bool focused) =>
            {
                var key = searchList[index];
                SerializedProperty value = null;
                SerializedProperty bindType = null;
                for (int i = 0; i < keys.arraySize; i++)
                {
                    if (keys.GetArrayElementAtIndex(i).stringValue == key)
                    {
                        value = values.GetArrayElementAtIndex(i);
                        bindType = bindTypes.GetArrayElementAtIndex(i);
                        break;
                    }
                }

                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(10);
                //key
                var keyRect = new Rect(rect.x, rect.y + 2, (rect.width - 20) / 3, EditorGUIUtility.singleLineHeight);
                EditorGUI.BeginDisabledGroup(true);
                EditorGUI.TextField(keyRect, key);
                //value
                var valueRect = keyRect;
                valueRect.x += keyRect.width + 10;
                Type type = objectCollector.collectorType;
                value.objectReferenceValue = EditorGUI.ObjectField(valueRect, value.objectReferenceValue, type, true);
                //type
                if (value.objectReferenceValue != null)
                {
                    var typeRect = valueRect;
                    typeRect.x += valueRect.width + 10;
                    EditorGUI.TextField(typeRect, bindType.stringValue);
                }
                EditorGUI.EndDisabledGroup();
                EditorGUILayout.EndHorizontal();
            };
        }
        private string[] GetObjectTypes(UnityEngine.Object obj)
        {
            var types = new List<string>();
            GameObject go = null;
            if (obj is Component component)
            {
                go = component.gameObject;
            }
            else if (obj is GameObject tmpGo)
            {
                go = tmpGo;
            }
            if (go != null)
            {
                types.Add(typeof(GameObject).Name);
                types.Add(typeof(Transform).Name);
                var components = go.GetComponents<Component>();
                foreach (var item in components)
                {
                    types.Add(GetTypeName(item));
                }
            }
            else
            {
                types.Add(GetTypeName(obj));
            }
            return types.ToArray();
        }
        const string UnityNamespace = "UnityEngine";
        private string GetTypeName(object obj)
        {
            var type = obj.GetType();
            if (type.Namespace == UnityNamespace)
            {
                return type.Name;
            }
            return type.FullName;
        }
        private bool CheckForSameValue(UnityEngine.Object value, int index)
        {
            for (int i = 0; i < values.arraySize; i++)
            {
                if (index == i) continue;
                var compareValue = values.GetArrayElementAtIndex(i).objectReferenceValue;
                if (compareValue == null) continue;
                if (value.GetInstanceID() == compareValue.GetInstanceID())
                {
                    EditorUtility.DisplayDialog("Warning",
                            $"已经添加过该{objectCollector.collectorType.Name}啦！",
                            "确定");
                    return true;
                }
            }
            return false;
        }

        private bool CheckForReName(string name, int index, UnityEngine.Object ob)
        {
            for (int i = 0; i < values.arraySize; i++)
            {
                if (i == index) continue;
                var value = values.GetArrayElementAtIndex(i);
                if (value.objectReferenceValue == null) continue;
                string compareName = value.objectReferenceValue.name.Replace(" ", "");
                if (!string.IsNullOrEmpty(compareName) && name == compareName)
                {
                    EditorUtility.DisplayDialog("Warning",
                            $"该{objectCollector.collectorType.Name}名称{name}重名啦！请修改GameObject名称。",
                            "确定");
                    EditorGUIUtility.PingObject(ob);
                    return true;
                }
            }
            return false;
        }

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return 0;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            keys = property.FindPropertyRelative("keys");
            values = property.FindPropertyRelative("values");
            bindTypes = property.FindPropertyRelative("bindTypes");

            GUILayout.BeginVertical("HelpBox");

            DrawInputTextField();
            EditorGUILayout.Space();
            if (collectList == null)
            {
                collectList = new ReorderableList(property.serializedObject, values, true, false, true, true);
                DrawsearchList = new ReorderableList(searchList, typeof(string), true, false, false, false);
                OnInit(property);
            }
            if (Application.isPlaying) EditorGUI.BeginDisabledGroup(true);
            if (string.IsNullOrEmpty(m_InputSearchText))
            {

                GUI.contentColor = Color.white;
                var dragArea = GUILayoutUtility.GetRect(0f, 45f);
                GUI.color = Color.grey;
                UnityEngine.Object ob = DragAreaGUI.ObjectField(dragArea, objectCollector.collectorType, objectCollector.blackTypes);
                if (ob != null)
                    AddObjectToList(ob);
                GUI.color = Color.white;
                GUILayout.Space(8);

                collectList.DoLayoutList();
            }
            else
            {
                GetSearchList();
                DrawsearchList.DoLayoutList();
            }
            GUILayout.EndVertical();

            if (Application.isPlaying) EditorGUI.EndDisabledGroup();
        }

        private void AddObjectToList(UnityEngine.Object ob)
        {
            if (CheckForSameValue(ob, -1)) return;
            string name = ob.name.Replace(" ", "");
            if (CheckForReName(name, -1, ob)) return;
            values.arraySize++;
            var size = values.arraySize;
            keys.arraySize = size;
            bindTypes.arraySize = size;
            values.GetArrayElementAtIndex(size - 1).objectReferenceValue = ob;
            keys.GetArrayElementAtIndex(size - 1).stringValue = ob.name;
            bindTypes.GetArrayElementAtIndex(size - 1).stringValue = GetTypeName(ob);
        }

        private void GetSearchList()
        {
            searchList.Clear();
            for (int index = 0; index < keys.arraySize; index++)
            {
                var key = keys.GetArrayElementAtIndex(index);
                if (!string.IsNullOrEmpty(m_InputSearchText))
                {
                    var inputStr = m_InputSearchText.ToLower();
                    var compareStr = key.stringValue.ToLower();
                    Regex r = new Regex(inputStr);
                    Match m = r.Match(compareStr);
                    if (m.Success)
                    {
                        searchList.Add(key.stringValue);
                    }
                }
            }
        }

        private void DrawInputTextField()
        {
            if (TextFieldRoundEdge == null)
            {
                TextFieldRoundEdge = new GUIStyle("SearchTextField");
                TextFieldRoundEdgeCancelButton = new GUIStyle("SearchCancelButton");
                TextFieldRoundEdgeCancelButtonEmpty = new GUIStyle("SearchCancelButtonEmpty");
                TransparentTextField = new GUIStyle(EditorStyles.whiteLabel);
                TransparentTextField.normal.textColor = EditorStyles.textField.normal.textColor;
            }

            Rect position = EditorGUILayout.GetControlRect();
            GUIStyle textFieldRoundEdge = TextFieldRoundEdge;
            GUIStyle transparentTextField = TransparentTextField;
            GUIStyle gUIStyle = (m_InputSearchText != "") ? TextFieldRoundEdgeCancelButton : TextFieldRoundEdgeCancelButtonEmpty;

            position.width -= gUIStyle.fixedWidth;
            if (Event.current.type == EventType.Repaint)
            {
                GUI.contentColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black);
                if (string.IsNullOrEmpty(m_InputSearchText))
                {
                    textFieldRoundEdge.Draw(position, new GUIContent("输入查询，可模糊匹配"), 0);
                }
                else
                {
                    textFieldRoundEdge.Draw(position, new GUIContent(""), 0);
                }
                GUI.contentColor = Color.white;
            }
            Rect rect = position;
            float num = textFieldRoundEdge.CalcSize(new GUIContent("")).x - 2f;
            rect.width -= num;
            rect.x += num;
            rect.y += 1f;

            m_InputSearchText = EditorGUI.TextField(rect, m_InputSearchText, transparentTextField);
            position.x += position.width;
            position.width = gUIStyle.fixedWidth;
            position.height = gUIStyle.fixedHeight;
            if (GUI.Button(position, GUIContent.none, gUIStyle) && m_InputSearchText != "")
            {
                m_InputSearchText = "";
                GUI.changed = true;
                GUIUtility.keyboardControl = 0;
            }
        }
    }
}
