using System;
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UniGLTF.Utils;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;


namespace UniVRM10
{
    [CustomEditor(typeof(Vrm10Instance))]
    public class Vrm10InstanceEditor : Editor
    {
        const string SaveTitle = "New folder for vrm-1.0 assets...";
        const string SpringsPath = "SpringBone.Springs";
        const string CollidersPath = "SpringBone.ColliderGroups";

        enum Tab
        {
            VrmInstance,
            LookAt,
            SpringBone,
        }

        Vrm10Instance m_instance;

        SerializedProperty m_script;
        SerializedProperty m_vrmObject;
        SerializedProperty m_updateType;

        SerializedProperty m_drawLookatGizmo;
        SerializedProperty m_lookatTarget;
        SerializedProperty m_lookatTargetType;

        ListView m_springs;

        void OnEnable()
        {
            m_instance = (Vrm10Instance)target;
            m_script = serializedObject.FindProperty("m_Script");

            m_vrmObject = serializedObject.FindProperty(nameof(m_instance.Vrm));
            m_updateType = serializedObject.FindProperty(nameof(m_instance.UpdateType));

            m_drawLookatGizmo = serializedObject.FindProperty(nameof(m_instance.DrawLookAtGizmo));
            m_lookatTarget = serializedObject.FindProperty(nameof(m_instance.LookAtTarget));
            m_lookatTargetType = serializedObject.FindProperty(nameof(m_instance.LookAtTargetType));
        }

        static VRM10Object CreateAsset(string path, Dictionary<ExpressionPreset, VRM10Expression> expressions, Vrm10Instance instance)
        {
            if (string.IsNullOrEmpty(path))
            {
                return null;
            }
            var unityPath = UnityPath.FromFullpath(path);
            if (!unityPath.IsUnderWritableFolder)
            {
                EditorUtility.DisplayDialog("error", "The specified path is not inside of Assets or writable Packages", "OK");
                return null;
            }

            var asset = ScriptableObject.CreateInstance<VRM10Object>();

            asset.Prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(instance?.gameObject);
            foreach (var kv in expressions)
            {
                switch (kv.Key)
                {
                    case ExpressionPreset.aa: asset.Expression.Aa = kv.Value; break;
                    case ExpressionPreset.ih: asset.Expression.Ih = kv.Value; break;
                    case ExpressionPreset.ou: asset.Expression.Ou = kv.Value; break;
                    case ExpressionPreset.ee: asset.Expression.Ee = kv.Value; break;
                    case ExpressionPreset.oh: asset.Expression.Oh = kv.Value; break;
                    case ExpressionPreset.happy: asset.Expression.Happy = kv.Value; break;
                    case ExpressionPreset.angry: asset.Expression.Angry = kv.Value; break;
                    case ExpressionPreset.sad: asset.Expression.Sad = kv.Value; break;
                    case ExpressionPreset.relaxed: asset.Expression.Relaxed = kv.Value; break;
                    case ExpressionPreset.surprised: asset.Expression.Surprised = kv.Value; break;
                    case ExpressionPreset.blink: asset.Expression.Blink = kv.Value; break;
                    case ExpressionPreset.blinkLeft: asset.Expression.BlinkLeft = kv.Value; break;
                    case ExpressionPreset.blinkRight: asset.Expression.BlinkRight = kv.Value; break;
                    case ExpressionPreset.lookUp: asset.Expression.LookUp = kv.Value; break;
                    case ExpressionPreset.lookDown: asset.Expression.LookDown = kv.Value; break;
                    case ExpressionPreset.lookLeft: asset.Expression.LookLeft = kv.Value; break;
                    case ExpressionPreset.lookRight: asset.Expression.LookRight = kv.Value; break;
                    case ExpressionPreset.neutral: asset.Expression.Neutral = kv.Value; break;
                    default: break;
                }
            }

            unityPath.CreateAsset(asset);
            AssetDatabase.Refresh();
            var loaded = unityPath.LoadAsset<VRM10Object>();

            return loaded;
        }

