// Animancer // Copyright 2020 Kybernetik //

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

namespace Animancer
{
    partial struct AnimancerEvent
    {
        /// <summary>
        /// A variable-size list of <see cref="AnimancerEvent"/>s which keeps itself sorted by
        /// <see cref="normalizedTime"/>.
        /// <para></para>
        /// Animancer Lite does not allow the use of events in a runtime build, except for <see cref="OnEnd"/>.
        /// </summary>
        public sealed partial class Sequence : IEnumerable<AnimancerEvent>
        {
            /************************************************************************************************************************/
            #region Fields and Properties
            /************************************************************************************************************************/

            private const string
                NoCallbackError = "Event has no callback",
                IndexTooHighError = "index must be less than Count and not negative";

            /// <summary>
            /// A zero length array of <see cref="AnimancerEvent"/>s which is used by all lists before any elements are
            /// added to them (unless their <see cref="Capacity"/> is set manually).
            /// </summary>
            public static readonly AnimancerEvent[] EmptyArray = new AnimancerEvent[0];

            /// <summary>The initial <see cref="Capacity"/> that will be used if another value is not specified.</summary>
            public const int DefaultCapacity = 8;

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

            /// <summary>
            /// An <see cref="AnimancerEvent"/> which denotes the end of the animation. Its values can be accessed via
            /// <see cref="OnEnd"/> and <see cref="NormalizedEndTime"/>.
            /// <para></para>
            /// By default, the <see cref="normalizedTime"/> will be <see cref="float.NaN"/> so that it can choose the
            /// correct value based on the current play direction: forwards ends at 1 and backwards ends at 0.
            /// <para></para>
            /// Animancer Lite does not allow the <see cref="normalizedTime"/> to be changed in a runtime build.
            /// </summary>
            ///
            /// <example>
            /// <code>
            /// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
            /// {
            ///     var state = animancer.Play(clip);
            ///     state.Events.OnEnd = OnAnimationEnd;
            ///     state.Events.NormalizedEndTime = 0.75f;
            ///
            ///     // Or set the time and callback at the same time:
            ///     state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
            /// }
            ///
            /// void OnAnimationEnd()
            /// {
            ///     Debug.Log("Animation ended");
            /// }
            /// </code>
            /// </example>
            ///
            /// <remarks>
            /// See the documentation for more information about
            /// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">
            /// End Events</see>.
            /// </remarks>
            public AnimancerEvent endEvent = new AnimancerEvent(float.NaN, null);

            /// <summary>The internal array in which the events are stored (excluding the <see cref="endEvent"/>).</summary>
            private AnimancerEvent[] _Events;

            /// <summary>[Pro-Only] The number of events in this sequence (excluding the <see cref="endEvent"/>).</summary>
            public int Count { get; private set; }

            /// <summary>[Pro-Only]
            /// The number of times the contents of this sequence have been modified. This applies to general events,
            /// but not the <see cref="endEvent"/>.
            /// </summary>
            public int Version { get; private set; }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Constructors
            /************************************************************************************************************************/

            /// <summary>
            /// Creates a new <see cref="Sequence"/> which starts at 0 <see cref="Capacity"/>.
            /// <para></para>
            /// Adding anything to the list will set the <see cref="Capacity"/> = <see cref="DefaultCapacity"/>
            /// and then double it whenever the <see cref="Count"/> would exceed the <see cref="Capacity"/>.
            /// </summary>
            public Sequence()
            {
                _Events = EmptyArray;
            }

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

            /// <summary>[Pro-Only]
            /// Creates a new <see cref="Sequence"/> which starts with the specified <see cref="Capacity"/>. It will be
            /// initially empty, but will have room for the given number of elements before any reallocations are
            /// required.
            /// </summary>
            public Sequence(int capacity)
            {
                _Events = capacity > 0 ? new AnimancerEvent[capacity] : EmptyArray;
            }

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

            /// <summary>
            /// Creates a new <see cref="Sequence"/>, copying the contents of `copyFrom` into it.
            /// </summary>
            public Sequence(Sequence copyFrom)
            {
                CopyFrom(copyFrom);
            }

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

            /// <summary>[Pro-Only]
            /// Creates a new <see cref="Sequence"/>, copying and sorting the contents of the `collection` into it.
            /// The <see cref="Count"/> and <see cref="Capacity"/> will be equal to the
            /// <see cref="ICollection{T}.Count"/>.
            /// </summary>
            public Sequence(ICollection<AnimancerEvent> collection)
            {
                if (collection == null)
                    throw new ArgumentNullException("collection");

                var count = collection.Count;
                if (count == 0)
                {
                    _Events = EmptyArray;
                }
                else
                {
                    _Events = new AnimancerEvent[count];
                    AddRange(collection);
                }
            }

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

