﻿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 ConvertDyncToPhys : EditorWindow {

#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS
        class dynamicBoneListObject {
            private bool Toggle;
            private DynamicBone Bone;

            public dynamicBoneListObject(DynamicBone b) {
                Toggle = true;
                Bone = b;
            }

            public dynamicBoneListObject(DynamicBone b, bool t) {
                Toggle = t;
                Bone = b;
            }

            public bool isToggled() {
                return Toggle;
            }

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

            public DynamicBone getDynamicBone() {
                return Bone;
            }
        }
#endif


        GameObject Avatar;
        GameObject PrevAvatar;

        [MenuItem("Fluffs Toolbox/Dynamic Bones/Convert Dynamic to Physics 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<ConvertDyncToPhys>("Converter");
        }

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

            resetDynamicBoneList();


            searchText = "Hair";

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

            grabbable = false;
            possing = false;
            collisions = true;
            isAnimated = false;

            grabmovement = 0.5f;
            maxstretch = 0f;
            maxstretchCurve = new AnimationCurve(new Keyframe[2] { new Keyframe(0, 1), new Keyframe(1, 1) });

            addEndBone = true;
            applyRootBoneFix = true;
            useOffset = true;
            originalColliders = true;
            replaceOnOriginal = false;
            tinyBoneFix = false;
            OneScriptPerBone = true;

            limitIndex = 0;
            maxPitch = 45f;
            maxYaw = 45f;
#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<dynamicBoneListObject> objList = new List<dynamicBoneListObject>();


        string searchText = "Hair";

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

        bool grabbable = false;
        bool possing = false;
        bool collisions = true;
        bool isAnimated = false;

        float grabmovement = 0.5f;
        float maxstretch = 0;
        AnimationCurve maxstretchCurve = new AnimationCurve(new Keyframe[2] { new Keyframe(0, 1), new Keyframe(1, 1) });

        bool addEndBone = true;
        bool applyRootBoneFix = true;
        bool useOffset = true;
        bool originalColliders = true;
        bool replaceOnOriginal = false;
        bool tinyBoneFix = false;
        bool OneScriptPerBone = true;

        int limitIndex = 0;
        float maxPitch = 45f;
        float maxYaw = 45f;
#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) {
                resetDynamicBoneList();
            }

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

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


            EditorGUILayout.LabelField("Multiplier: (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("Damping: ", new GUILayoutOption[] { GUILayout.Width(130) });
            dampingX = EditorGUILayout.FloatField(dampingX);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Elasticity: ", 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("Inert: ", new GUILayoutOption[] { GUILayout.Width(130) });
            inertX = EditorGUILayout.FloatField(inertX);
            EditorGUILayout.EndHorizontal();

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

            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Options: (Warning: these option will effect performance!)", EditorStyles.boldLabel);
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Allow Collision: ", new GUILayoutOption[] { GUILayout.Width(130) });
            collisions = EditorGUILayout.Toggle(collisions, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.LabelField("Allow players to collide with the bone (touch/move)!");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Grabbable: ", new GUILayoutOption[] { GUILayout.Width(130) });
            grabbable = EditorGUILayout.Toggle(grabbable, new GUILayoutOption[] { GUILayout.Width(30) });
            if (!grabbable) possing = false;
            EditorGUILayout.LabelField("Bones are able to be grabbed and moved!");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Posing: ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(!grabbable);
            possing = EditorGUILayout.Toggle(possing, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUI.EndDisabledGroup();
            if (!grabbable) {
                EditorGUILayout.LabelField("Grabbable needs to be enabled!");
            } else {
                EditorGUILayout.LabelField("Bones can be posed and stay in that pose until release!");
            }
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Is Animated: ", new GUILayoutOption[] { GUILayout.Width(130) });
            isAnimated = EditorGUILayout.Toggle(isAnimated, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.LabelField("When you got animations that changes the bones!");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Grab & Pose Settings: (Warning: Grabbable needs to be enabled!)", EditorStyles.boldLabel);
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Grab Movement: ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(!grabbable);
            grabmovement = EditorGUILayout.Slider(grabmovement, 0, 1);
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Max Stretch: ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(!grabbable);
            maxstretch = EditorGUILayout.Slider(maxstretch, 0, 5);
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Max Stretch Curve:", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(!grabbable);
            maxstretchCurve = EditorGUILayout.CurveField(maxstretchCurve, Color.cyan, new Rect(0, 0, 1, 1));
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Limits Options: (Warning: Some options are disabled depending on selection!)", EditorStyles.boldLabel);

            limitIndex = EditorGUILayout.Popup(limitIndex, Enum.GetNames(typeof(VRCPhysBone.LimitType)));

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Max " + (limitIndex == 3 ? "Pitch" : "Angle") + ": ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(limitIndex == 0);
            maxPitch = EditorGUILayout.Slider(maxPitch, 0, 180);
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();


            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Max Yaw: ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(limitIndex != 3);
            maxYaw = EditorGUILayout.Slider(maxYaw, 0, 90);
            EditorGUI.EndDisabledGroup();
            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("Add Endbones: ", new GUILayoutOption[] { GUILayout.Width(130) });
            addEndBone = EditorGUILayout.Toggle(addEndBone, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Apply Rootbone Fix: ", new GUILayoutOption[] { GUILayout.Width(130) });
            applyRootBoneFix = EditorGUILayout.Toggle(applyRootBoneFix, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUI.BeginDisabledGroup(true);
            EditorGUILayout.LabelField("Apply Tinybone Fix: ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.EndDisabledGroup();
            tinyBoneFix = EditorGUILayout.Toggle(tinyBoneFix, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.LabelField("Deprecated, being fixed in Avatar SDK 2022.04.26.15.46");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Use Offset: ", new GUILayoutOption[] { GUILayout.Width(130) });
            useOffset = EditorGUILayout.Toggle(useOffset, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Original Collider Setup: ", new GUILayoutOption[] { GUILayout.Width(130) });
            originalColliders = EditorGUILayout.Toggle(originalColliders, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.EndHorizontal();


            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("One Script Per Bone: ", new GUILayoutOption[] { GUILayout.Width(130) });
            OneScriptPerBone = EditorGUILayout.Toggle(OneScriptPerBone, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUILayout.LabelField("Multiple script per bone can cause issues for animating!");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Replace On Selected: ", new GUILayoutOption[] { GUILayout.Width(130) });
            EditorGUI.BeginDisabledGroup(applyRootBoneFix);
            replaceOnOriginal = EditorGUILayout.Toggle(replaceOnOriginal, new GUILayoutOption[] { GUILayout.Width(30) });
            EditorGUI.EndDisabledGroup();
            if (applyRootBoneFix) replaceOnOriginal = false;
            EditorGUILayout.LabelField((applyRootBoneFix ? "Can't be enabled while Apply Rootbone Fix is enabled." : "Replaces it on the selected Avatar!"));
            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 dynamic bones objects to convert.");
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.EndVertical();

            if (customSetup) {
                EditorGUILayout.Space();
                EditorGUILayout.LabelField("Selective DynamicBones: (Only shows Dynamic 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 (dynamicBoneListObject o in objList)
                            o.setToggled(true);
                        

                    if (GUILayout.Button("Select None", new GUILayoutOption[] { GUILayout.Width(80) }))
                        foreach (dynamicBoneListObject 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 (dynamicBoneListObject b in objList)
                            if (b.getDynamicBone().transform.name.ToLower().Contains(searchText.ToLower()))
                                b.setToggled(true);
                    
                    if (GUILayout.Button("Deselect", new GUILayoutOption[] { GUILayout.Width(65f) })) 
                        foreach (dynamicBoneListObject b in objList)
                            if (b.getDynamicBone().transform.name.ToLower().Contains(searchText.ToLower()))
                                b.setToggled(false);
                    
                    GUILayout.EndHorizontal();

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

                    EditorGUILayout.Space();
                    if (Avatar.GetComponentInChildren<DynamicBoneCollider>() != null) {
                        makeWarning("This avatar still contains Dynamic Bone Colliders!\nThese only get automatically removed when your converted ALL dynamic bones! In the case you wanna keep a couple dynamic bones use this button to remove the dynamic bone colliders after your done!, this can't be undone!", MessageType.Warning, delegate () {
                            EditorGUI.BeginDisabledGroup(!isNotPrefab);
                            if (GUILayout.Button("Remove", new GUILayoutOption[] { GUILayout.ExpandHeight(true) }))
                                foreach (DynamicBoneCollider dbc in Avatar.GetComponentsInChildren<DynamicBoneCollider>())
                                    DestroyImmediate(dbc);
                            EditorGUI.EndDisabledGroup();
                        });
                    } else {
                        makeWarning("Avatar doesn't contain any Dynamic Bone Colliders anymore.", MessageType.Info, delegate () { });
                    }
                } else {
                    EditorGUILayout.LabelField("No Dynamic 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"))
                convertDynamicBonesToPhysicsBones();
            EditorGUILayout.EndScrollView();




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

#endif

        }

        public AnimationCurve rescaledCurve(AnimationCurve c) {
            AnimationCurve c2 = new AnimationCurve();
            float scale = 1.0f / (c.keys[c.length - 1].time - c.keys[0].time);
            float high = 0f;
            float low = 0f;

            for (int i = 0; i < c.length; i++) {
                if (c.keys[i].value < low) low = c.keys[i].value;
                if (c.keys[i].value > high) high = c.keys[i].value;
            }

            Debug.Log(low + " - " + high);
            float scale2 = Math.Abs(1f / (high - low));

            for (int i = 0; i < c.length; i++) {
                c2.AddKey(new Keyframe((Math.Abs(c.keys[0].time) + c.keys[i].time) * scale, (c.keys[i].value - low) * scale2, c.keys[i].inTangent, c.keys[i].outTangent));
            }

            return c2;
        }

#if VRC_SDK_VRCSDK3 && DYNAMICBONES_EXISTS

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

            objList.Clear();
            DynamicBone[] dynamicBonesScripts = Avatar.GetComponentsInChildren<DynamicBone>(true);
            foreach (DynamicBone dbs in dynamicBonesScripts) {
                objList.Add(new dynamicBoneListObject(dbs));
            }
        }

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

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

            if (customSetup == false) resetDynamicBoneList();

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

            List<Transform> avatarBones = new List<Transform>();
            Animator animator = newAvi.GetComponent<Animator>();
            if (animator != null && animator.isHuman) {
                foreach (HumanBodyBones b in Enum.GetValues(typeof(HumanBodyBones)))
                    if (b != HumanBodyBones.LastBone && animator.GetBoneTransform(b) != null)
                        avatarBones.Add(animator.GetBoneTransform(b));

            }

            DynamicBoneCollider[] dynamicBonesColliders = newAvi.GetComponentsInChildren<DynamicBoneCollider>(true);
            //only update when the are not colliders on it yet!
            if (newAvi.GetComponentInChildren<VRCPhysBoneCollider>() == null)
                foreach (DynamicBoneCollider dbc in dynamicBonesColliders) {

                    if (dbc.m_Height > 0 && originalColliders) {
                        VRCPhysBoneCollider physboneCollider = dbc.gameObject.AddComponent<VRCPhysBoneCollider>();
                        VRCPhysBoneCollider physboneCollider2 = dbc.gameObject.AddComponent<VRCPhysBoneCollider>();

                        physboneCollider.shapeType = VRC.Dynamics.VRCPhysBoneColliderBase.ShapeType.Sphere;
                        physboneCollider.position = dbc.m_Center + new Vector3(dbc.m_Direction == DynamicBoneCollider.Direction.X ? dbc.m_Height / 2 - dbc.m_Radius : 0, dbc.m_Direction == DynamicBoneCollider.Direction.Y ? dbc.m_Height / 2 - dbc.m_Radius : 0, dbc.m_Direction == DynamicBoneCollider.Direction.Z ? dbc.m_Height / 2 - dbc.m_Radius : 0);

                        physboneCollider2.shapeType = VRC.Dynamics.VRCPhysBoneColliderBase.ShapeType.Sphere;
                        physboneCollider2.position = dbc.m_Center + new Vector3(dbc.m_Direction == DynamicBoneCollider.Direction.X ? -(dbc.m_Height / 2 - dbc.m_Radius) : 0, dbc.m_Direction == DynamicBoneCollider.Direction.Y ? -(dbc.m_Height / 2 - dbc.m_Radius) : 0, dbc.m_Direction == DynamicBoneCollider.Direction.Z ? -(dbc.m_Height / 2 - dbc.m_Radius) : 0);

                        physboneCollider.radius = dbc.m_Radius;
                        physboneCollider.insideBounds = dbc.m_Bound == DynamicBoneCollider.Bound.Inside;
                        physboneCollider.rootTransform = dbc.transform;

                        physboneCollider2.radius = dbc.m_Radius;
                        physboneCollider2.insideBounds = dbc.m_Bound == DynamicBoneCollider.Bound.Inside;
                        physboneCollider2.rootTransform = dbc.transform;

                        physboneCollider.bonesAsSpheres = true;
                        physboneCollider2.bonesAsSpheres = true;

                    } else {
                        VRCPhysBoneCollider physboneCollider = dbc.gameObject.AddComponent<VRCPhysBoneCollider>();

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

                        physboneCollider.radius = dbc.m_Radius;
                        physboneCollider.insideBounds = dbc.m_Bound == DynamicBoneCollider.Bound.Inside;
                        physboneCollider.rootTransform = dbc.transform;

                        if (originalColliders) physboneCollider.bonesAsSpheres = true;

                        physboneCollider.position = dbc.m_Center;
                        physboneCollider.rotation = Quaternion.Euler(new Vector3(dbc.m_Direction == DynamicBoneCollider.Direction.Z ? 90 : 0, 0, dbc.m_Direction == DynamicBoneCollider.Direction.X ? 90 : 0));

                    }




                    //check direction?
                    //plane?
                }

            int c = 0;

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

            foreach (DynamicBone dbs in dynamicBonesScripts) {
                if (customSetup && listO.Any(o => o.getDynamicBone() == dbs && !o.isToggled())) continue;

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

                Func<GameObject, VRCPhysBone> convert = delegate (GameObject root) {
                    VRCPhysBone physbone = root.AddComponent<VRCPhysBone>();
                    bonesAdded.Add(physbone);

                    physbone.rootTransform = (applyRootBoneFix && dbs.m_Root == dbs.transform.gameObject ? root.transform : dbs.m_Root);

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


                    physbone.allowGrabbing = grabbable;
                    physbone.allowPosing = possing;
                    physbone.allowCollision = collisions;

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

                    physbone.limitType = VRC.Dynamics.VRCPhysBoneBase.LimitType.None;
                    physbone.integrationType = VRC.Dynamics.VRCPhysBoneBase.IntegrationType.Advanced;

                    if (useOffset && 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(root.transform);
                        physbone.endpointPosition = new Vector3(dbs.m_EndOffset.x / ofsc.x, dbs.m_EndOffset.y / ofsc.y, dbs.m_EndOffset.z / ofsc.z);
                    }

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


                    physbone.pull = dbs.m_Elasticity * elasticityX;
                    physbone.pullCurve = dbs.m_ElasticityDistrib;

                    physbone.spring = 1 - dbs.m_Damping * dampingX;
                    physbone.springCurve = dbs.m_DampingDistrib;

                    physbone.stiffness = dbs.m_Stiffness * stiffnessX;
                    physbone.stiffnessCurve = dbs.m_StiffnessDistrib;

                    physbone.immobile = dbs.m_Inert * inertX;
                    physbone.immobileCurve = dbs.m_InertDistrib;

                    physbone.isAnimated = isAnimated;

                    if (limitIndex > 0) {
                        physbone.limitType = (VRCPhysBone.LimitType)Enum.GetValues(typeof(VRCPhysBone.LimitType)).GetValue(limitIndex);
                        physbone.maxAngleX = maxPitch;
                        if (limitIndex == 3) {
                            physbone.maxAngleZ = maxYaw;
                        }
                    }

                    if (grabbable) {
                        physbone.grabMovement = grabmovement;
                        physbone.maxStretch = maxstretch;
                        physbone.maxStretchCurve = maxstretchCurve;
                    }

                    physbone.gravity = (dbs.m_Gravity.y != 0 ? -dbs.m_Gravity.y * gravityForceMultiplier : -dbs.m_Force.y * gravityForceMultiplier);
                    return physbone;
                };

                List<Transform> fixedList = new List<Transform>();
                foreach (Transform transf in dbs.m_Exclusions)
                    if (transf != null) fixedList.Add(transf);

                GameObject newRoot = null;
                if (applyRootBoneFix && (dbs.m_Root.childCount - fixedList.Count) > 1) {
                    newRoot = new GameObject(dbs.m_Root.name + "_Root");
                    newRoot.transform.parent = dbs.m_Root;
                    newRoot.transform.localPosition = new Vector3(0, 0, 0);
                    newRoot.transform.localScale = new Vector3(1, 1, 1);
                    newRoot.transform.localRotation = Quaternion.Euler(0, 0, 0);

                    List<Transform> objToMove = new List<Transform>();
                    for (int i = 0; i < dbs.m_Root.childCount; i++) {
                        //only make script if child isn't excluded
                        if (!dbs.m_Exclusions.Contains(dbs.m_Root.GetChild(i)) && dbs.m_Root.GetChild(i) != newRoot.transform && dbs.GetComponent<VRCPhysBone>() == null) {

                            if (avatarBones.Contains(dbs.m_Root.GetChild(i))) continue;

                            VRCPhysBone physBone = convert(newRoot);
                            for (int i2 = 0; i2 < dbs.m_Root.childCount; i2++)
                                if (i != i2 && !dbs.m_Exclusions.Contains(dbs.m_Root.GetChild(i2)) && dbs.m_Root.GetChild(i) != newRoot.transform && dbs.GetComponent<VRCPhysBone>() == null) {
                                    if (avatarBones.Contains(dbs.m_Root.GetChild(i2))) continue;
                                    physBone.ignoreTransforms.Add(dbs.m_Root.GetChild(i2));
                                }
                            objToMove.Add(dbs.m_Root.GetChild(i));
                        }
                    }

                    foreach (Transform T in objToMove) {
                        T.parent = newRoot.transform;
                    }

                } else {
                    //do normal way
                    convert(dbs.m_Root.gameObject);
                }

                //recursive adding addbone only when reach end of bone~
                if (addEndBone || (useOffset && dbs.m_EndLength != 0)) {
                    Action<Transform> addEnd = null;
                    addEnd = delegate (Transform parent) {
                        if (parent.childCount == 0 && parent.parent != null) {
                            GameObject endBone = new GameObject(parent.name + "-end");
                            endBone.transform.parent = parent;

                            Vector3 d = (parent.parent.position - parent.position);
                            if (useOffset && dbs.m_EndLength != 0) d.Scale(new Vector3(dbs.m_EndLength, dbs.m_EndLength, dbs.m_EndLength));
                            endBone.transform.localScale = new Vector3(1, 1, 1);
                            endBone.transform.position = parent.position - d;
                        } else {
                            //when its not the end go to the next inline~ until finding ends
                            foreach (Transform t2 in parent)
                                if (!dbs.m_Exclusions.Contains(t2))
                                    addEnd(t2);

                        }
                    };
                    //loop only through the object that ARE effected by the PhysicsBones
                    addEnd(dbs.m_Root);
                }

                if (tinyBoneFix) {
                    bool usedTinyBones = false;
                    Action<Transform> addEnd = null;
                    addEnd = delegate (Transform parent) {
                        if (parent.childCount != 0)
                            //when its not the end go to the next inline~ until finding ends
                            foreach (Transform t2 in parent)
                                if (!dbs.m_Exclusions.Contains(t2) && t2.GetComponent<VRCPhysBone>() == null) {
                                    addEnd(t2);
                                    if (Vector3.Distance(parent.transform.position, t2.transform.position) < 0.012f && t2.childCount != 0 && ((newRoot != null && (newRoot != parent)) || (newRoot == null && dbs.m_Root != parent))) {
                                        GameObject tinyBone = new GameObject(parent.name + "-tinybonefix");
                                        tinyBone.transform.parent = t2;
                                        Vector3 d = (parent.position - t2.position);

                                        float x = 0.024f / (0.012f - d.x);
                                        float y = 0.024f / (0.012f - d.y);
                                        float z = 0.024f / (0.012f - d.z);

                                        float h = x;
                                        h = y > h ? y : h;
                                        h = z > h ? z : h;
                                        h *= 2.4f;
                                        d.Scale(new Vector3(h, h, h));
                                        tinyBone.transform.localScale = new Vector3(1, 1, 1);
                                        tinyBone.transform.position = parent.position - d;
                                        usedTinyBones = true;

                                    }
                                    //go through all children~
                                }
                    };

                    addEnd((newRoot == null ? dbs.m_Root : newRoot.transform));
                    if (usedTinyBones)
                        foreach (VRCPhysBone b in bonesAdded)
                            b.multiChildType = VRC.Dynamics.VRCPhysBoneBase.MultiChildType.Average;


                }

                c++;
            }


            if (animator != null && animator.isHuman)
                //check for all humanbones if exist and physicsbone on it enable isAnimated
                foreach (HumanBodyBones b in Enum.GetValues(typeof(HumanBodyBones)))
                    if (b != HumanBodyBones.LastBone && animator.GetBoneTransform(b) != null && animator.GetBoneTransform(b).gameObject.GetComponent<VRCPhysBone>() != null)
                        animator.GetBoneTransform(b).gameObject.GetComponent<VRCPhysBone>().isAnimated = true;


            if (OneScriptPerBone) {
                List<Transform> bonesWithScripts = new List<Transform>();
                foreach (VRCPhysBone phb in newAvi.GetComponentsInChildren<VRCPhysBone>(true)) {
                    bonesWithScripts.Add(phb.transform);
                }

                bonesWithScripts = bonesWithScripts.Distinct().ToList();
                foreach (Transform bones in bonesWithScripts) {
                    VRCPhysBone[] scriptsOnTransform = bones.GetComponents<VRCPhysBone>();
                    if (scriptsOnTransform.Count() > 1) {
                        for (int i = scriptsOnTransform.Count() - 1; i >= 0; i--) {
                            GameObject newRoot = new GameObject(bones.transform.name + "_s" + (i + 1));
                            newRoot.transform.parent = bones.transform;
                            newRoot.transform.localPosition = new Vector3(0, 0, 0);
                            newRoot.transform.localScale = new Vector3(1, 1, 1);
                            newRoot.transform.localRotation = Quaternion.Euler(0, 0, 0);
                            UnityEditorInternal.ComponentUtility.CopyComponent(scriptsOnTransform[i]);
                            VRCPhysBone nb = newRoot.AddComponent<VRCPhysBone>();
                            UnityEditorInternal.ComponentUtility.PasteComponentValues(nb);
                            nb.rootTransform = newRoot.transform;
                            foreach (Transform childrenOFParentX in bones) {
                                if (!nb.ignoreTransforms.Contains(childrenOFParentX) && !childrenOFParentX.name.Contains(bones.transform.name + "_s") && !avatarBones.Contains(childrenOFParentX))
                                    childrenOFParentX.parent = newRoot.transform;
                            }
                            DestroyImmediate(scriptsOnTransform[i]);
                        }
                    }
                }


                List<Transform> bonesWithColliderScripts = new List<Transform>();
                foreach (VRCPhysBoneCollider phb in newAvi.GetComponentsInChildren<VRCPhysBoneCollider>(true)) {
                    bonesWithColliderScripts.Add(phb.transform);
                }

                bonesWithColliderScripts = bonesWithColliderScripts.Distinct().ToList();

                foreach (Transform bones in bonesWithColliderScripts) {
                    VRCPhysBoneCollider[] scriptsOnTransform = bones.GetComponents<VRCPhysBoneCollider>();
                    if (scriptsOnTransform.Count() > 1) {
                        //when more then 1 collider are on the object (probably not many~)
                        List<VRCPhysBone> bonesThatContainedColliderX = new List<VRCPhysBone>();
                        for (int i = scriptsOnTransform.Count() - 1; i >= 0; i--) {
                            GameObject newRoot = new GameObject(bones.transform.name + "_c" + (i + 1));
                            newRoot.transform.parent = bones.transform;
                            newRoot.transform.localPosition = new Vector3(0, 0, 0);
                            newRoot.transform.localScale = new Vector3(1, 1, 1);
                            newRoot.transform.localRotation = Quaternion.Euler(0, 0, 0);
                            UnityEditorInternal.ComponentUtility.CopyComponent(scriptsOnTransform[i]);
                            VRCPhysBoneCollider nb = newRoot.AddComponent<VRCPhysBoneCollider>();
                            UnityEditorInternal.ComponentUtility.PasteComponentValues(nb);
                            nb.rootTransform = newRoot.transform;

                            //loop through all physicsbones and check IF they have this specific transform in it~ if so
                            foreach (VRCPhysBone bone in newAvi.GetComponentsInChildren<VRCPhysBone>()) {
                                if (bone.colliders.Contains(scriptsOnTransform[0])) {
                                    bone.colliders.Add(nb);
                                    bonesThatContainedColliderX.Add(bone);
                                }
                            }
                            DestroyImmediate(scriptsOnTransform[i]);
                        }

                        bonesThatContainedColliderX = bonesThatContainedColliderX.Distinct().ToList();

                        foreach (VRCPhysBone bone in bonesThatContainedColliderX) {
                            bone.colliders.Remove(scriptsOnTransform[0]);
                        }
                    }
                }
            }

            foreach (DynamicBone dbs in dynamicBonesScripts) {
                if (customSetup && listO.Any(o => o.getDynamicBone() == dbs && !o.isToggled())) continue;
                DestroyImmediate(dbs);
            }

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

            if (newAvi.GetComponentInChildren<DynamicBone>() == null)
                foreach (DynamicBoneCollider dbc in dynamicBonesColliders) {
                    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();

        }
    }
}