        static bool CheckHumanoid(GameObject go)
        {
            if (go.TryGetComponent<Animator>(out var animator))
            {
                if (animator.avatar == null)
                {
                    EditorGUILayout.HelpBox("animator.avatar is null", MessageType.Error);
                    return false;
                }
                if (!animator.avatar.isValid)
                {
                    EditorGUILayout.HelpBox("animator.avatar is not valid", MessageType.Error);
                    return false;

                }
                if (!animator.avatar.isHuman)
                {
                    EditorGUILayout.HelpBox("animator.avatar is not human", MessageType.Error);
                    return false;
                }

                return true;
            }

            var humanoid = go.GetComponentOrNull<UniHumanoid.Humanoid>();
            if (humanoid == null)
            {
                EditorGUILayout.HelpBox("vrm-1.0 require Animator or UniHumanoid.Humanoid", MessageType.Error);
                return false;
            }

            if (humanoid != null)
            {
                if (humanoid.Validate().Any())
                {
                    // 不正
                    EditorGUILayout.HelpBox("Please create humanoid avatar", MessageType.Error);
                    return false;
                }
            }

            return true;
        }

        static VRM10Expression CreateAndSaveExpression(ExpressionPreset preset, string dir, Vrm10Instance instance)
        {
            var prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(instance.gameObject);
            var clip = ScriptableObject.CreateInstance<VRM10Expression>();
            clip.name = preset.ToString();
            clip.Prefab = prefab;
            var path = System.IO.Path.Combine(dir, $"{preset}.asset");
            var unityPath = UnityPath.FromFullpath(path);
            unityPath.CreateAsset(clip);
            var loaded = unityPath.LoadAsset<VRM10Expression>();
            return loaded;
        }

        static string GetSaveName(Vrm10Instance instance)
        {
            if (instance == null)
            {
                return "Assets/vrm-1.0.assets";
            }

            if (PathObject.TryGetFromAsset(instance, out var asset))
            {
                return (asset.Parent.Child(instance.name + ".asset")).UnityAssetPath;
            }

            return $"Assets/{instance.name}.assets";
        }

        void SetupVRM10Object(Vrm10Instance instance)
        {
            if (!CheckHumanoid(instance.gameObject))
            {
                // can not
                return;
            }

            EditorGUILayout.HelpBox("Humanoid OK.", MessageType.Info);

            // VRM10Object
            var prop = serializedObject.FindProperty(nameof(Vrm10Instance.Vrm));
            if (prop.objectReferenceValue == null)
            {
                EditorGUILayout.HelpBox("No VRM10Object.", MessageType.Error);
            }
            if (GUILayout.Button("Create new VRM10Object and default Expressions. select target folder"))
            {
                var saveName = GetSaveName(instance);
                if (UniGltfEditorDialog.TryGetDir(SaveTitle, System.IO.Path.GetDirectoryName(saveName), out var dir))
                {
                    var expressions = new Dictionary<ExpressionPreset, VRM10Expression>();
                    foreach (ExpressionPreset expression in CachedEnum.GetValues<ExpressionPreset>())
                    {
                        if (expression == ExpressionPreset.custom)
                        {
                            continue;
                        }
                        expressions[expression] = CreateAndSaveExpression(expression, dir, instance);
                    }

                    var path = System.IO.Path.Combine(dir, (instance.name ?? "VRMObject") + ".asset");
                    var asset = CreateAsset(path, expressions, instance);
                    if (asset != null)
                    {
                        // update editor
                        serializedObject.Update();
                        prop.objectReferenceValue = asset;
                        serializedObject.ApplyModifiedProperties();
                    }
                }
            }
        }

