﻿using System;
using System.Runtime.CompilerServices;
using JLGames.RocketDriver.CSharp.Extensions;
using JLGames.RocketDriver.CSharp;
using JLGames.RocketDriver.CSharp.Utils;
using UnityEngine;
using Object = UnityEngine.Object;

namespace JLGames.RocketDriver.Actions.Animatorx
{
    /// <summary>
    /// Notice:
    ///   1. The function corresponding to functionName cannot be overloaded
    ///   2. functionName will only execute the function with the same name in the component under the node level where the Animator is located
    ///   3. When there are multiple components in functionName that have the functionName function, all of them will be executed
    ///   4. The array returned by the events property is in chronological order
    /// 注意：
    ///   1. functionName 对应的函数不能重载
    ///   2. functionName 只会执行Animator所在节点层次下组件内命名一致的函数
    ///   3. functionName 存在多个组件都有functionName函数时，全部执行
    ///   4. events属性返回的数组是按时间顺序
    /// </summary>
    public static class AnimationUtils
    {
        /// <summary>
        /// Get frame count
        /// 取帧数量
        /// </summary>
        /// <param name="clip"></param>
        /// <returns></returns>
        public static int GetFrameCount(this AnimationClip clip)
        {
            return Mathf.CeilToInt(clip.length * clip.frameRate);
        }

        /// <summary>
        /// Whether to include an Event that takes a string parameter
        /// 是否包含使用某个字符串参数的Event
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="stringParameter"></param>
        /// <returns></returns>
        public static bool ContainsEvent(this AnimationClip clip, string stringParameter)
        {
            var events = clip.events;
            if (null == events || events.Length == 0) return false;
            return Array.FindLastIndex(events, e => e.stringParameter == stringParameter) != -1;
        }

        /// <summary>
        /// Remove animation event by reference.
        /// 通过事件引用移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="evt"></param>
        [Obsolete("Can not find the AnimationEvent by reference.")]
        public static void RemoveEvent(this AnimationClip clip, AnimationEvent evt)
        {
            if (null == evt) return;
            var events = clip.events;
            if (null == events || events.Length == 0) return;
            var index = Array.FindLastIndex(events, e => e == evt);
            var arr = ArrayUtil.RemoveElement(events, index, 1);
            clip.events = arr;
        }

        /// <summary>
        /// Add Event
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="timeSeconds"></param>
        /// <param name="functionName"></param>
        public static void AddEvent(this AnimationClip clip, float timeSeconds, string functionName)
        {
            var evt = new AnimationEvent
            {
                time = timeSeconds,
                functionName = functionName,
            };
            clip.AddEvent(evt);
        }

        /// <summary>
        /// Add Event
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="timeSeconds"></param>
        /// <param name="functionName"></param>
        /// <param name="stringParameter"></param>
        public static void AddEvent(this AnimationClip clip, float timeSeconds, string functionName, string stringParameter)
        {
            var evt = new AnimationEvent
            {
                time = timeSeconds,
                functionName = functionName,
                stringParameter = stringParameter,
            };
            clip.AddEvent(evt);
        }

        /// <summary>
        /// Add Event
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="timeSeconds"></param>
        /// <param name="functionName"></param>
        /// <param name="intParameter"></param>
        public static void AddEvent(this AnimationClip clip, float timeSeconds, string functionName, int intParameter)
        {
            var evt = new AnimationEvent
            {
                time = timeSeconds,
                functionName = functionName,
                intParameter = intParameter,
            };
            clip.AddEvent(evt);
        }

        /// <summary>
        /// Add Event
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="timeSeconds"></param>
        /// <param name="functionName"></param>
        /// <param name="floatParameter"></param>
        public static void AddEvent(this AnimationClip clip, float timeSeconds, string functionName, float floatParameter)
        {
            var evt = new AnimationEvent
            {
                time = timeSeconds,
                functionName = functionName,
                floatParameter = floatParameter,
            };
            clip.AddEvent(evt);
        }

        /// <summary>
        /// Add Event
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="timeSeconds"></param>
        /// <param name="functionName"></param>
        /// <param name="objParameter"></param>
        public static void AddEvent(this AnimationClip clip, float timeSeconds, string functionName, Object objParameter)
        {
            var evt = new AnimationEvent
            {
                time = timeSeconds,
                functionName = functionName,
                objectReferenceParameter = objParameter,
            };
            clip.AddEvent(evt);
        }

