﻿// FlexiMotion // https://kybernetik.com.au/flexi-motion // Copyright 2023 Kybernetik //

#if UNITY_EDITOR && FLEXI_MOTION_DYNAMIC_BONE

using FlexiMotion.Modifiers;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace FlexiMotion.Editor
{
    /// <summary>[Editor-Only]
    /// Utilities for converting between <see cref="FlexiMotion"/> and <see cref="DynamicBone"/>.
    /// </summary>
    /// <example><see href="https://kybernetik.com.au/flexi-motion/docs/samples/benchmark">Benchmark</see></example>
    public static class FlexiMotionToDynamicBone
    {
        /************************************************************************************************************************/

        public const int MenuPriority = 3;

        public const string MenuPrefix = "CONTEXT/" + nameof(FlexiMotion) + "/Dynamic Bone/";

        /************************************************************************************************************************/

        [MenuItem(MenuPrefix + "Copy to Dynamic Bone", priority = MenuPriority)]
        private static void CopyToDynamicBone(MenuCommand command)
            => CopyToDynamicBone((FlexiMotion)command.context);

        /// <summary>Creates <see cref="DynamicBone"/> components with similar parameters to the `target`.</summary>
        public static List<DynamicBone> CopyToDynamicBone(FlexiMotion target)
        {
            Undo.RecordObject(target, "Copy to Dynamic Bone");

            var dynamicBones = new List<DynamicBone>();
            target.GetComponents(dynamicBones);
            foreach (var dynamicBone in dynamicBones)
                Object.DestroyImmediate(dynamicBone, true);

            dynamicBones.Clear();

            CopyIndividualRootsAsDynamicBones(target, dynamicBones);

            var colliderCount = CopyColliders(target, dynamicBones);

            EditorUtility.SetDirty(target);

            Debug.Log($"Created {dynamicBones.Count} Dynamic Bones and {colliderCount} Colliders.");

            return dynamicBones;
        }

        /************************************************************************************************************************/

        private static void CopyIndividualRootsAsDynamicBones(FlexiMotion target, List<DynamicBone> dynamicBones)
        {
            var springCount = target.Definition.Springs.Count;
            for (int i = 0; i < springCount; i++)
            {
                var transform = target.Definition.Springs.Transforms[i];
                if (transform == null)
                    continue;

                var dynamicBone = GetOrCreateDynamicBoneForTransform(target.gameObject, dynamicBones, transform);
                if (dynamicBone == null)
                    continue;

                var stats = target.Definition.Springs.Details[i];
                var radius = target.Definition.Springs.Radii[i];

                dynamicBone.m_UpdateRate = target.Definition.UpdatesPerSecond;
                dynamicBone.m_Root = transform;
                dynamicBone.m_Damping = stats.drag;
                dynamicBone.m_Stiffness = stats.stiffness;
                dynamicBone.m_Radius = radius;
            }
        }

        /************************************************************************************************************************/

        private static DynamicBone GetOrCreateDynamicBoneForTransform(
            GameObject gameObject, List<DynamicBone> dynamicBones, Transform transform)
        {
            foreach (var dynamicBone in dynamicBones)
            {
                if (dynamicBone.m_Root.IsChildOf(transform))
                    return dynamicBone;
                else if (transform.IsChildOf(dynamicBone.m_Root))
                    return null;
            }

            var newDynamicBone = gameObject.AddComponent<DynamicBone>();
            newDynamicBone.m_DistanceToObject = float.MaxValue;
            if (newDynamicBone.m_Colliders == null)
                newDynamicBone.m_Colliders = new List<DynamicBoneColliderBase>();
            dynamicBones.Add(newDynamicBone);
            return newDynamicBone;
        }

        /************************************************************************************************************************/

        private static int CopyColliders(FlexiMotion target, List<DynamicBone> dynamicBones)
        {
            foreach (var collider in target.GetComponentsInChildren<DynamicBoneCollider>())
                Object.DestroyImmediate(collider, true);

            var colliderCount = 0;
            if (!target.TryGetComponent<SphereCollidersModifier>(out var modifier))
                return colliderCount;

            var count = modifier.Colliders.Count;
            for (int i = 0; i < count; i++)
            {
                if (!modifier.Colliders.TryGet(i, out var transform, out var sphere))
                    continue;

                var dynamicBoneCollider = transform.gameObject.AddComponent<DynamicBoneCollider>();
                dynamicBoneCollider.m_Center = sphere.position;
                dynamicBoneCollider.m_Radius = sphere.radius;
                colliderCount++;

                foreach (var dynamicBone in dynamicBones)
                    dynamicBone.m_Colliders.Add(dynamicBoneCollider);
            }

            return colliderCount;
        }

        /************************************************************************************************************************/
    }
}

#endif