            /// <summary>[Pro-Only]
            /// Creates a new <see cref="Sequence"/>, copying and sorting the contents of the `enumerable` into it.
            /// </summary>
            public Sequence(IEnumerable<AnimancerEvent> enumerable)
            {
                if (enumerable == null)
                    throw new ArgumentNullException("enumerable");

                _Events = EmptyArray;
                AddRange(enumerable);
            }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Iteration
            /************************************************************************************************************************/

            /// <summary>
            /// Indicates whether the list has any events in it or the <see cref="endEvent"/> event's
            /// <see cref="normalizedTime"/> is not at the default value (1).
            /// </summary>
            public bool IsEmpty
            {
                get
                {
                    return
                        endEvent.callback == null &&
                        float.IsNaN(endEvent.normalizedTime) &&
                        Count == 0;
                }
            }

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

            /// <summary>[Pro-Only]
            /// The size of the internal array used to hold events.
            /// <para></para>
            /// When set, the array is reallocated to the given size.
            /// <para></para>
            /// By default, the <see cref="Capacity"/> starts at 0 and increases to the <see cref="DefaultCapacity"/>
            /// when the first event is added.
            /// </summary>
            public int Capacity
            {
                get { return _Events.Length; }
                set
                {
                    if (value < Count)
                        throw new ArgumentOutOfRangeException("value", "Capacity cannot be set lower than Count");

                    if (value == _Events.Length)
                        return;

                    if (value > 0)
                    {
                        var newEvents = new AnimancerEvent[value];
                        if (Count > 0)
                            Array.Copy(_Events, 0, newEvents, 0, Count);
                        _Events = newEvents;
                    }
                    else
                    {
                        _Events = EmptyArray;
                    }
                }
            }

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

            /// <summary>[Pro-Only] Gets the event at the specified `index`.</summary>
            public AnimancerEvent this[int index]
            {
                get
                {
                    Debug.Assert((uint)index < (uint)Count, IndexTooHighError);
                    return _Events[index];
                }
            }

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

            /// <summary>[Assert]
            /// Throws an <see cref="ArgumentOutOfRangeException"/> if the <see cref="normalizedTime"/> of any events
            /// is less than 0 or greater than or equal to 1.
            /// <para></para>
            /// This does not include the <see cref="endEvent"/> since it works differently to other events.
            /// </summary>
            [System.Diagnostics.Conditional(Strings.Assert)]
            public void AssertNormalizedTimes()
            {
                if (Count == 0 ||
                    (_Events[0].normalizedTime >= 0 && _Events[Count - 1].normalizedTime < 1))
                    return;

                throw new ArgumentOutOfRangeException("The normalized time of an event in the Sequence is" +
                    " < 0 or >= 1, which is not allowed on looping animations. " + DeepToString());
            }

            /// <summary>[Assert]
            /// Calls <see cref="AssertNormalizedTimes()"/> if `isLooping` is true.
            /// </summary>
            [System.Diagnostics.Conditional(Strings.Assert)]
            public void AssertNormalizedTimes(bool isLooping)
            {
                if (isLooping)
                    AssertNormalizedTimes();
            }

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

            /// <summary>Returns a string containing the details of all events in this sequence.</summary>
            public string DeepToString(bool multiLine = true)
            {
                var text = new StringBuilder()
                    .Append(ToString())
                    .Append(" [")
                    .Append(Count)
                    .Append(multiLine ? "]\n{" : "] { ");

                for (int i = 0; i < Count; i++)
                {
                    if (multiLine)
                        text.Append("\n    ");
                    else if (i > 0)
                        text.Append(", ");

                    text.Append(this[i]);
                }

                text.Append(multiLine ? "\n}\nendEvent=" : " } (endEvent=")
                    .Append(endEvent);

                if (!multiLine)
                    text.Append(")");

                return text.ToString();
            }

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

            /// <summary>[Pro-Only] Returns an <see cref="Enumerator"/> for this sequence.</summary>
            public Enumerator GetEnumerator()
            {
                return new Enumerator(this);
            }

            IEnumerator<AnimancerEvent> IEnumerable<AnimancerEvent>.GetEnumerator()
            {
                return new Enumerator(this);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return new Enumerator(this);
            }

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

