﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Dynamics.PhysBone.Components;


namespace Fluff_Toolbox.Views.VRChat {
    public class ConvertPhysToDync : EditorWindow {

#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS
        class physicsBoneListObject {
            private bool Toggle;
            private VRCPhysBone Bone;

            public physicsBoneListObject(VRCPhysBone b) {
                Toggle = true;
                Bone = b;
            }

            public physicsBoneListObject(VRCPhysBone b, bool t) {
                Toggle = t;
                Bone = b;
            }

            public bool isToggled() {
                return Toggle;
            }

            public void setToggled(bool b) {
                Toggle = b;
            }

            public VRCPhysBone getPhysicsBone() {
                return Bone;
            }
        }
#endif


        GameObject Avatar;
        GameObject PrevAvatar;

        [MenuItem("Fluffs Toolbox/Dynamic Bones/Convert Physics to Dynamic Bones", false, 0)]
        private static void ShowConverterWindow() {
#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS
            if (File.Exists("Assets/Fluff Toolbox/Commercial.txt") || File.Exists("Assets/Fluff Toolbox/Personal.txt")) {
                isUsingFluffToolbox = true;
            }
#endif
            GetWindow<ConvertPhysToDync>("Converter");
        }

        public void reset() {
#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS
            customSetup = false;

            resetPhysicsBoneList();


            searchText = "Hair";

            dampingX = 0.5f;
            elasticityX = 0.8f;
            stiffnessX = 0.2f;
            inertX = 1f;
            gravityForceMultiplier = 10f;

            replaceOnOriginal = false;
#endif
        }

#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS
        static bool isUsingFluffToolbox;
        Vector2 windowscroll = new Vector2(0, 0);
        Vector2 listscroll = new Vector2(0, 0);

        bool customSetup = false;

        List<physicsBoneListObject> objList = new List<physicsBoneListObject>();


        string searchText = "Hair";

        float dampingX = 0.5f;
        float elasticityX = 0.8f;
        float stiffnessX = 0.2f;
        float inertX = 1f;
        float gravityForceMultiplier = 10f;

        int gravityOption = 0;