        /// <summary>
        /// Remove animation event by string parameter.
        /// 通过字符参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="strParam"></param>
        /// <param name="others"></param>
        public static void RemoveEvent(this AnimationClip clip, string strParam, params string[] others)
        {
            if (0 != others.Length)
            {
                var arr = ArrayUtil.MergeArray(others, strParam);
                clip.RemoveEvent(arr);
                return;
            }

            if (string.IsNullOrEmpty(strParam)) return;
            var events = clip.events;
            if (null == events || events.Length == 0) return;
            var index = Array.FindLastIndex(events, e => e.stringParameter == strParam);
            events = ArrayUtil.RemoveElement(events, index, 1);
            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by string parameter.
        /// 通过字符参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="strParams"></param>
        public static void RemoveEvent(this AnimationClip clip, string[] strParams)
        {
            var events = clip.events;
            if (null == events || events.Length == 0 || null == strParams || strParams.Length == 0) return;
            for (var index = events.Length - 1; index >= 0; index--)
            {
                if (string.IsNullOrEmpty(events[index].stringParameter)) continue;
                if (-1 == Array.FindLastIndex(strParams, strParam
                        => strParam == events[index].stringParameter)) continue;
                events = ArrayUtil.RemoveElement(events, index, 1);
            }

            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by int parameter.
        /// 通过整形参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="intParam"></param>
        /// <param name="others"></param>
        public static void RemoveEvent(this AnimationClip clip, int intParam, params int[] others)
        {
            if (0 != others.Length)
            {
                var arr = ArrayUtil.MergeArray(others, intParam);
                clip.RemoveEvent(arr);
                return;
            }

            var events = clip.events;
            if (null == events || events.Length == 0) return;
            var index = Array.FindLastIndex(events, e => e.intParameter == intParam);
            events = ArrayUtil.RemoveElement(events, index, 1);
            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by int parameter.
        /// 通过整形参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="intParams"></param>
        public static void RemoveEvent(this AnimationClip clip, int[] intParams)
        {
            var events = clip.events;
            if (null == events || events.Length == 0 || null == intParams || intParams.Length == 0) return;
            for (var index = events.Length - 1; index >= 0; index--)
            {
                if (-1 == Array.FindLastIndex(intParams, intParam
                        => intParam == events[index].intParameter)) continue;
                events = ArrayUtil.RemoveElement(events, index, 1);
            }

            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by float parameter.
        /// 通过浮点参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="floatParam"></param>
        /// <param name="others"></param>
        public static void RemoveEvent(this AnimationClip clip, float floatParam, params float[] others)
        {
            if (0 != others.Length)
            {
                var arr = ArrayUtil.MergeArray(others, floatParam);
                clip.RemoveEvent(arr);
                return;
            }

            var events = clip.events;
            if (null == events || events.Length == 0) return;
            var index = Array.FindLastIndex(events, e => e.floatParameter.FloatEquals(floatParam));
            events = ArrayUtil.RemoveElement(events, index, 1);
            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by float parameter.
        /// 通过浮点参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="floatParams"></param>
        public static void RemoveEvent(this AnimationClip clip, float[] floatParams)
        {
            var events = clip.events;
            if (null == events || events.Length == 0 || null == floatParams || floatParams.Length == 0) return;
            for (var index = events.Length - 1; index >= 0; index--)
            {
                if (-1 == Array.FindLastIndex(floatParams, floatParam
                        => floatParam.FloatEquals(events[index].floatParameter))) continue;
                events = ArrayUtil.RemoveElement(events, index, 1);
            }

            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by float parameter.
        /// 通过对象参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="objParam"></param>
        /// <param name="others"></param>
        public static void RemoveEvent(this AnimationClip clip, Object objParam, params Object[] others)
        {
            if (0 != others.Length)
            {
                var arr = ArrayUtil.MergeArray(others, objParam);
                clip.RemoveEvent(arr);
                return;
            }

            var events = clip.events;
            if (null == events || events.Length == 0) return;
            var index = Array.FindLastIndex(events, e => e.objectReferenceParameter == objParam);
            events = ArrayUtil.RemoveElement(events, index, 1);
            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by float parameter.
        /// 通过对象参数移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="objParams"></param>
        public static void RemoveEvent(this AnimationClip clip, Object[] objParams)
        {
            var events = clip.events;
            if (null == events || events.Length == 0 || null == objParams || objParams.Length == 0) return;
            for (var index = events.Length - 1; index >= 0; index--)
            {
                if (-1 == Array.FindLastIndex(objParams, objParam
                        => objParam != null && objParam == events[index].objectReferenceParameter)) continue;
                events = ArrayUtil.RemoveElement(events, index, 1);
            }

            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by float parameter.
        /// 通过函数声名移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="funcName"></param>
        /// <param name="others"></param>
        public static void RemoveEventByFuncName(this AnimationClip clip, string funcName, params string[] others)
        {
            if (0 != others.Length)
            {
                var funcNames = ArrayUtil.MergeArray(others, funcName);
                clip.RemoveEventByFuncName(funcNames);
                return;
            }

            var events = clip.events;
            if (null == events || events.Length == 0) return;
            var index = Array.FindLastIndex(events, e => e.functionName == funcName);
            events = ArrayUtil.RemoveElement(events, index, 1);
            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by float parameter.
        /// 通过函数声名移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="funcNames"></param>
        public static void RemoveEventByFuncName(this AnimationClip clip, string[] funcNames)
        {
            var events = clip.events;
            if (null == events || events.Length == 0 || null == funcNames || funcNames.Length == 0) return;
            for (var index = events.Length - 1; index >= 0; index--)
            {
                if (-1 == Array.FindLastIndex(funcNames, funcName
                        => funcName == events[index].functionName)) continue;
                events = ArrayUtil.RemoveElement(events, index, 1);
            }

            clip.events = events;
        }

        /// <summary>
        /// Remove animation event by event index.
        /// 通过事件索引移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="eventIndex"></param>
        /// <param name="others"></param>
        public static void RemoveEventByIndex(this AnimationClip clip, int eventIndex, params int[] others)
        {
            if (0 != others.Length)
            {
                var indexs = ArrayUtil.MergeArray(others, eventIndex);
                clip.RemoveEventByIndex(indexs);
                return;
            }

            var events = clip.events;
            if (null == events || events.Length == 0 || eventIndex >= events.Length) return;
            var arr = ArrayUtil.RemoveElement(events, eventIndex, 1);
            clip.events = arr;
        }

        /// <summary>
        /// Remove animation event by event index.
        /// 通过事件索引移除动画事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="eventIndexs"></param>
        public static void RemoveEventByIndex(this AnimationClip clip, int[] eventIndexs)
        {
            var events = clip.events;
            if (null == events || events.Length == 0 || null == eventIndexs || eventIndexs.Length == 0) return;
            Array.Sort(eventIndexs, (i1, i2) => i2.CompareTo(i1));
            for (var index = eventIndexs.Length - 1; index >= 0; index--)
            {
                if (eventIndexs[index] >= events.Length) continue;
                events = ArrayUtil.RemoveElement(events, eventIndexs[index], 1);
            }

            clip.events = events;
        }

        // ----------------------------------------------

        /// <summary>
        /// Add Event base on AnimationEventInvoker
        /// 基于AnimationEventInvoker添加事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="target"></param>
        /// <param name="seconds"></param>
        /// <param name="eventName"></param>
        /// <param name="evtCall"></param>
        public static void AddInvokerEvent(this AnimationClip clip, GameObject target, float seconds, string eventName, Callback evtCall)
        {
            var invoker = AnimationEventInvoker.TryAddInvoker(target);
            invoker.AddEventCall(eventName, evtCall);
            clip.AddEvent(seconds, AnimationEventInvoker.FunctionName, eventName);
//            PrintEvents(clip.events);
        }

        /// <summary>
        /// Add Event base on AnimationEventInvoker
        /// 基于AnimationEventInvoker添加事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="target"></param>
        /// <param name="seconds"></param>
        /// <param name="eventName"></param>
        /// <param name="evtFunc"></param>
        [MethodImpl((MethodImplOptions) 256)]
        public static void AddInvokerEvent(this AnimationClip clip, GameObject target, float seconds, string eventName, Callback.Func evtFunc)
        {
            clip.AddInvokerEvent(target, seconds, eventName, new Callback(evtFunc));
        }

        /// <summary>
        /// Remove Event base on AnimationEventInvoker
        /// 基于AnimationEventInvoker添加事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="target"></param>
        /// <param name="eventName"></param>
        public static void RemoveInvokerEvent(this AnimationClip clip, GameObject target, string eventName)
        {
            clip.RemoveEvent(eventName);
            var invoker = target.GetComponent<AnimationEventInvoker>();
            if (null == invoker) return;
            invoker.RemoveEventCall(eventName);
        }

        /// <summary>
        /// Add event at the end of the animation base on AnimationEventInvoker
        /// 基于AnimationEventInvoker，在动画的结束点添加事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="target"></param>
        /// <param name="eventName"></param>
        /// <param name="evtCall"></param>
        public static void AddInvokerEventEnd(this AnimationClip clip, GameObject target, string eventName, Callback evtCall)
        {
            clip.AddInvokerEvent(target, clip.length, eventName, evtCall);
        }

        /// <summary>
        /// Add event at the end of the animation base on AnimationEventInvoker
        /// 基于AnimationEventInvoker，在动画的结束点添加事件
        /// </summary>
        /// <param name="clip"></param>
        /// <param name="target"></param>
        /// <param name="eventName"></param>
        /// <param name="evtFunc"></param>
        public static void AddInvokerEventEnd(this AnimationClip clip, GameObject target, string eventName, Callback.Func evtFunc)
        {
            clip.AddInvokerEvent(target, clip.length, eventName, new Callback(evtFunc));
        }

#if UNITY_EDITOR
        /// <summary>
        /// Print all clip names
        /// 打印全部剪辑名称
        /// </summary>
        /// <param name="events"></param>
        public static void PrintEvents(AnimationEvent[] events)
        {
            Debug.Log($"Event Size={events?.Length}");
            if (null == events || events.Length == 0) return;
            for (var index = 0; index < events.Length; index++)
            {
                var evt = events[index];
                Debug.Log($"{index}. Time='{evt.time:F2}', FuncName='{evt.functionName}'," +
                          $"Int='{evt.intParameter}', Float='{evt.floatParameter}', " +
                          $"String='{evt.stringParameter}', Object='{evt.objectReferenceParameter}'");
            }
        }
#endif
    }
}