/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * Licensed under the Oculus SDK License Agreement (the "License");
 * you may not use the Oculus SDK except in compliance with the License,
 * which is provided at the time of installation or download, or which
 * otherwise accompanies this software in either electronic or hard copy form.
 *
 * You may obtain a copy of the License at
 *
 * https://developer.oculus.com/licenses/oculussdk/
 *
 * Unless required by applicable law or agreed to in writing, the Oculus SDK
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.ComponentModel;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

[CustomEditor(typeof(OVRCustomSkeleton))]
public class OVRCustomSkeletonEditor : OVRSkeletonEditor
{
    public override void OnInspectorGUI()
    {
        var skeleton = (OVRCustomSkeleton)target;

        base.OnInspectorGUI();


        DrawBonesMapping(skeleton);
    }

    private static void DrawBonesMapping(OVRCustomSkeleton skeleton)
    {
        if (GUILayout.Button($"Auto Map Bones"))
        {
            skeleton.AutoMapBones(skeleton.retargetingType);
            EditorUtility.SetDirty(skeleton);
            EditorSceneManager.MarkSceneDirty(skeleton.gameObject.scene);
        }

        EditorGUILayout.LabelField("Bones", EditorStyles.boldLabel);
        var start = skeleton.GetCurrentStartBoneId();
        var end = skeleton.GetCurrentEndBoneId();
        if (skeleton.IsValidBone(start) && skeleton.IsValidBone(end))
        {
            for (var i = (int)start; i < (int)end; ++i)
            {
                var boneName = OVRSkeleton.BoneLabelFromBoneId(skeleton.GetSkeletonType(), (OVRSkeleton.BoneId)i);
                EditorGUI.BeginChangeCheck();
                var val =
                    EditorGUILayout.ObjectField(boneName, skeleton.CustomBones[i], typeof(Transform), true);
                if (EditorGUI.EndChangeCheck())
                {
                    skeleton.CustomBones[i] = (Transform)val;
                    EditorUtility.SetDirty(skeleton);
                }
            }
        }
    }
}

/// <summary>
/// Extensions class for the editor methods of <see cref="OVRCustomSkeleton"/>.
/// </summary>
public static class OVRCustomSkeletonEditorExtensions
{
    /// <summary>
    /// This method tries to retarget the skeleton structure present in the current <see cref="GameObject"/> to the one supported by the body tracking system.
    /// </summary>
    /// <param name="customSkeleton" cref="OVRCustomSkeleton">The custom skeleton to run this method on</param>
    /// <param name="type" cref="OVRCustomSkeleton.RetargetingType">The skeleton structure to auto map from</param>
    public static void AutoMapBones(this OVRCustomSkeleton customSkeleton, OVRCustomSkeleton.RetargetingType type)
    {
        try
        {
            switch (type)
            {
                case OVRCustomSkeleton.RetargetingType.OculusSkeleton:
                    customSkeleton.AutoMapBonesFromOculusSkeleton();
                    break;
                default:
                    throw new InvalidEnumArgumentException($"Invalid {nameof(OVRCustomSkeleton.RetargetingType)}");
            }
        }
        catch (Exception e)
        {
            EditorUtility.DisplayDialog($"Auto Map Bones Error", e.Message, "Ok");
        }
    }

    public static void TryAutoMapBonesByName(this OVRCustomSkeleton customSkeleton)
    {
        customSkeleton.AutoMapBonesFromOculusSkeleton();
    }

    internal static void AutoMapBonesFromOculusSkeleton(this OVRCustomSkeleton customSkeleton)
    {
        var start = customSkeleton.GetCurrentStartBoneId();
        var end = customSkeleton.GetCurrentEndBoneId();
        var skeletonType = customSkeleton.GetSkeletonType();
        if (customSkeleton.IsValidBone(start) && customSkeleton.IsValidBone(end))
        {
            for (var bi = (int)start; bi < (int)end; ++bi)
            {
                string fbxBoneName = FbxBoneNameFromBoneId(skeletonType, (OVRSkeleton.BoneId)bi);
                Transform t = customSkeleton.transform.FindChildRecursive(fbxBoneName);

                if (t == null && skeletonType == OVRSkeleton.SkeletonType.Body)
                {
                    var legacyBoneName = fbxBoneName
                        .Replace("Little", "Pinky")
                        .Replace("Metacarpal", "Meta");
                    t = customSkeleton.transform.FindChildRecursive(legacyBoneName);
                }

                if (t != null)
                {
                    customSkeleton.CustomBones[bi] = t;
                }
            }
        }
    }