        public override VisualElement CreateInspectorGUI()
        {
            var root = new VisualElement();

            root.Bind(serializedObject);
            {
                var s = new PropertyField { bindingPath = "m_Script" };
                s.SetEnabled(false);
                root.Add(s);
            }

            var tabs = new EnumField("select UI", default(Tab));
            root.Add(tabs);

            var body = new VisualElement();
            List<(Tab, VisualElement)> contents = new()
            {
                (Tab.VrmInstance, new IMGUIContainer(GUIVrmInstance)),
                (Tab.LookAt, new IMGUIContainer(GUILookAt)),
                (Tab.SpringBone, GUISpringBone()),
            };
            foreach (var (tab, content) in contents)
            {
                // content.visible = tab == Tab.VrmInstance;
                content.style.display = tab == Tab.VrmInstance
                    ? DisplayStyle.Flex
                    : DisplayStyle.None
                    ;
                body.Add(content);
            }
            root.Add(body);

            tabs.RegisterValueChangedCallback(e =>
            {
                var selected = (Tab)e.newValue;
                foreach (var (tab, content) in contents)
                {
                    // content.visible = tab == selected;
                    content.style.display = tab == selected
                        ? DisplayStyle.Flex
                        : DisplayStyle.None
                        ;
                }
            });

            return root;
        }

        void GUIVrmInstance()
        {
            if (m_instance.Vrm == null)
            {
                SetupVRM10Object(m_instance);
            }
            serializedObject.Update();
            EditorGUILayout.PropertyField(m_vrmObject);
            EditorGUILayout.PropertyField(m_updateType);
            serializedObject.ApplyModifiedProperties();
        }

        void GUILookAt()
        {
            serializedObject.Update();
            EditorGUILayout.PropertyField(m_drawLookatGizmo);
            EditorGUILayout.PropertyField(m_lookatTarget);
            EditorGUILayout.PropertyField(m_lookatTargetType);
            serializedObject.ApplyModifiedProperties();
        }

        VisualElement GUISpringBone()
        {
            var root = new VisualElement();

            root.Add(new PropertyField { bindingPath = CollidersPath });

            m_springs = new ListView
            {
                bindingPath = SpringsPath,
                makeItem = () =>
                {
                    return new Label();
                },
                bindItem = (v, i) =>
                {
                    var prop = serializedObject.FindProperty($"{SpringsPath}.Array.data[{i}].Name");
                    (v as Label).BindProperty(prop);
                },
            };
            m_springs.headerTitle = "Springs";
            m_springs.showFoldoutHeader = true;
            m_springs.showAddRemoveFooter = true;
            root.Add(m_springs);

            var selected = new PropertyField();
            m_springs.selectedIndicesChanged += (e) =>
            {
                var values = e.ToArray();
                if (values.Length > 0)
                {
                    var path = $"{SpringsPath}.Array.data[{values[0]}]";
                    var prop = serializedObject.FindProperty(path);
                    selected.BindProperty(prop);
                }
            };
            root.Add(selected);

            return root;
        }

        static IEnumerable<VRM10SpringBoneJoint> MakeJointsRecursive(VRM10SpringBoneJoint parent)
        {
            if (parent.transform.childCount > 0)
            {
                var child = parent.transform.GetChild(0);
                var joint = child.gameObject.GetOrAddComponent<VRM10SpringBoneJoint>();
                // set params
                joint.m_dragForce = parent.m_dragForce;
                joint.m_gravityDir = parent.m_gravityDir;
                joint.m_gravityPower = parent.m_gravityPower;
                joint.m_jointRadius = parent.m_jointRadius;
                joint.m_stiffnessForce = parent.m_stiffnessForce;

                yield return joint;
                foreach (var x in MakeJointsRecursive(joint))
                {
                    yield return x;
                }
            }
        }

        public void OnSceneGUI()
        {
            HandleUtility.Repaint();

            // 親指のガイド          
            DrawThumbGuide(target as Vrm10Instance);

            // 選択中の SpringBone
            if (m_springs != null && m_springs.selectedIndex >= 0 && m_springs.selectedIndex < m_instance.SpringBone.Springs.Count)
            {
                Handles.color = Color.red;
                var selected = m_instance.SpringBone.Springs[m_springs.selectedIndex];
                for (int i = 1; i < selected.Joints.Count; ++i)
                {
                    var head = selected.Joints[i - 1];
                    var tail = selected.Joints[i];
                    if (head != null && tail != null)
                    {
                        Handles.DrawLine(head.transform.position, tail.transform.position);
                    }
                }
            }
        }