        bool replaceOnOriginal = false;

#endif
        void OnGUI() {
            this.minSize = new Vector2(500, 400);

#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Avatar Prefab: ", EditorStyles.boldLabel, new GUILayoutOption[] { GUILayout.Width(90) });
            PrevAvatar = Avatar;
            Avatar = (GameObject)EditorGUILayout.ObjectField("", Avatar, typeof(GameObject), true);
            if (GUILayout.Button("Select From Scene", new GUILayoutOption[] { GUILayout.Width(120) }))
                selectAvatarFromScene();
            EditorGUILayout.EndHorizontal();

            if (Avatar == null) return;

            if (PrevAvatar != Avatar) {
                resetPhysicsBoneList();
            }

            if (GUILayout.Button("Reset Default")) {
                reset();
            }

            windowscroll = EditorGUILayout.BeginScrollView(windowscroll);
            EditorGUILayout.BeginVertical(new GUIStyle() { clipping = TextClipping.Clip });


            EditorGUILayout.LabelField("Divide: (Warning: Stay in 0-1 range above might make it break it)", EditorStyles.boldLabel);
            EditorGUILayout.LabelField("Warning doesn't apply for Gravity/Force");
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Momentum: ", new GUILayoutOption[] { GUILayout.Width(130) });
            dampingX = EditorGUILayout.FloatField(dampingX);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Pull: ", new GUILayoutOption[] { GUILayout.Width(130) });
            elasticityX = EditorGUILayout.FloatField(elasticityX);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Stiffness: ", new GUILayoutOption[] { GUILayout.Width(130) });
            stiffnessX = EditorGUILayout.FloatField(stiffnessX);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Immobile: ", new GUILayoutOption[] { GUILayout.Width(130) });
            inertX = EditorGUILayout.FloatField(inertX);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Gravity: ", new GUILayoutOption[] { GUILayout.Width(130) });
            gravityForceMultiplier = EditorGUILayout.FloatField(gravityForceMultiplier);
            EditorGUILayout.EndHorizontal();


            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Options: ", EditorStyles.boldLabel);
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Gravity Or Force: ", new GUILayoutOption[] { GUILayout.Width(130) });
            gravityOption = EditorGUILayout.Popup(gravityOption, new string[2] { "Gravity", "Force" });
            EditorGUILayout.EndHorizontal();

            bool isNotPrefab = PrefabUtility.GetPrefabAssetType(Avatar) == PrefabAssetType.NotAPrefab;

            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Advanced Settings", EditorStyles.boldLabel, new GUILayoutOption[] { GUILayout.Width(120) });

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Replace On Selected: ", new GUILayoutOption[] { GUILayout.Width(130) });
            replaceOnOriginal = EditorGUILayout.Toggle(replaceOnOriginal, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.EndHorizontal();


            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Selective Convert: ", new GUILayoutOption[] { GUILayout.Width(130) });
            customSetup = EditorGUILayout.Toggle(customSetup, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.LabelField("Select specific physics bones objects to convert.");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.EndVertical();

            if (customSetup) {
                EditorGUILayout.Space();
                EditorGUILayout.LabelField("Selective Physics Bones: (Only shows Physics Bones remaining on the object)", EditorStyles.boldLabel);
                EditorGUILayout.BeginVertical(new GUIStyle() { clipping = TextClipping.Clip, fixedHeight = (objList.Count > 15 ? 300 : objList.Count * 15) });
                
                if (objList.Count > 0) {
                    GUILayout.BeginHorizontal();
                    if (GUILayout.Button("Select All", new GUILayoutOption[] { GUILayout.Width(80) }))
                        foreach (physicsBoneListObject o in objList)
                            o.setToggled(true);
                        

                    if (GUILayout.Button("Select None", new GUILayoutOption[] { GUILayout.Width(80) }))
                        foreach (physicsBoneListObject o in objList)
                            o.setToggled(false);

                    GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.ExpandHeight(false), GUILayout.Height(-1) });
                    EditorGUILayout.LabelField("Search:", new GUILayoutOption[] { GUILayout.Width(45f) });
                    searchText = EditorGUILayout.TextField(searchText);
                    if (GUILayout.Button("Select", new GUILayoutOption[] { GUILayout.Width(65f) })) 
                        foreach (physicsBoneListObject b in objList)
                            if (b.getPhysicsBone().transform.name.ToLower().Contains(searchText.ToLower()))
                                b.setToggled(true);
                    
                    if (GUILayout.Button("Deselect", new GUILayoutOption[] { GUILayout.Width(65f) })) 
                        foreach (physicsBoneListObject b in objList)
                            if (b.getPhysicsBone().transform.name.ToLower().Contains(searchText.ToLower()))
                                b.setToggled(false);
                    
                    GUILayout.EndHorizontal();

                    GUILayout.EndHorizontal();
                    listscroll = EditorGUILayout.BeginScrollView(listscroll);
                    foreach (physicsBoneListObject obj in objList) {
                        EditorGUILayout.BeginHorizontal();
                        obj.setToggled(EditorGUILayout.Toggle(obj.isToggled(), new GUILayoutOption[] { GUILayout.Width(30) }));
                        EditorGUI.BeginDisabledGroup(true);
                        EditorGUILayout.ObjectField(obj.getPhysicsBone().transform.gameObject, typeof(GameObject), true);
                        EditorGUI.EndDisabledGroup();
                        EditorGUILayout.EndHorizontal();
                    }
                    EditorGUILayout.EndScrollView();

                    EditorGUILayout.Space();
                    if (Avatar.GetComponentInChildren<VRCPhysBoneCollider>() != null) {
                        makeWarning("This avatar still contains Physics Bone Colliders!\nThese only get automatically removed when your converted ALL physics bones to dynamic bones!", MessageType.Warning, delegate () {
                            EditorGUI.BeginDisabledGroup(!isNotPrefab);
                            if (GUILayout.Button("Remove", new GUILayoutOption[] { GUILayout.ExpandHeight(true) }))
                                foreach (VRCPhysBoneCollider dbc in Avatar.GetComponentsInChildren<VRCPhysBoneCollider>())
                                    DestroyImmediate(dbc);
                            EditorGUI.EndDisabledGroup();
                        });
                    } else {
                        makeWarning("Avatar doesn't contain any Physics Bone Colliders anymore.", MessageType.Info, delegate () { });
                    }
                } else {
                    EditorGUILayout.LabelField("No Physics Bones found on this Avatar!", EditorStyles.boldLabel);
                }

                EditorGUILayout.EndVertical();
            }


            makeWarning("This converter doesn't convert it over 100%! Meaning adjustment needs to be made!", MessageType.Warning, delegate () { });

            if (!isUsingFluffToolbox) {
                makeWarning("Check out my other work at fluffs.gumroad.com", MessageType.Info, delegate () { });
            }


            if (GUILayout.Button("Convert"))
                convertPhysicsBonesToPhysicsBones();
            EditorGUILayout.EndScrollView();




#else
makeWarning("Dynamic Bones or SDK3 isn't imported!", MessageType.Error, delegate () {});

#endif

        }

#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS

