﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace FIMSpace.AnimationTools
{
    public static class SkeletonRecognize
    {

        public enum EWhatIsIt
        {
            Unknown, Humanoidal, Quadroped, Creature
        }


        public class SkeletonInfo
        {
            public Transform AnimatorTransform;
            public float LowestVsHighestLen;
            public float MostLeftVsMostRightLen;
            public float MostForwVsMostBackLen;
            public float AverageLen;

            public Transform ProbablyRootBone;
            public Transform ProbablyHips;
            public Transform ProbablyChest;
            public Transform ProbablyHead;

            public List<Transform> TrReachingGround = new List<Transform>();
            public List<Transform> TrReachingSides = new List<Transform>();
            public List<Transform> TrEnds = new List<Transform>();

            public List<Transform> ProbablySpineChain = new List<Transform>();
            public List<Transform> ProbablySpineChainShort = new List<Transform>();
            public List<List<Transform>> ProbablyRightArms = new List<List<Transform>>();
            public List<List<Transform>> ProbablyLeftArms = new List<List<Transform>>();
            public List<List<Transform>> ProbablyLeftLegs = new List<List<Transform>>();
            public List<Transform> ProbablyLeftLegRoot = new List<Transform>();
            public List<List<Transform>> ProbablyRightLegs = new List<List<Transform>>();
            public List<Transform> ProbablyRightLegRoot = new List<Transform>();

            public Vector3 LocalSpaceHighest = Vector3.zero;
            public Vector3 LocalSpaceMostRight = Vector3.zero;
            public Vector3 LocalSpaceMostForward = Vector3.zero;
            public Vector3 LocalSpaceMostBack = Vector3.zero;
            public Vector3 LocalSpaceMostLeft = Vector3.zero;
            public Vector3 LocalSpaceLowest = Vector3.zero;

            public int SpineChainLength { get { return ProbablySpineChain.Count; } }
            public int LeftArms { get { return ProbablyLeftArms.Count; } }
            public int LeftLegs { get { return ProbablyLeftLegs.Count; } }
            public int RightArms { get { return ProbablyRightArms.Count; } }
            public int RightLegs { get { return ProbablyRightLegs.Count; } }
            public int Legs { get { return RightLegs + LeftLegs; } }
            public int Arms { get { return LeftArms + RightArms; } }

            public EWhatIsIt WhatIsIt = EWhatIsIt.Unknown;

            public SkeletonInfo(Transform t, List<Transform> checkOnly = null, Transform pelvisHelp = null)
            {
                AnimatorTransform = t;

                Transform[] childTransforms;

                if (checkOnly != null)
                {
                    childTransforms = new Transform[checkOnly.Count];
                    for (int i = 0; i < checkOnly.Count; i++)
                    {
                        childTransforms[i] = checkOnly[i];
                    }
                }
                else
                    childTransforms = AnimatorTransform.GetComponentsInChildren<Transform>(true);


                if (childTransforms.Length > 0)
                {
                    Vector3 l = AnimatorTransform.InverseTransformPoint(childTransforms[0].position);
                    LocalSpaceHighest = l;
                    LocalSpaceMostRight = l;
                    LocalSpaceMostForward = l;
                    LocalSpaceMostBack = l;
                    LocalSpaceMostLeft = l;
                    LocalSpaceLowest = l;
                }

                List<Transform> childT = new List<Transform>();
                for (int i = 0; i < childTransforms.Length; i++)
                {
                    Transform c = childTransforms[i];
                    SkinnedMeshRenderer skin = c.GetComponentInChildren<SkinnedMeshRenderer>();
                    if (skin != null) continue;
                    childT.Add(c);
                }

                //UnityEngine.Debug.Log("ChildTransforms = " + childTransforms.Length);

                #region Defining Skeleton Bounds Guides


                for (int i = 0; i < childT.Count; i++)
                {
                    Transform c = childT[i];

                    if (c.GetComponent<SkinnedMeshRenderer>()) continue;

                    Vector3 local = AnimatorTransform.InverseTransformPoint(c.position);

                    if (local.x > LocalSpaceMostRight.x) LocalSpaceMostRight = local;
                    else if (local.x < LocalSpaceMostLeft.x) LocalSpaceMostLeft = local;

                    if (local.z > LocalSpaceMostForward.z) LocalSpaceMostForward = local;
                    else if (local.z < LocalSpaceMostBack.z) LocalSpaceMostBack = local;

                    if (local.y > LocalSpaceHighest.y) LocalSpaceHighest = local;
                    else if (local.y < LocalSpaceLowest.y) LocalSpaceLowest = local;
                }


                #endregion


                // Helper Measures
                LowestVsHighestLen = Mathf.Abs(LocalSpaceLowest.y - LocalSpaceHighest.y);
                MostLeftVsMostRightLen = Mathf.Abs(LocalSpaceMostLeft.x - LocalSpaceMostRight.x);
                MostForwVsMostBackLen = Mathf.Abs(LocalSpaceMostForward.z - LocalSpaceMostBack.z);
                AverageLen = (LowestVsHighestLen + MostLeftVsMostRightLen + MostForwVsMostBackLen) / 3f;


                float limbMinimumLength = LowestVsHighestLen * 0.55f;



                #region Initial finding name based


                // arms
                for (int c = 0; c < childT.Count; c++)
                {
                    Transform ct = childT[c];
                    if (NameContains(ct.name, ShouldersNames))
                    {
                        Transform getCh = GetBottomMostChildTransform(ct);
                        if (NotContainedYetByLimbs(getCh)) TrReachingSides.Add(getCh);
                    }
                    else
                    {
                        if (NameContains(ct.name, ElbowNames))
                        {
                            Transform getCh = GetBottomMostChildTransform(ct);
                            if (NotContainedYetByLimbs(getCh)) TrReachingSides.Add(getCh);
                        }
                    }
                }

                // legs
                for (int c = 0; c < childT.Count; c++)
                {
                    Transform ct = childT[c];
                    if (NameContains(ct.name, UpperLegNames))
                    {
                        Transform getCh = GetBottomMostChildTransform(ct);
                        if (NotContainedYetByLimbs(getCh)) TrReachingGround.Add(getCh);
                    }
                    else
                    {
                        if (NameContains(ct.name, KneeNames))
                        {
                            Transform getCh = GetBottomMostChildTransform(ct);
                            if (NotContainedYetByLimbs(getCh)) TrReachingGround.Add(getCh);
                        }
                    }
                }

                // pelvis
                bool hipsByName = false;

                for (int c = 0; c < childT.Count; c++)
                {
                    Transform ct = childT[c];
                    if (NameContains(ct.name, PelvisNames))
                    {
                        hipsByName = true;
                        ProbablyHips = ct;
                        break;
                    }
                }

                // chest
                bool chestByName = false;

                for (int c = 0; c < childT.Count; c++)
                {
                    Transform ct = childT[c];
                    if (NameContains(ct.name, ChestNames))
                    {
                        chestByName = true;
                        ProbablyChest = ct;
                        break;
                    }
                }

                // head

                bool headByName = false;

                for (int c = 0; c < childT.Count; c++)
                {
                    Transform ct = childT[c];
                    if (NameContains(ct.name, HeadNames))
                    {
                        headByName = true;
                        ProbablyHead = ct;
                        break;
                    }
                }

                if (ProbablyHead != null)
                    if (ProbablyHips != null)
                    {
                        if (IsChildOf(ProbablyHead, ProbablyHips) == false)
                        {
                            ProbablyHead = null;
                        }
                    }

                // root

                for (int c = 0; c < childT.Count; c++)
                {
                    Transform ct = childT[c];
                    if (NameContains(ct.name, RootNames))
                    {
                        ProbablyRootBone = ct;
                        break;
                    }
                }



                #endregion




                #region Defining End Transforms for Arms / Legs / Head


                if (childT.Count > 2)
                {
                    for (int i = 1; i < childT.Count; i++)
                    {
                        Transform tr = childT[i];

                        if (tr.childCount == 0)
                        {
                            TrEnds.Add(tr);

                            Vector3 l = Loc(tr);

                            if (l.y < LocalSpaceLowest.y + LowestVsHighestLen * 0.1f)
                            {
                                if (NotContainedYetByLimbs(tr)) TrReachingGround.Add(tr);
                            }
                            else
                            {
                                if (l.y > LocalSpaceLowest.y + LowestVsHighestLen * 0.2f)
                                {
                                    if (l.x < MostLeftVsMostRightLen * -0.1f || l.x > MostLeftVsMostRightLen * 0.1f)
                                    {
                                        if (NotContainedYetByLimbs(tr)) TrReachingSides.Add(tr);
                                    }
                                }
                            }
                        }

                    }
                }


                #endregion




                #region Chest Basing on Left / Right Sides Limbs

                if (!chestByName)
                {
                    List<Transform> probablyChestOnes = new List<Transform>();
                    for (int i = 0; i < TrReachingSides.Count; i++)
                    {
                        if (childT[i].GetComponent<SkinnedMeshRenderer>()) continue;

                        Transform par = TrReachingSides[i].parent;

                        while (par != null)
                        {
                            if (par.childCount > 2)
                            {
                                Vector3 loc = Loc(par);
                                if (loc.x > -MostLeftVsMostRightLen * 0.03f && loc.x < MostLeftVsMostRightLen * 0.03f)
                                {
                                    probablyChestOnes.Add(par);
                                    break;
                                }
                            }

                            par = par.parent;
                        }
                    }

                    if (probablyChestOnes.Count == 1) ProbablyChest = probablyChestOnes[0];
                    else if (probablyChestOnes.Count > 1)
                    {
                        if (probablyChestOnes[0] == probablyChestOnes[1])
                            ProbablyChest = probablyChestOnes[0];
                    }
                }

                #endregion


                #region Pelvis Basing On Left / Right Low Limbs


                if (!hipsByName)
                {
                    List<Transform> probablyHipsOnes = new List<Transform>();

                    for (int i = 0; i < TrReachingGround.Count; i++)
                    {
                        Transform par = TrReachingGround[i].parent;

                        while (par != null)
                        {
                            if (par.childCount > 2)
                            {
                                Vector3 loc = Loc(par);
                                if (loc.y > LocalSpaceLowest.y + LowestVsHighestLen * 0.04f)
                                    if (loc.x > -MostLeftVsMostRightLen * 0.02f && loc.x < MostLeftVsMostRightLen * 0.02f)
                                    {
                                        probablyHipsOnes.Add(par);
                                        break;
                                    }
                            }

                            par = par.parent;
                        }
                    }

                    if (probablyHipsOnes.Count == 1) ProbablyChest = probablyHipsOnes[0];
                    else if (probablyHipsOnes.Count > 1)
                    {
                        if (probablyHipsOnes[0] == probablyHipsOnes[1])
                            ProbablyHips = probablyHipsOnes[0];
                    }
                }

                if (ProbablyHips == null) ProbablyHips = pelvisHelp;

                #endregion


                #region correcting chest if required


                if (ProbablyChest == null || ProbablyChest == ProbablyHips || (ProbablyHips != null && IsChildOf(ProbablyChest, ProbablyHips) == false))
                {
                    if (ProbablyHips) if (ProbablyHead)
                        {
                            Transform checkT = ProbablyHead.parent;
                            bool found = false;

                            while (checkT.parent != null && checkT.parent != ProbablyHips)
                            {
                                if (checkT.childCount > 2)
                                {
                                    // Check if some side limbs are child bones of chest check bone
                                    for (int s = 0; s < TrReachingSides.Count; s++)
                                    {
                                        Transform side = TrReachingSides[s];

                                        if (IsChildOf(side, checkT))
                                        {
                                            found = true;
                                            break;
                                        }
                                    }
                                }

                                if (found) break;

                                checkT = checkT.parent;
                            }

                            if (found) ProbablyChest = checkT;
                        }
                }

                if (ProbablyHips == null) ProbablyHips = pelvisHelp;

                #endregion


                // Probably correctly detected chest and hips
                if (ProbablyChest && ProbablyHips)
                {

                    if (MostForwVsMostBackLen > LowestVsHighestLen * 0.9f) // If forward legth is bigger than model's height
                    {
                        // In most cases chest is more in front than hips
                        if (Loc(ProbablyChest).z < Loc(ProbablyHips).z) // Chest is behind hips - swap!
                        {
                            Transform swap = ProbablyChest;
                            ProbablyChest = ProbablyHips;
                            ProbablyHips = swap;
                            UnityEngine.Debug.Log("Hips - Chest - Reversed Detection Swap!");
                        }
                    }


                    #region Trying To Detect Head

                    if (!headByName)
                    {
                        Vector3 highestForHead = Vector3.zero;
                        for (int c = 0; c < ProbablyChest.childCount; c++)
                        {
                            // checking all probably chest child transforms
                            Transform ch = ProbablyChest.GetChild(c);
                            Vector3 lc;

                            if (ch.childCount > 0) // Going through 
                            {
                                for (int c2 = 0; c2 < ch.childCount; c2++)
                                {
                                    Transform ch2 = ch.GetChild(c2);
                                    lc = Loc(ch2);

                                    if (lc.x > -MostLeftVsMostRightLen * 0.04f && lc.x < MostLeftVsMostRightLen * 0.04f)
                                    {
                                        if (Loc(ch2).y > highestForHead.y)
                                        { highestForHead = Loc(ch2); ProbablyHead = ch2; }
                                    }
                                }
                            }

                            lc = Loc(ch);
                            if (lc.x > -MostLeftVsMostRightLen * 0.04f && lc.x < MostLeftVsMostRightLen * 0.04f)
                                if (lc.y > highestForHead.y)
                                { highestForHead = Loc(ch); ProbablyHead = ch; }
                        }


                        if (ProbablyChest && ProbablyHead && ProbablyHips)
                        {
                            float chestToPelvis = Vector3.Distance(Loc(ProbablyChest), Loc(ProbablyHips));

                            if ((ProbablyChest.childCount < 3 || chestToPelvis < AverageLen * 0.12f) && ProbablyHead.childCount > 1)
                            {
                                ProbablyChest = ProbablyHead;
                                ProbablyHead = GetHighestChild(ProbablyHead, AnimatorTransform, MostLeftVsMostRightLen * 0.05f);
                                if (ProbablyHead == ProbablyChest) ProbablyHead = ProbablyChest.GetChild(0);
                            }
                        }

                    }


                    #endregion


                    #region Eliminating wrong detected arms (it can be ear bones)

                    if (ProbablyHead)
                    {
                        for (int i = TrReachingSides.Count - 1; i >= 0; i--)
                        {
                            if (IsChildOf(TrReachingSides[i], ProbablyHead)) TrReachingSides.RemoveAt(i);
                        }
                    }

                    for (int i = TrReachingSides.Count - 1; i >= 0; i--)
                    {
                        if (GetDepth(TrReachingSides[i], AnimatorTransform) < 5)
                        {
                            TrReachingSides.RemoveAt(i);
                        }
                    }

                    #endregion


                    #region Detecting Spine Chain

                    Transform headC = null;
                    if (ProbablyHead)
                    {
                        //if (ProbablyHead.parent) headC = ProbablyHead.parent;
                        ProbablySpineChain.Add(ProbablyHead);
                        headC = ProbablyHead.parent;
                    }

                    while (headC != null && headC != ProbablyHips)
                    {
                        ProbablySpineChain.Add(headC);
                        headC = headC.parent;
                    }

                    ProbablySpineChain.Reverse();

                    for (int i = 0; i < Mathf.Min(4, ProbablySpineChain.Count); i++)
                    {
                        ProbablySpineChainShort.Add(ProbablySpineChain[i]);
                    }

                    #endregion


                    #region Detecting Legs

                    List<Transform> confirmedLegs = new List<Transform>();

                    for (int i = 0; i < TrReachingGround.Count; i++)
                    {
                        Transform start = TrReachingGround[i];
                        Vector3 startLoc = Loc(start);

                        List<Transform> fullChain = new List<Transform>();

                        Transform untilHips = start;
                        while (untilHips != null && (untilHips != ProbablyHips && untilHips != ProbablyChest))
                        {
                            fullChain.Add(untilHips);
                            untilHips = untilHips.parent;
                        }

                        if (fullChain.Count >= 3)
                        {
                            List<Transform> legChain = new List<Transform>();
                            legChain.Add(fullChain[fullChain.Count - 1]);
                            legChain.Add(fullChain[fullChain.Count - 2]);
                            legChain.Add(fullChain[fullChain.Count - 3]);

                            confirmedLegs.Add(start);

                            if (startLoc.x < MostLeftVsMostRightLen * 0.02f)
                            {
                                ProbablyLeftLegs.Add(legChain);
                                ProbablyLeftLegRoot.Add(untilHips);
                            }
                            else
                            {
                                ProbablyRightLegs.Add(legChain);
                                ProbablyRightLegRoot.Add(untilHips);
                            }
                        }
                    }

                    #endregion


                    #region Detecting Arms


                    for (int i = 0; i < TrReachingSides.Count; i++)
                    {
                        Transform start = TrReachingSides[i];
                        Vector3 startLoc = Loc(start);

                        List<Transform> fullChain = new List<Transform>();

                        Transform untilChest = start;
                        while (untilChest != null && untilChest != ProbablyChest)
                        {
                            fullChain.Add(untilChest);
                            untilChest = untilChest.parent;
                        }

                        if (fullChain.Count >= 4)
                        {
                            List<Transform> armChain = new List<Transform>();
                            armChain.Add(fullChain[fullChain.Count - 1]);
                            armChain.Add(fullChain[fullChain.Count - 2]);
                            armChain.Add(fullChain[fullChain.Count - 3]);
                            armChain.Add(fullChain[fullChain.Count - 4]);

                            if (startLoc.x < MostLeftVsMostRightLen * 0.02f)
                                ProbablyLeftArms.Add(armChain);
                            else
                                ProbablyRightArms.Add(armChain);
                        }
                    }

                    #endregion

                    #region Removing Duplicates (resulting by fingers counts)

                    ClearDuplicates(ProbablyLeftArms, null);
                    ClearDuplicates(ProbablyRightArms, null);
                    ClearDuplicates(ProbablyLeftLegs, ProbablyLeftLegRoot);
                    ClearDuplicates(ProbablyRightLegs, ProbablyRightLegRoot);

                    #endregion


                    if (Legs == 2 && Arms == 2)
                    {
                        WhatIsIt = EWhatIsIt.Humanoidal;
                    }
                    else if (Legs == 4 && Arms == 0)
                    {
                        WhatIsIt = EWhatIsIt.Quadroped;
                    }
                    else if (Legs > 0 || Arms > 0)
                    {
                        WhatIsIt = EWhatIsIt.Creature;
                    }
                    else
                    {
                        WhatIsIt = EWhatIsIt.Unknown;
                    }

                }


                float middleHeight = Mathf.Lerp(LocalSpaceLowest.y, LocalSpaceHighest.y, 0.5f);
                UnityEngine.Debug.DrawLine(t.TransformPoint(new Vector3(LocalSpaceMostLeft.x, LocalSpaceHighest.y, LocalSpaceMostForward.z)), t.TransformPoint(new Vector3(LocalSpaceMostLeft.x, LocalSpaceLowest.y, LocalSpaceMostForward.z)), Color.green, 12);
                UnityEngine.Debug.DrawLine(t.TransformPoint(new Vector3(LocalSpaceMostLeft.x, middleHeight, LocalSpaceMostForward.z)), t.TransformPoint(new Vector3(LocalSpaceMostRight.x, middleHeight, LocalSpaceMostForward.z)), Color.red, 12);
                UnityEngine.Debug.DrawLine(t.TransformPoint(new Vector3(LocalSpaceMostRight.x, middleHeight, LocalSpaceMostForward.z)), t.TransformPoint(new Vector3(LocalSpaceMostRight.x, middleHeight, LocalSpaceMostBack.z)), Color.blue, 12);

            }


            bool NotContainedYetByAny(Transform t)
            {
                return (!TrReachingSides.Contains(t) && !TrReachingGround.Contains(t) && !TrEnds.Contains(t)
                    && t != ProbablyChest && t != ProbablyHips && t != ProbablyHead && t != ProbablyChest && t != ProbablyRootBone && t != AnimatorTransform);
            }

            bool NotContainedYetByLimbs(Transform t)
            {
                return (!TrReachingSides.Contains(t) && !TrReachingGround.Contains(t));
            }

            public Transform GetHighestChild(Transform t, Transform root, float inCenterRangeFactor)
            {
                if (t == null) return null;

                Transform highT = t;
                Vector3 highest = root.InverseTransformPoint(t.position);
                foreach (var ct in t.GetComponentsInChildren<Transform>(true))
                {
                    Vector3 pos = root.InverseTransformPoint(ct.position);

                    if (pos.x > -inCenterRangeFactor && pos.x < inCenterRangeFactor)
                        if (pos.y > highest.y)
                        {
                            highest.y = pos.y;
                            highT = ct;
                        }
                }

                return highT;
            }

            //float ComputeLength(Transform p, int parentBack)
            //{
            //    float len = 0f;

            //    if (p != null)
            //        for (int i = 0; i < parentBack; i++)
            //        {
            //            if (p.parent != null)
            //            {
            //                len += Vector3.Distance(p.position, p.parent.position);
            //                p = p.parent;
            //            }
            //            else
            //                break;
            //        }

            //    return len;
            //}

            void ClearDuplicates(List<List<Transform>> limbs, List<Transform> roots)
            {
                if (limbs.Count > 1)
                {
                    for (int main = 0; main < limbs.Count; main++) // Checking all limb chains
                    {
                        if (main >= limbs.Count) return;

                        var limb = limbs[main];

                        // Checking if some other limbs contains duplicate bones of each other
                        // It can be caused by finger bones - how many fingers -> that many hands detected
                        for (int i = limbs.Count - 1; i >= 0; i--)
                        {
                            if (i == main) continue; // Don't check self

                            var otherLimb = limbs[i];

                            bool remove = false;

                            for (int p = 0; p < otherLimb.Count; p++)
                            {

                                if (limb.Contains(otherLimb[p]))
                                {
                                    remove = true;
                                    break;
                                }

                            }

                            if (remove)
                            {
                                limbs.RemoveAt(i);
                            }
                        }
                    }

                }
            }


            Vector3 Loc(Transform t)
            {
                return AnimatorTransform.InverseTransformPoint(t.position);
            }

            #region Debug Log Report

            public string GetLog()
            {
                string log = "< " + AnimatorTransform.name + " >\n";

                log += "\nGenerate Guides:\n";

                log += "Highest: " + LocalSpaceHighest + "     ";
                log += "Lowest: " + LocalSpaceLowest + "     ";
                log += "Left: " + LocalSpaceMostLeft + "     ";
                log += "Right: " + LocalSpaceMostRight + "     ";
                log += "Forward: " + LocalSpaceMostForward + "     ";
                log += "Back: " + LocalSpaceMostBack + "     ";


                log += "\n\nGenerated Helper Measurements: \n";
                log += "UpDown: " + LowestVsHighestLen + "     ";
                log += "LeftRight: " + MostLeftVsMostRightLen + "     ";
                log += "ForwBack: " + MostForwVsMostBackLen + "     ";
                log += "Avr: " + AverageLen + "     ";

                log += "\n\nDetected Propabilities: \n";
                log += "ProbablyHips: " + ProbablyHips + "     ";
                log += "ProbablyChest: " + ProbablyChest + "     ";
                log += "ProbablyHead: " + ProbablyHead + "     ";

                log += "\n\nLimb End Detections: \n";
                log += "Reaching Ground: " + TrReachingGround.Count + "     ";
                log += "Reaching Sides: " + TrReachingSides.Count + "     ";
                log += "Spine Chain Length: " + ProbablySpineChain.Count + " (" + ProbablySpineChainShort.Count + ")     ";

                log += "\n\nDetected Propabilities: \n";
                log += "Probably Left Arms: " + ProbablyLeftArms.Count + "     ";
                log += "Probably Right Arms: " + ProbablyRightArms.Count + "     ";
                log += "Probably Left Legs: " + ProbablyLeftLegs.Count + "     ";
                log += "Probably Right Legs: " + ProbablyRightLegs.Count + "     ";

                log += "\n\n\nTr Ends: \n";
                for (int i = 0; i < TrEnds.Count; i++)
                {
                    if (TrEnds[i] == null) continue;
                    log += TrEnds[i].name + "     ";
                }
                log += "\n\nTr Reaching Ground: \n";
                for (int i = 0; i < TrReachingGround.Count; i++)
                {
                    if (TrReachingGround[i] == null) continue;
                    log += TrReachingGround[i].name + "     ";
                }
                log += "\n\nTr Reaching Sides: \n";
                for (int i = 0; i < TrReachingSides.Count; i++)
                {
                    if (TrReachingSides[i] == null) continue;
                    log += TrReachingSides[i].name + "     ";
                }


                if (ProbablyLeftArms.Count > 0)
                {
                    log += "\n\nDebug Left Arms: \n";
                    for (int i = 0; i < ProbablyLeftArms.Count; i++)
                    {
                        if (ProbablyLeftArms[i] == null) continue;
                        log += "[" + i + "] ";

                        for (int l = 0; l < ProbablyLeftArms[i].Count; l++)
                        {
                            log += ProbablyLeftArms[i][l].name + "  ";
                        }

                        log += "\n";
                    }
                }

                if (ProbablySpineChainShort.Count > 0)
                {
                    log += "\n\nDebug Spine Chain: \n";
                    for (int i = 0; i < ProbablySpineChainShort.Count; i++)
                    {
                        if (ProbablySpineChainShort[i] == null) continue;
                        log += ProbablySpineChainShort[i].name + "  ";
                    }
                }

                log += "\n\n";

                return log;
            }

            #endregion


            public static int GetDepth(Transform t, Transform skelRootBone)
            {
                int depth = 0;
                if (t == skelRootBone) return 0;
                if (t == null) return 0;
                if (t.parent == null) return 0;

                while (t != null && t != skelRootBone)
                {
                    t = t.parent;
                    depth += 1;
                }

                return depth;
            }
        }



        #region Transforms Utils


        public static bool IsChildOf(Transform child, Transform parent)
        {
            Transform p = child;
            while (p != null)
            {
                if (p == parent) return true;
                p = p.parent;
            }

            return false;
        }

        public static Transform GetBottomMostChildTransform(Transform parent)
        {
            var allCh = parent.GetComponentsInChildren<Transform>(true);
            int lowest = 0;
            Transform lowestT = parent;
            
            for (int c = 0; c < allCh.Length; c++)
            {
                if (allCh[c] == parent) continue;

                Transform ch = allCh[c];
                int depth = 0;

                while (ch.parent != parent && ch.parent != null)
                {
                    depth += 1;
                    ch = ch.parent;
                }

                if (depth > lowest)
                {
                    lowest = depth;
                    lowestT = allCh[c];
                }
            }

            return lowestT;
        }

        #endregion


        #region Name Based Search Utils

        public static readonly string[] SpineNames = new string[] { "spine" };
        public static readonly string[] NeckNames = new string[] { "neck" };
        public static readonly string[] HeadNames = new string[] { "head" };
        public static readonly string[] RootNames = new string[] { "root", "origin", "skel" };
        public static readonly string[] PelvisNames = new string[] { "pelvis", "hips", "pelv" };
        public static readonly string[] ChestNames = new string[] { "chest", "upperspine" };
        public static readonly string[] ShouldersNames = new string[] { "shoulde", "collarbon", "clavicl" };
        public static readonly string[] UpperLegNames = new string[] { "upperleg", "thigh" };
        public static readonly string[] KneeNames = new string[] { "knee", "calf", "lowerleg" };
        public static readonly string[] ElbowNames = new string[] { "elbow", "lowerarm" };

        public static bool NameContains(string name, string[] names)
        {
            string nm = name.ToLower();
            nm = nm.Replace("-", "");
            nm = nm.Replace(" ", "");
            nm = nm.Replace("_", "");
            nm = nm.Replace("|", "");
            nm = nm.Replace("@", "");

            for (int n = 0; n < names.Length; n++)
            {
                if (nm.Contains(names[n])) return true;
            }

            return false;
        }

        #endregion

    }
}
