// Copyright (c) 2021 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#pragma once
#include "../../libs.h"
#include "../../util/math.h"
#include "easing.h"

/** Maximum number of actions a timeline can support, smaller than 0xFF */
#define TIMELINE_ACTION_COUNT_MAX 128

/** Type forwarders */
typedef struct _timeline_t timeline_t;
typedef struct _timelineaction_t timelineaction_t;

/**
 * Callback for when a timeline event occurs
 * @param timeline The timeline that fired this callback.
 * @param action The action that this callback is attached to.
 * @param i The index that this action is within the timeline.
 */
typedef void timelinecallback_t(timeline_t *timeline, timelineaction_t *action,
  uint8_t i
);

typedef struct _timelineaction_t {
  /** Pointer to any custom user data the timeline action wants to use. */
  void *data;

  /**
   * The time that this action should occur within the timeline
   * set to 0 or less to start immediately.
   */
  float start;

  /**
   * The duration of the action, this will decide for how long onDuration will
   * be called for, and when onEnd should be called.
   * Set to a negative number to have this continue forever.
   * Set to 0 to only fire the onStart. 
   * onStart can fire with either onDuration and onEnd on the same frame, but
   * onEnd and onDuration cannot fire the same frame.
   */
  float duration;

  /**
   * Enables animation looping. This works by forcing start to be equal to the
   * current time at the point in time that onEnd is called. This will also stop
   * onStart being called so ensure that your onStart and onEnd logic works.
   */
  bool loop;

  timelinecallback_t *onStart;
  
  timelinecallback_t *onDuration;

  timelinecallback_t *onEnd;
} timelineaction_t;

typedef struct _timeline_t {
  /** The current time as far as the timeline is concerned */
  float current;
  
  /** The time of the last "frame" as far as the timeline is concerned */
  float previous;

  /** The frame time diff, essentially current = previous + diff */
  float diff;

  /** User pointer, allows you to point to some other data */
  void *user;

  /** Actions within the timeline */
  timelineaction_t actions[TIMELINE_ACTION_COUNT_MAX];

  /** For delta actions, storage of the initial values */
  float initials[TIMELINE_ACTION_COUNT_MAX];
  /** For delta actions, storage of their deltas. */
  float deltas[TIMELINE_ACTION_COUNT_MAX];
  /** Easing functions to use for delta functions */
  easefunction_t *easings[TIMELINE_ACTION_COUNT_MAX];

  uint8_t actionCount;
} timeline_t;


/**
 * Initializes a timeline back to its default state.
 * 
 * @param timeline Timeline to initialize.
 */
void timelineInit(timeline_t *timeline);

/**
 * Ticks the timeline. This can be done using any delta.
 * 
 * @param timeline Timeline to tick
 * @param delta Delta to tick.
 */
void timelineUpdate(timeline_t *timeline, float delta);

/**
 * Returns true if every action in the timeline has finished.
 * 
 * @param timeline Timeline to check
 * @return True if finished, otherwise false.
 */
bool timelineIsFinished(timeline_t *timeline);

/**
 * Adds an action to the timeline. This will initialize the action callbacks to
 * NULL, you will need to initialize your own callbacks.
 * 
 * @param timeline Timeline to add to.
 * @param start Start time.
 * @param duration Duration time
 * @return Pointer to the timeline action or NULL if the list is full.
 */
timelineaction_t * timelineAddAction(timeline_t *timeline, float start,
  float duration
);

/**
 * Add a special action kind that can treat delta style animations. These are
 * animations that have a start, and an end value, that will be tracked for you
 * and keep you up to date.
 * 
 * @param timeline Timeline to add to.
 * @param start Starting time.
 * @param duration Animation duration.
 * @param initial Initial value for the animation.
 * @param delta Delta value for the animation.
 * @param easing Pointer to an easing function to use, set to NULL for linear.
 * @param destination A constant floating point to update.
 * @return The queued timeline action.
 */
timelineaction_t * timelineAddDeltaAction(timeline_t *timeline,
  float start, float duration, float initial, float delta,
  easefunction_t *easing, float *destination
);

/**
 * Shorthand for timelineAddDeltaAction that will calculate the delta based on
 * and end position.
 * 
 * @param timeline Timeline to add to.
 * @param start Starting time.
 * @param duration Animation duration.
 * @param initial Initial value for the animation.
 * @param end End value for the animation.
 * @param easing Pointer to an easing function to use, set to NULL for linear.
 * @param destination A constant floating point to update.
 * @return The queued timeline action.
 */
timelineaction_t * timelineAddDeltaActionTo(timeline_t *timeline,
  float start, float duration, float initial, float end,
  easefunction_t *easing, float *destination
);

/**
 * Gets the timeline action's animation time. This is a representation of
 * 0 - 1 where 0 means the animation is at the start, and 1 meaning the 
 * animation is at full completion. This is not clamped and may exceed 1.
 * 
 * @param timeline Timeline the action belongs to.
 * @param action Action itself.
 * @return 0 - 1+, where 0 = start, 1 = duration, >1 is time since duration.
 */
float timelineActionGetTimeRaw(timeline_t *timeline, timelineaction_t *action);

/**
 * Gets the timeline action's animation time between 0 - 1, clamped.
 * 
 * @param tl Timeline to get delta from.
 * @param at Timeline action.
 * @return 0 - 1, where 0 = start, 1 = duration.
 */
float timelineActionGetTime(timeline_t *tl, timelineaction_t *at);

/**
 * Removes all actions from the timeline.
 * 
 * @param timeline Timeline to clear.
 */
void timelineClear(timeline_t *timeline);