        public void resetPhysicsBoneList() {
            if (Avatar == null) return;

            objList.Clear();
            VRCPhysBone[] physicsBonesScripts = Avatar.GetComponentsInChildren<VRCPhysBone>(true);
            foreach (VRCPhysBone dbs in physicsBonesScripts) {
                objList.Add(new physicsBoneListObject(dbs));
            }
        }

        public void convertPhysicsBonesToPhysicsBones() {
            if (Avatar == null) return;

            List<physicsBoneListObject> listO = objList;
            GameObject newAvi = Avatar;

            if (customSetup == false) resetPhysicsBoneList();

            if (replaceOnOriginal == false) {
                newAvi = GameObject.Instantiate(Avatar);
                newAvi.name = Avatar.name + " Dynamic Bones";
            }

            VRCPhysBoneCollider[] physicsbonesColliders = newAvi.GetComponentsInChildren<VRCPhysBoneCollider>(true);
            //only update when the are not colliders on it yet!
            if (newAvi.GetComponentInChildren<DynamicBoneCollider>() == null)
                foreach (VRCPhysBoneCollider physboneCollider in physicsbonesColliders) {
                    if (physboneCollider.shapeType == VRC.Dynamics.VRCPhysBoneColliderBase.ShapeType.Plane) {
                        DynamicBonePlaneCollider dbc = physboneCollider.gameObject.AddComponent<DynamicBonePlaneCollider>();
                        dbc.m_Center = physboneCollider.position;
                        dbc.m_Direction = physboneCollider.rotation.x > physboneCollider.rotation.y && physboneCollider.rotation.x > physboneCollider.rotation.z ? DynamicBoneCollider.Direction.Z : physboneCollider.rotation.z > physboneCollider.rotation.y && physboneCollider.rotation.z > physboneCollider.rotation.x ? DynamicBoneCollider.Direction.X : DynamicBoneCollider.Direction.Y;
                        dbc.m_Bound = physboneCollider.insideBounds ? DynamicBoneCollider.Bound.Inside : DynamicBoneCollider.Bound.Outside;
                    } else {
                        DynamicBoneCollider dbc = physboneCollider.gameObject.AddComponent<DynamicBoneCollider>();

                        if (physboneCollider.shapeType == VRC.Dynamics.VRCPhysBoneColliderBase.ShapeType.Capsule)
                            dbc.m_Height = physboneCollider.height;

                        dbc.m_Radius = physboneCollider.radius;
                        dbc.m_Bound = physboneCollider.insideBounds ? DynamicBoneCollider.Bound.Inside : DynamicBoneCollider.Bound.Outside;

                        dbc.m_Center = physboneCollider.position;

                        if (physboneCollider.height > 0)
                            dbc.m_Direction = physboneCollider.rotation.x > physboneCollider.rotation.y && physboneCollider.rotation.x > physboneCollider.rotation.z ? DynamicBoneCollider.Direction.Z : physboneCollider.rotation.z > physboneCollider.rotation.y && physboneCollider.rotation.z > physboneCollider.rotation.x ? DynamicBoneCollider.Direction.X : DynamicBoneCollider.Direction.Y;
                    }
                    
                }



            VRCPhysBone[] physicsBonesScripts = newAvi.GetComponentsInChildren<VRCPhysBone>(true);
            if (!replaceOnOriginal) {
                listO = new List<physicsBoneListObject>();
                for (int i = 0; i < physicsBonesScripts.Count(); i++)
                    listO.Add(new physicsBoneListObject(physicsBonesScripts[i], objList[i].isToggled()));
            }

            foreach (VRCPhysBone physbone in physicsBonesScripts) {
                if (customSetup && listO.Any(o => o.getPhysicsBone() == physbone && !o.isToggled())) continue;

                List<DynamicBone> bonesAdded = new List<DynamicBone>(); 

                Func<DynamicBone> convert = delegate () {
                    DynamicBone dbs = physbone.transform.gameObject.AddComponent<DynamicBone>();

                    bonesAdded.Add(dbs);

                    dbs.m_Root = physbone.rootTransform == null ? physbone.transform : physbone.rootTransform;

                    dbs.m_Exclusions = new List<Transform>();

                    //need foreach else it links... (unity is retarded thx)
                    foreach (Transform transform in physbone.ignoreTransforms)
                        if (transform != null) dbs.m_Exclusions.Add(transform);


                    dbs.m_Radius = physbone.radius;
                    dbs.m_RadiusDistrib = physbone.radiusCurve;

                    //reverse calculate endpointposition 

                    
                    if (dbs.m_EndLength == 0) {
                        Func<Transform, Vector3> getOriginalScale = null;

                        getOriginalScale = delegate (Transform x) {
                            if (x.parent == null) return new Vector3(1 * x.localScale.x, 1 * x.localScale.y, 1 * x.localScale.z); ;
                            Vector3 r = getOriginalScale(x.parent);
                            return new Vector3(r.x * x.localScale.x, r.y * x.localScale.y, r.z * x.localScale.z);
                        };

                        Vector3 ofsc = getOriginalScale(dbs.transform.gameObject.transform);
                        dbs.m_EndOffset = new Vector3(physbone.endpointPosition.x * ofsc.x, physbone.endpointPosition.y * ofsc.y, physbone.endpointPosition.z * ofsc.z);
                        
                    }

                    dbs.m_Colliders = new List<DynamicBoneColliderBase>();
                    dbs.m_Colliders.Clear(); //done
                    foreach (VRCPhysBoneCollider col in physbone.colliders)
                        if (col != null && col.gameObject.GetComponent<DynamicBoneCollider>() != null)
                            dbs.m_Colliders.Add(col.gameObject.GetComponent<DynamicBoneCollider>());


                    dbs.m_Elasticity = physbone.pull * (1 / elasticityX); 
                    dbs.m_ElasticityDistrib = physbone.pullCurve; 

                    dbs.m_Damping = (-physbone.spring + 1) * (1/dampingX); 
                    dbs.m_DampingDistrib = physbone.springCurve; 
                    
                    dbs.m_Stiffness = physbone.stiffness * (1 / stiffnessX); 
                    dbs.m_StiffnessDistrib = physbone.stiffnessCurve; 

                    dbs.m_Inert = physbone.immobile * (1 / inertX); 
                    dbs.m_InertDistrib = physbone.immobileCurve; 

                    if (gravityOption == 0) {
                        dbs.m_Gravity = new Vector3(0, -physbone.gravity / gravityForceMultiplier, 0); 
                    } else {
                        dbs.m_Force = new Vector3(0, -physbone.gravity / gravityForceMultiplier, 0); 
                    }

                    return dbs;
                };
                
                convert();
            }

            foreach (VRCPhysBone dbs in physicsBonesScripts) {
                if (customSetup && listO.Any(o => o.getPhysicsBone() == dbs && !o.isToggled())) continue;
                DestroyImmediate(dbs);
            }

            if (customSetup) {
                if (replaceOnOriginal == false) {
                    Avatar = newAvi;
                    replaceOnOriginal = true;
                }
                resetPhysicsBoneList();
            }

            if (newAvi.GetComponentInChildren<VRCPhysBone>() == null)
                foreach (VRCPhysBoneCollider dbc in physicsbonesColliders) {
                    DestroyImmediate(dbc);
                }
        }

#endif
        public void selectAvatarFromScene() {
            if (Selection.activeGameObject != null) this.Avatar = Selection.activeGameObject;
        }


        public void makeWarning(string message, MessageType type, Action a) {
            GUILayout.BeginHorizontal(new GUILayoutOption[] {
                GUILayout.ExpandHeight(true),
                GUILayout.Height(-1)
        });

            EditorGUILayout.HelpBox(message, type);
            a();

            GUILayout.EndHorizontal();

        }
    }
}