            /// <summary>[Pro-Only]
            /// An iterator that can cycle through every event in a <see cref="Sequence"/> except for the
            /// <see cref="endEvent"/>.
            /// </summary>
            public struct Enumerator : IEnumerator<AnimancerEvent>
            {
                /************************************************************************************************************************/

                /// <summary>The target <see cref="AnimancerEvent.Sequence"/>.</summary>
                public readonly Sequence Sequence;

                private int _Index;
                private int _Version;
                private AnimancerEvent _Current;

                private const string InvalidVersion =
                    "AnimancerEvent.Sequence was modified. Enumeration operation may not execute.";

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

                /// <summary>The event this iterator is currently pointing to.</summary>
                public AnimancerEvent Current { get { return _Current; } }

                /// <summary>The event this iterator is currently pointing to.</summary>
                object IEnumerator.Current
                {
                    get
                    {
                        if (_Index == 0 || _Index == Sequence.Count + 1)
                            throw new InvalidOperationException(
                                "Operation is not valid due to the current state of the object.");

                        return Current;
                    }
                }

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

                /// <summary>Creates a new <see cref="Enumerator"/>.</summary>
                public Enumerator(Sequence sequence)
                {
                    Sequence = sequence;
                    _Index = 0;
                    _Version = sequence.Version;
                    _Current = default(AnimancerEvent);
                }

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

                void IDisposable.Dispose() { }

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

                /// <summary>
                /// Moves to the next event in the <see cref="Sequence"/> and returns true if there is one.
                /// </summary>
                /// <exception cref="InvalidOperationException">
                /// Thrown if the <see cref="Version"/> has changed since this iterator was created.
                /// </exception>
                public bool MoveNext()
                {
                    if (_Version != Sequence.Version)
                        throw new InvalidOperationException(InvalidVersion);

                    if ((uint)_Index < (uint)Sequence.Count)
                    {
                        _Current = Sequence._Events[_Index];
                        _Index++;
                        return true;
                    }
                    else
                    {
                        _Index = Sequence.Count + 1;
                        _Current = default(AnimancerEvent);
                        return false;
                    }
                }

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

                /// <summary>
                /// Returns this iterator to the start of the <see cref="Sequence"/>.
                /// </summary>
                /// <exception cref="InvalidOperationException">
                /// Thrown if the <see cref="Version"/> has changed since this iterator was created.
                /// </exception>
                void IEnumerator.Reset()
                {
                    if (_Version != Sequence.Version)
                        throw new InvalidOperationException(InvalidVersion);

                    _Index = 0;
                    _Current = default(AnimancerEvent);
                }

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

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Modification
            /************************************************************************************************************************/

            /// <summary>[Pro-Only]
            /// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
            /// <see cref="Capacity"/> is doubled to fit the new event.
            /// <para></para>
            /// This methods returns the index at which the event is added, which is determined by its
            /// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
            /// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
            /// </summary>
            public int Add(AnimancerEvent animancerEvent)
            {
                Debug.Assert(animancerEvent.callback != null, NoCallbackError);
                var index = Insert(animancerEvent.normalizedTime);
                _Events[index] = animancerEvent;
                return index;
            }

            /// <summary>[Pro-Only]
            /// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
            /// <see cref="Capacity"/> is doubled to fit the new event.
            /// <para></para>
            /// This methods returns the index at which the event is added, which is determined by its
            /// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
            /// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
            /// </summary>
            public int Add(float normalizedTime, Action callback)
            {
                return Add(new AnimancerEvent(normalizedTime, callback));
            }

            /// <summary>[Pro-Only]
            /// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
            /// <see cref="Capacity"/> is doubled to fit the new event.
            /// <para></para>
            /// This methods returns the index at which the event is added, which is determined by its
            /// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
            /// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
            /// </summary>
            public int Add(int indexHint, AnimancerEvent animancerEvent)
            {
                Debug.Assert(animancerEvent.callback != null, NoCallbackError);
                indexHint = Insert(indexHint, animancerEvent.normalizedTime);
                _Events[indexHint] = animancerEvent;
                return indexHint;
            }

            /// <summary>[Pro-Only]
            /// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
            /// <see cref="Capacity"/> is doubled to fit the new event.
            /// <para></para>
            /// This methods returns the index at which the event is added, which is determined by its
            /// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
            /// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
            /// </summary>
            public int Add(int indexHint, float normalizedTime, Action callback)
            {
                return Add(indexHint, new AnimancerEvent(normalizedTime, callback));
            }

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

