using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using System.Collections;

public class OptitrackSkeletonAnimator_Meechu : MonoBehaviour
{
    [Header("OptiTrack 설정")]
    [Tooltip("OptiTrackStreamingClient가 포함된 오브젝트")]
    public OptitrackStreamingClient StreamingClient;

    [Tooltip("Motive의 스켈레톤 에셋 이름")]
    public string SkeletonAssetName = "Skeleton1";

    private Animator TargetAnimator;

    private OptitrackSkeletonDefinition m_skeletonDef;
    private Dictionary<string, HumanBodyBones> m_optitrackToHumanBoneMap;

    private string previousSkeletonName;

    [HideInInspector]
    public bool isSkeletonFound = false;

    private float updateInterval = 0.1f;

    void Start()
    {
        TargetAnimator = GetComponent<Animator>();
        if (TargetAnimator == null)
        {
            //Debug.LogError("이 게임오브젝트에서 Animator 컴포넌트를 찾을 수 없습니다.", this);
            enabled = false;
            return;
        }

        InitializeStreamingClient();

        // StreamingClient 등록 추가
        if (StreamingClient != null)
        {
            StreamingClient.RegisterSkeleton(this, this.SkeletonAssetName);
            //Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'이(가) 등록되었습니다.");
        }

        InitializeBoneMapping();

        // 주기적으로 스켈레톤 연결 상태를 확인하는 코루틴 시작
        StartCoroutine(CheckSkeletonConnectionPeriodically());
    }

    void Update()
    {
        if (TargetAnimator == null)
            return;

        // StreamingClient 체크
        if (StreamingClient == null)
        {
            InitializeStreamingClient();
            return;
        }

        // 스켈레톤 이름이 변경되었을 때
        if (previousSkeletonName != SkeletonAssetName)
        {
            // 새 스켈레톤 등록
            StreamingClient.RegisterSkeleton(this, SkeletonAssetName);
            //Debug.Log($"[OptiTrack] 새 스켈레톤 '{SkeletonAssetName}' 등록");

            // 스켈레톤 정의 새로 가져오기
            m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName);

            if (m_skeletonDef == null)
            {
                //Debug.LogWarning($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'을(를) 찾을 수 없습니다. Motive에서 올바른 스켈레톤 이름을 확인해주세요.", this);
                previousSkeletonName = SkeletonAssetName; // 이름 업데이트
                return;
            }

            //Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'을(를) 성공적으로 찾았습니다.", this);
            previousSkeletonName = SkeletonAssetName; // 이름 업데이트
        }