    internal static bool ClearBonesMapping(this OVRCustomSkeleton skeleton)
    {
        var start = skeleton.GetCurrentStartBoneId();
        var end = skeleton.GetCurrentEndBoneId();
        var cleared = false;

        if (!skeleton.IsValidBone(start) || !skeleton.IsValidBone(end))
        {
            return false;
        }

        for (var i = (int)start; i < (int)end; ++i)
        {
            if (skeleton.CustomBones[i] != null)
            {
                skeleton.CustomBones[i] = null;
                cleared = true;
            }
        }

        return cleared;
    }

    private static string FbxBoneNameFromBoneId(OVRSkeleton.SkeletonType skeletonType, OVRSkeleton.BoneId bi)
    {
        if (skeletonType == OVRSkeleton.SkeletonType.Body)
        {
            return FBXBodyBoneNames[(int)bi];
        }
        else
        {
            if (bi >= OVRSkeleton.BoneId.Hand_ThumbTip && bi <= OVRSkeleton.BoneId.Hand_PinkyTip)
            {
                return FBXHandSidePrefix[(int)skeletonType] + FBXHandFingerNames[(int)bi - (int)OVRSkeleton.BoneId.Hand_ThumbTip] +
                       "_finger_tip_marker";
            }
            else
            {
                return FBXHandBonePrefix + FBXHandSidePrefix[(int)skeletonType] + FBXHandBoneNames[(int)bi];
            }
        }
    }

    private static readonly string[] FBXBodyBoneNames =
    {
        "Root",
        "Hips",
        "SpineLower",
        "SpineMiddle",
        "SpineUpper",
        "Chest",
        "Neck",
        "Head",
        "LeftShoulder",
        "LeftScapula",
        "LeftArmUpper",
        "LeftArmLower",
        "LeftHandWristTwist",
        "RightShoulder",
        "RightScapula",
        "RightArmUpper",
        "RightArmLower",
        "RightHandWristTwist",
        "LeftHandPalm",
        "LeftHandWrist",
        "LeftHandThumbMetacarpal",
        "LeftHandThumbProximal",
        "LeftHandThumbDistal",
        "LeftHandThumbTip",
        "LeftHandIndexMetacarpal",
        "LeftHandIndexProximal",
        "LeftHandIndexIntermediate",
        "LeftHandIndexDistal",
        "LeftHandIndexTip",
        "LeftHandMiddleMetacarpal",
        "LeftHandMiddleProximal",
        "LeftHandMiddleIntermediate",
        "LeftHandMiddleDistal",
        "LeftHandMiddleTip",
        "LeftHandRingMetacarpal",
        "LeftHandRingProximal",
        "LeftHandRingIntermediate",
        "LeftHandRingDistal",
        "LeftHandRingTip",
        "LeftHandLittleMetacarpal",
        "LeftHandLittleProximal",
        "LeftHandLittleIntermediate",
        "LeftHandLittleDistal",
        "LeftHandLittleTip",
        "RightHandPalm",
        "RightHandWrist",
        "RightHandThumbMetacarpal",
        "RightHandThumbProximal",
        "RightHandThumbDistal",
        "RightHandThumbTip",
        "RightHandIndexMetacarpal",
        "RightHandIndexProximal",
        "RightHandIndexIntermediate",
        "RightHandIndexDistal",
        "RightHandIndexTip",
        "RightHandMiddleMetacarpal",
        "RightHandMiddleProximal",
        "RightHandMiddleIntermediate",
        "RightHandMiddleDistal",
        "RightHandMiddleTip",
        "RightHandRingMetacarpal",
        "RightHandRingProximal",
        "RightHandRingIntermediate",
        "RightHandRingDistal",
        "RightHandRingTip",
        "RightHandLittleMetacarpal",
        "RightHandLittleProximal",
        "RightHandLittleIntermediate",
        "RightHandLittleDistal",
        "RightHandLittleTip"
    };


    private static readonly string[] FBXHandSidePrefix = { "l_", "r_" };
    private const string FBXHandBonePrefix = "b_";

    private static readonly string[] FBXHandBoneNames =
    {
        "wrist",
        "forearm_stub",
        "thumb0",
        "thumb1",
        "thumb2",
        "thumb3",
        "index1",
        "index2",
        "index3",
        "middle1",
        "middle2",
        "middle3",
        "ring1",
        "ring2",
        "ring3",
        "pinky0",
        "pinky1",
        "pinky2",
        "pinky3"
    };

    private static readonly string[] FBXHandFingerNames =
    {
        "thumb",
        "index",
        "middle",
        "ring",
        "pinky"
    };
}
