/** @param last May be null. */
  private TrackEntry trackEntry(
      int trackIndex, Animation animation, boolean loop, TrackEntry last) {
    TrackEntry entry = trackEntryPool.obtain();
    entry.trackIndex = trackIndex;
    entry.animation = animation;
    entry.loop = loop;

    entry.eventThreshold = 0;
    entry.attachmentThreshold = 0;
    entry.drawOrderThreshold = 0;

    entry.animationStart = 0;
    entry.animationEnd = animation.getDuration();
    entry.animationLast = -1;
    entry.nextAnimationLast = -1;

    entry.delay = 0;
    entry.trackTime = 0;
    entry.trackLast = -1;
    entry.nextTrackLast = -1;
    entry.trackEnd = Float.MAX_VALUE;
    entry.timeScale = 1;

    entry.alpha = 1;
    entry.mixAlpha = 1;
    entry.mixTime = 0;
    entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation);
    return entry;
  }
  /**
   * Adds an animation to be played after the current or last queued animation for a track. If the
   * track is empty, it is equivalent to calling {@link #setAnimation(int, Animation, boolean)}.
   *
   * @param delay Seconds to begin this animation after the start of the previous animation. May be
   *     <= 0 to use the animation duration of the previous track minus any mix duration plus the
   *     <code>delay</code>.
   * @return A track entry to allow further customization of animation playback. References to the
   *     track entry must not be kept after the {@link AnimationStateListener#dispose(TrackEntry)}
   *     event occurs.
   */
  public TrackEntry addAnimation(int trackIndex, Animation animation, boolean loop, float delay) {
    if (animation == null) throw new IllegalArgumentException("animation cannot be null.");

    TrackEntry last = expandToIndex(trackIndex);
    if (last != null) {
      while (last.next != null) last = last.next;
    }

    TrackEntry entry = trackEntry(trackIndex, animation, loop, last);

    if (last == null) {
      setCurrent(trackIndex, entry, true);
      queue.drain();
    } else {
      last.next = entry;
      if (delay <= 0) {
        float duration = last.animationEnd - last.animationStart;
        if (duration != 0)
          delay +=
              duration * (1 + (int) (last.trackTime / duration))
                  - data.getMix(last.animation, animation);
        else delay = 0;
      }
    }

    entry.delay = delay;
    return entry;
  }
  private void setCurrent(int index, TrackEntry entry) {
    TrackEntry current = expandToIndex(index);
    if (current != null) {
      TrackEntry previous = current.previous;
      current.previous = null;

      if (current.listener != null) current.listener.end(index);
      for (int i = 0, n = listeners.size; i < n; i++) listeners.get(i).end(index);

      entry.mixDuration = data.getMix(current.animation, entry.animation);
      if (entry.mixDuration > 0) {
        entry.mixTime = 0;
        // If a mix is in progress, mix from the closest animation.
        if (previous != null && current.mixTime / current.mixDuration < 0.5f) {
          entry.previous = previous;
          previous = current;
        } else entry.previous = current;
      } else trackEntryPool.free(current);

      if (previous != null) trackEntryPool.free(previous);
    }

    tracks.set(index, entry);

    if (entry.listener != null) entry.listener.start(index);
    for (int i = 0, n = listeners.size; i < n; i++) listeners.get(i).start(index);
  }
  /**
   * Adds an animation to be played delay seconds after the current or last queued animation.
   *
   * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the
   *     negative delay.
   */
  public TrackEntry addAnimation(int trackIndex, Animation animation, boolean loop, float delay) {
    TrackEntry entry = Pools.obtain(TrackEntry.class);
    entry.animation = animation;
    entry.loop = loop;
    entry.endTime = animation.getDuration();

    TrackEntry last = expandToIndex(trackIndex);
    if (last != null) {
      while (last.next != null) last = last.next;
      last.next = entry;
    } else tracks.set(trackIndex, entry);

    if (delay <= 0) {
      if (last != null) delay += last.endTime - data.getMix(last.animation, animation);
      else delay = 0;
    }
    entry.delay = delay;

    return entry;
  }
  private void setCurrent(int index, TrackEntry entry) {
    TrackEntry current = expandToIndex(index);
    if (current != null) {
      if (current.previous != null) {
        Pools.free(current.previous);
        current.previous = null;
      }

      if (current.listener != null) current.listener.end(index);
      for (int i = 0, n = listeners.size; i < n; i++) listeners.get(i).end(index);

      entry.mixDuration = data.getMix(current.animation, entry.animation);
      if (entry.mixDuration > 0) {
        entry.mixTime = 0;
        entry.previous = current;
      } else Pools.free(current);
    }

    tracks.set(index, entry);

    if (entry.listener != null) entry.listener.start(index);
    for (int i = 0, n = listeners.size; i < n; i++) listeners.get(i).start(index);
  }
 /** {@link #addAnimation(int, Animation, boolean, float)} */
 public TrackEntry addAnimation(int trackIndex, String animationName, boolean loop, float delay) {
   Animation animation = data.getSkeletonData().findAnimation(animationName);
   if (animation == null)
     throw new IllegalArgumentException("Animation not found: " + animationName);
   return addAnimation(trackIndex, animation, loop, delay);
 }