        // 스켈레톤 정의가 없는 경우 체크
        if (m_skeletonDef == null)
        {
            m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName);
            if (m_skeletonDef == null)
            {
                return;
            }
        }

        // 최신 스켈레톤 상태 가져오기
        OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id);

        if (skelState == null)
        {
            //Debug.LogWarning($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'의 상태가 null입니다. Motive에 마커가 제대로 트래킹되고 있는지 확인해주세요.", this);
            return;
        }
        // 각 본 업데이트
        foreach (var bone in m_skeletonDef.Bones)
        {
            string boneName = bone.Name;
            // SkeletonAssetName_ 부분을 제거
            string optitrackBoneName = boneName.Contains("_") ? boneName.Substring(boneName.IndexOf('_') + 1) : boneName;

            if (m_optitrackToHumanBoneMap.TryGetValue(optitrackBoneName, out HumanBodyBones humanBone))
            {
                Transform boneTransform = TargetAnimator.GetBoneTransform(humanBone);
                if (boneTransform != null)
                {
                    // 로컬 포즈 가져오기
                    if (skelState.BonePoses.TryGetValue(bone.Id, out OptitrackPose bonePose))
                    {
                        boneTransform.localPosition = bonePose.Position;
                        boneTransform.localRotation = bonePose.Orientation;
                    }
                }
            }
        }
    }

    private void InitializeStreamingClient()
    {
        if (StreamingClient == null)
        {
            // 씬에서 OptitrackStreamingClient 찾기
            StreamingClient = FindFirstObjectByType<OptitrackStreamingClient>();
            if (StreamingClient == null)
            {
                //Debug.LogWarning("씬에서 OptiTrack Streaming Client를 찾을 수 없습니다. 다음 프레임에서 다시 시도합니다.", this);
            }
            else
            {
                Debug.Log("OptiTrack Streaming Client를 찾았습니다.", this);
            }
        }
    }

    private void InitializeBoneMapping()
    {
        m_optitrackToHumanBoneMap = new Dictionary<string, HumanBodyBones>();

        if (TargetAnimator == null || !TargetAnimator.isHuman)
        {
            Debug.LogError("휴머노이드 아바타가 설정되지 않았습니다.", this);
            return;
        }

        // OptiTrack 본 이름을 HumanBodyBones enum과 매핑
        // 스켈레톤 에셋 이름을 제외한 기본 매핑 설정
        SetupBoneNameMapping();
    }

    private void SetupBoneNameMapping()
    {
        // 기본 본 매핑 (스켈레톤 에셋 이름 없이)
        m_optitrackToHumanBoneMap.Add("Hip", HumanBodyBones.Hips);
        m_optitrackToHumanBoneMap.Add("Ab", HumanBodyBones.Spine);
        m_optitrackToHumanBoneMap.Add("Chest", HumanBodyBones.Chest);
        m_optitrackToHumanBoneMap.Add("Neck", HumanBodyBones.Neck);
        m_optitrackToHumanBoneMap.Add("Head", HumanBodyBones.Head);

        // 왼쪽 팔
        m_optitrackToHumanBoneMap.Add("LShoulder", HumanBodyBones.LeftShoulder);
        m_optitrackToHumanBoneMap.Add("LUArm", HumanBodyBones.LeftUpperArm);
        m_optitrackToHumanBoneMap.Add("LFArm", HumanBodyBones.LeftLowerArm);
        m_optitrackToHumanBoneMap.Add("LHand", HumanBodyBones.LeftHand);

        // 오른쪽 팔
        m_optitrackToHumanBoneMap.Add("RShoulder", HumanBodyBones.RightShoulder);
        m_optitrackToHumanBoneMap.Add("RUArm", HumanBodyBones.RightUpperArm);
        m_optitrackToHumanBoneMap.Add("RFArm", HumanBodyBones.RightLowerArm);
        m_optitrackToHumanBoneMap.Add("RHand", HumanBodyBones.RightHand);

        // 왼쪽 다리
        m_optitrackToHumanBoneMap.Add("LThigh", HumanBodyBones.LeftUpperLeg);
        m_optitrackToHumanBoneMap.Add("LShin", HumanBodyBones.LeftLowerLeg);
        m_optitrackToHumanBoneMap.Add("LFoot", HumanBodyBones.LeftFoot);
        m_optitrackToHumanBoneMap.Add("LToe", HumanBodyBones.LeftToes);

        // 오른쪽 다리
        m_optitrackToHumanBoneMap.Add("RThigh", HumanBodyBones.RightUpperLeg);
        m_optitrackToHumanBoneMap.Add("RShin", HumanBodyBones.RightLowerLeg);
        m_optitrackToHumanBoneMap.Add("RFoot", HumanBodyBones.RightFoot);
        m_optitrackToHumanBoneMap.Add("RToe", HumanBodyBones.RightToes);

        // 왼쪽 손가락들
        m_optitrackToHumanBoneMap.Add("LThumb1", HumanBodyBones.LeftThumbProximal);
        m_optitrackToHumanBoneMap.Add("LThumb2", HumanBodyBones.LeftThumbIntermediate);
        m_optitrackToHumanBoneMap.Add("LThumb3", HumanBodyBones.LeftThumbDistal);

        m_optitrackToHumanBoneMap.Add("LIndex1", HumanBodyBones.LeftIndexProximal);
        m_optitrackToHumanBoneMap.Add("LIndex2", HumanBodyBones.LeftIndexIntermediate);
        m_optitrackToHumanBoneMap.Add("LIndex3", HumanBodyBones.LeftIndexDistal);

        m_optitrackToHumanBoneMap.Add("LMiddle1", HumanBodyBones.LeftMiddleProximal);
        m_optitrackToHumanBoneMap.Add("LMiddle2", HumanBodyBones.LeftMiddleIntermediate);
        m_optitrackToHumanBoneMap.Add("LMiddle3", HumanBodyBones.LeftMiddleDistal);

        m_optitrackToHumanBoneMap.Add("LRing1", HumanBodyBones.LeftRingProximal);
        m_optitrackToHumanBoneMap.Add("LRing2", HumanBodyBones.LeftRingIntermediate);
        m_optitrackToHumanBoneMap.Add("LRing3", HumanBodyBones.LeftRingDistal);

        m_optitrackToHumanBoneMap.Add("LPinky1", HumanBodyBones.LeftLittleProximal);
        m_optitrackToHumanBoneMap.Add("LPinky2", HumanBodyBones.LeftLittleIntermediate);
        m_optitrackToHumanBoneMap.Add("LPinky3", HumanBodyBones.LeftLittleDistal);

        // 오른쪽 손가락들
        m_optitrackToHumanBoneMap.Add("RThumb1", HumanBodyBones.RightThumbProximal);
        m_optitrackToHumanBoneMap.Add("RThumb2", HumanBodyBones.RightThumbIntermediate);
        m_optitrackToHumanBoneMap.Add("RThumb3", HumanBodyBones.RightThumbDistal);

        m_optitrackToHumanBoneMap.Add("RIndex1", HumanBodyBones.RightIndexProximal);
        m_optitrackToHumanBoneMap.Add("RIndex2", HumanBodyBones.RightIndexIntermediate);
        m_optitrackToHumanBoneMap.Add("RIndex3", HumanBodyBones.RightIndexDistal);

        m_optitrackToHumanBoneMap.Add("RMiddle1", HumanBodyBones.RightMiddleProximal);
        m_optitrackToHumanBoneMap.Add("RMiddle2", HumanBodyBones.RightMiddleIntermediate);
        m_optitrackToHumanBoneMap.Add("RMiddle3", HumanBodyBones.RightMiddleDistal);

        m_optitrackToHumanBoneMap.Add("RRing1", HumanBodyBones.RightRingProximal);
        m_optitrackToHumanBoneMap.Add("RRing2", HumanBodyBones.RightRingIntermediate);
        m_optitrackToHumanBoneMap.Add("RRing3", HumanBodyBones.RightRingDistal);

        m_optitrackToHumanBoneMap.Add("RPinky1", HumanBodyBones.RightLittleProximal);
        m_optitrackToHumanBoneMap.Add("RPinky2", HumanBodyBones.RightLittleIntermediate);
        m_optitrackToHumanBoneMap.Add("RPinky3", HumanBodyBones.RightLittleDistal);
    }

    private IEnumerator CheckSkeletonConnectionPeriodically()
    {
        while (true)
        {
            if (StreamingClient != null)
            {
                m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName);

                if (m_skeletonDef != null)
                {
                    OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id);
                    isSkeletonFound = (skelState != null);

                    if (isSkeletonFound && previousSkeletonName != SkeletonAssetName)
                    {
                        StreamingClient.RegisterSkeleton(this, SkeletonAssetName);
                        previousSkeletonName = SkeletonAssetName;
                        Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}' 연결 성공");
                    }
                }
                else
                {
                    isSkeletonFound = false;
                }
            }

            yield return new WaitForSeconds(updateInterval);
        }
    }
}