            /// <summary>[Pro-Only]
            /// Adds every event in the `enumerable` to this list. The <see cref="Count"/> is increased by one and if
            /// required, the <see cref="Capacity"/> is doubled to fit the new event.
            /// <para></para>
            /// This methods returns the index at which the event is added, which is determined by its
            /// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
            /// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
            /// </summary>
            public void AddRange(IEnumerable<AnimancerEvent> enumerable)
            {
                foreach (var item in enumerable)
                    Add(item);
            }

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

            /// <summary>[Pro-Only]
            /// Replaces the <see cref="callback"/> of the event at the specified `index`.
            /// </summary>
            public void Set(int index, Action callback)
            {
                var animancerEvent = _Events[index];
                animancerEvent.callback = callback;
                _Events[index] = animancerEvent;
                Version++;
            }

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

            /// <summary>[Pro-Only]
            /// Determines the index where a new event with the specified `normalizedTime` should be added in order to
            /// keep this list sorted, increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if
            /// required, moves any existing events to open up the chosen index, and returns that index.
            /// <para></para>
            /// This overload starts searching for the desired index from the end of the list, using the assumption
            /// that elements will usually be added in order.
            /// </summary>
            private int Insert(float normalizedTime)
            {
                var index = Count;
                while (index > 0 && _Events[index - 1].normalizedTime > normalizedTime)
                    index--;
                Insert(index);
                return index;
            }

            /// <summary>[Pro-Only]
            /// Determines the index where a new event with the specified `normalizedTime` should be added in order to
            /// keep this list sorted, increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if
            /// required, moves any existing events to open up the chosen index, and returns that index.
            /// <para></para>
            /// This overload starts searching for the desired index from the `hint`.
            /// </summary>
            private int Insert(int hint, float normalizedTime)
            {
                if (hint >= Count)
                    return Insert(normalizedTime);

                if (_Events[hint].normalizedTime > normalizedTime)
                {
                    while (hint > 0 && _Events[hint - 1].normalizedTime > normalizedTime)
                        hint--;
                }
                else
                {
                    while (hint < Count && _Events[hint].normalizedTime <= normalizedTime)
                        hint++;
                }

                Insert(hint);
                return hint;
            }

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

            /// <summary>[Pro-Only]
            /// Increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if required, and moves any
            /// existing events to open up the `index`.
            /// </summary>
            private void Insert(int index)
            {
                Debug.Assert((uint)index <= (uint)Count, "index must be less than or equal to Count");

                var capacity = _Events.Length;
                if (Count == capacity)
                {
                    if (capacity == 0)
                    {
                        _Events = new AnimancerEvent[DefaultCapacity];
                    }
                    else
                    {
                        capacity *= 2;
                        if (capacity < DefaultCapacity)
                            capacity = DefaultCapacity;

                        var newEvents = new AnimancerEvent[capacity];

                        Array.Copy(_Events, 0, newEvents, 0, index);
                        if (Count > index)
                            Array.Copy(_Events, index, newEvents, index + 1, Count - index);

                        _Events = newEvents;
                    }
                }
                else if (Count > index)
                {
                    Array.Copy(_Events, index, _Events, index + 1, Count - index);
                }

                Count++;
                Version++;
            }

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

            /// <summary>[Pro-Only]
            /// Removes the event at the specified `index` from this list by decrementing the <see cref="Count"/> and
            /// copying all events after the removed one down one place.
            /// </summary>
            public void Remove(int index)
            {
                Debug.Assert((uint)index < (uint)Count, IndexTooHighError);
                Count--;
                if (index < Count)
                    Array.Copy(_Events, index + 1, _Events, index, Count - index);
                _Events[Count] = default(AnimancerEvent);
                Version++;
            }

            /// <summary>[Pro-Only]
            /// Removes the `animancerEvent` from this list by decrementing the <see cref="Count"/> and copying all
            /// events after the removed one down one place. Returns true if the event was found and removed.
            /// </summary>
            public bool Remove(AnimancerEvent animancerEvent)
            {
                var index = Array.IndexOf(_Events, animancerEvent);
                if (index >= 0)
                {
                    Remove(index);
                    return true;
                }
                else return false;
            }

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

            /// <summary>[Pro-Only]
            /// Removes all events except the <see cref="endEvent"/>.
            /// <seealso cref="Clear"/>
            /// </summary>
            public void RemoveAll()
            {
                Array.Clear(_Events, 0, Count);
                Count = 0;
                Version++;
            }

            /// <summary>
            /// Removes all events, including the <see cref="endEvent"/>.
            /// <seealso cref="RemoveAll"/>
            /// </summary>
            public void Clear()
            {
                RemoveAll();
                endEvent = new AnimancerEvent(float.NaN, null);
            }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region On End
            /************************************************************************************************************************/