        static void DrawThumbGuide(Vrm10Instance instance)
        {
            if (instance == null)
            {
                return;
            }
            if (instance.TryGetBoneTransform(HumanBodyBones.LeftThumbProximal, out var l0))
            {
                if (instance.TryGetBoneTransform(HumanBodyBones.LeftThumbIntermediate, out var l1))
                {
                    if (instance.TryGetBoneTransform(HumanBodyBones.LeftThumbDistal, out var l2))
                    {
                        var color = new Color(0.5f, 1.0f, 0.0f, 1.0f);
                        var thumbDir = (Vector3.forward + Vector3.left).normalized;
                        var nailNormal = (Vector3.forward + Vector3.right).normalized;
                        DrawThumbGuide(l0.position, l2.position, thumbDir, nailNormal, color);
                    }
                }
            }
            if (instance.TryGetBoneTransform(HumanBodyBones.RightThumbProximal, out var r0))
            {
                if (instance.TryGetBoneTransform(HumanBodyBones.RightThumbIntermediate, out var r1))
                {
                    if (instance.TryGetBoneTransform(HumanBodyBones.RightThumbDistal, out var r2))
                    {
                        var color = new Color(0.5f, 1.0f, 0.0f, 1.0f);
                        var thumbDir = (Vector3.forward + Vector3.right).normalized;
                        var nailNormal = (Vector3.forward + Vector3.left).normalized;
                        DrawThumbGuide(r0.position, r2.position, thumbDir, nailNormal, color);
                    }
                }
            }
        }
        static void DrawThumbGuide(Vector3 metacarpalPos, Vector3 distalPos, Vector3 thumbDir, Vector3 nailNormal, Color color)
        {
            Handles.color = color;
            Handles.matrix = Matrix4x4.identity;

            var thumbVector = distalPos - metacarpalPos;
            var thumbLength = thumbVector.magnitude * 1.5f;
            var thickness = thumbLength * 0.1f;
            var tipCenter = metacarpalPos + thumbDir * (thumbLength - thickness);
            var crossVector = Vector3.Cross(thumbDir, nailNormal);

            // 指の形を描く
            Handles.DrawLine(metacarpalPos + crossVector * thickness, tipCenter + crossVector * thickness);
            Handles.DrawLine(metacarpalPos - crossVector * thickness, tipCenter - crossVector * thickness);
            Handles.DrawWireArc(tipCenter, nailNormal, crossVector, 180f, thickness);

            Handles.DrawLine(metacarpalPos + nailNormal * thickness, tipCenter + nailNormal * thickness);
            Handles.DrawLine(metacarpalPos - nailNormal * thickness, tipCenter - nailNormal * thickness);
            Handles.DrawWireArc(tipCenter, crossVector, -nailNormal, 180f, thickness);

            Handles.DrawWireDisc(metacarpalPos, thumbDir, thickness);
            Handles.DrawWireDisc(tipCenter, thumbDir, thickness);

            // 爪の方向に伸びる線を描く
            Handles.DrawDottedLine(tipCenter, tipCenter + nailNormal * thickness * 8.0f, 1.0f);

            // 爪を描く
            Vector2[] points2 = {
                new Vector2(-0.2f, -0.5f),
                new Vector2(0.2f, -0.5f),
                new Vector2(0.5f, -0.3f),
                new Vector2(0.5f, 0.3f),
                new Vector2(0.2f, 0.5f),
                new Vector2(-0.2f, 0.5f),
                new Vector2(-0.5f, 0.3f),
                new Vector2(-0.5f, -0.3f),
                new Vector2(-0.2f, -0.5f),
            };
            Vector3[] points = points2
                .Select(v => tipCenter + (nailNormal + crossVector * v.x + thumbDir * v.y) * thickness)
                .ToArray();

            Handles.DrawAAPolyLine(points);
            Handles.color = color * new Color(1.0f, 1.0f, 1.0f, 0.1f);
            Handles.DrawAAConvexPolygon(points);

            // 文字ラベルを描く
            Handles.Label(tipCenter + nailNormal * thickness * 6.0f, "Thumb nail direction");
        }
    }
}