            /// <summary>
            /// Shorthand for the <c>endEvent.callback</c>. This callback is triggered when the animation passes the
            /// <see cref="NormalizedEndTime"/> (not when the state is interrupted or exited for whatever reason).
            /// <para></para>
            /// Unlike regular events, this callback will be triggered every frame while it is past the end so if you
            /// want to ensure that your callback only occurs once, you will need to clear it as part of that callback.
            /// <para></para>
            /// This callback is automatically cleared by <see cref="AnimancerState.Play"/>,
            /// <see cref="AnimancerState.OnStartFade"/>, and <see cref="AnimancerState.Stop"/>.
            /// </summary>
            ///
            /// <example>
            /// <code>
            /// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
            /// {
            ///     var state = animancer.Play(clip);
            ///     state.Events.OnEnd = OnAnimationEnd;
            ///     state.Events.NormalizedEndTime = 0.75f;
            ///
            ///     // Or set the time and callback at the same time:
            ///     state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
            /// }
            ///
            /// void OnAnimationEnd()
            /// {
            ///     Debug.Log("Animation ended");
            /// }
            /// </code>
            /// </example>
            ///
            /// <remarks>
            /// See the documentation for more information about
            /// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">
            /// End Events</see>.
            /// </remarks>
            public Action OnEnd
            {
                get { return endEvent.callback; }
                set { endEvent.callback = value; }
            }

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

            /// <summary>[Pro-Only]
            /// Shorthand for <c>endEvent.normalizedTime</c>.
            /// <para></para>
            /// By default, this value will be <see cref="float.NaN"/> so that it can choose the correct value based on
            /// the current play direction: forwards ends at 1 and backwards ends at 0.
            /// <para></para>
            /// Animancer Lite does not allow this value to be changed in a runtime build.
            /// </summary>
            ///
            /// <example>
            /// <code>
            /// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
            /// {
            ///     var state = animancer.Play(clip);
            ///     state.Events.OnEnd = OnAnimationEnd;
            ///     state.Events.NormalizedEndTime = 0.75f;
            ///
            ///     // Or set the time and callback at the same time:
            ///     state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
            /// }
            ///
            /// void OnAnimationEnd()
            /// {
            ///     Debug.Log("Animation ended");
            /// }
            /// </code>
            /// </example>
            ///
            /// <remarks>
            /// See the documentation for more information about
            /// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">
            /// End Events</see>.
            /// </remarks>
            public float NormalizedEndTime
            {
                get { return endEvent.normalizedTime; }
                set { endEvent.normalizedTime = value; }
            }

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

            /// <summary>
            /// The default <see cref="AnimancerState.NormalizedTime"/> for an animation to start at when playing
            /// forwards is 0 (the start of the animation) and when playing backwards is 1 (the end of the animation).
            /// <para></para>
            /// `speed` 0 or <see cref="float.NaN"/> will also return 0.
            /// </summary>
            /// <remarks>
            /// This method has nothing to do with events, so it is only here because of
            /// <see cref="GetDefaultNormalizedEndTime"/>.
            /// </remarks>
            public static float GetDefaultNormalizedStartTime(float speed)
            {
                return speed < 0 ? 1 : 0;
            }

            /// <summary>
            /// The default <see cref="normalizedTime"/> for an <see cref="endEvent"/> when playing forwards is 1 (the
            /// end of the animation) and when playing backwards is 0 (the start of the animation).
            /// <para></para>
            /// `speed` 0 or <see cref="float.NaN"/> will also return 1.
            /// </summary>
            public static float GetDefaultNormalizedEndTime(float speed)
            {
                return speed < 0 ? 0 : 1;
            }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Copying
            /************************************************************************************************************************/

            /// <summary>
            /// Copies all the events from the `source` to replace the previous contents of this list.
            /// </summary>
            public void CopyFrom(Sequence source)
            {
                var sourceCount = source.Count;

                if (Count > sourceCount)
                    Array.Clear(_Events, Count, sourceCount - Count);
                else if (_Events.Length < sourceCount)
                    Capacity = sourceCount;

                Count = sourceCount;

                Array.Copy(source._Events, 0, _Events, 0, sourceCount);

                endEvent = source.endEvent;
            }

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

            /// <summary>[<see cref="ICollection{T}"/>]
            /// Copies all the events from this list into the `array`, starting at the `index`.
            /// </summary>
            public void CopyTo(AnimancerEvent[] array, int index)
            {
                Array.Copy(_Events, 0, array, index, Count);
            }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
        }
    }
}