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

#include "timeline.h"

void _timelineActionDeltaUpdateStart(
  timeline_t *timeline, timelineaction_t *action, uint8_t i
) {
  if(action->data == NULL) return;
  *((float *)action->data) = timeline->initials[i];
}

void _timelineActionDeltaUpdateDuration(
  timeline_t *timeline, timelineaction_t *action, uint8_t i
) {
  float n;
  if(action->data == NULL) return;
  n = timeline->easings[i](
    timelineActionGetTimeRaw(timeline, action)
  );
  *((float *)action->data) = timeline->initials[i] + (timeline->deltas[i] * n);
}

void _timelineActionDeltaUpdateEnd(
  timeline_t *timeline, timelineaction_t *action, uint8_t i
) {
  if(action->data == NULL) return;
  *((float *)action->data) = timeline->initials[i] + timeline->deltas[i];
}


void timelineInit(timeline_t *timeline) {
  timeline->current = 0;
  timeline->diff = 0;
  timeline->previous = 0;
  timeline->user = 0;
  timeline->actionCount = 0;
}

void timelineUpdate(timeline_t *timeline, float delta) {
  uint8_t i;
  timelineaction_t *action;
  float full;

  timeline->diff = delta;
  timeline->previous = timeline->current;
  timeline->current = timeline->current + delta;

  // Find all actions that would have started or ended in this timespan.
  for(i = 0; i < timeline->actionCount; i++) {
    action = timeline->actions +i;

    // Has the action started yet?
    if(action->start > timeline->current) continue;

    // Did we start this frame?
    if(action->start > timeline->previous && action->onStart != NULL) {
      action->onStart(timeline, action, i);
    }

    // Durations of 0 only fire starts, never ends or durations.
    if(action->duration == 0) continue;

    // Is the end still in the future? Durations in negatives go forever
    full = action->start+action->duration;
    if(action->duration < 0 || full > timeline->current) {
      if(action->onDuration != NULL) action->onDuration(timeline, action, i);
    } else if(full > timeline->previous) {// Did we end this frame?
      if(action->onEnd != NULL) action->onEnd(timeline, action, i);
      if(action->loop) action->start = timeline->current;
    }
  }
}

bool timelineIsFinished(timeline_t *timeline) {
  uint8_t i;
  timelineaction_t *action;

  for(i = 0; i < timeline->actionCount; i++) {
    action = timeline->actions +i;
    if(action->start > timeline->current) return false;
    if(action->duration < 0) return false;
    if(action->duration == 0) continue;
    if(action->start+action->duration > timeline->current) return false;
  }

  return true;
}

timelineaction_t * timelineAddAction(timeline_t *timeline, float start,
  float duration
) {
  timelineaction_t *action = timeline->actions + (timeline->actionCount++);
  action->loop = false;
  action->start = start, action->duration = duration;
  action->onStart = action->onEnd = action->onDuration = NULL;
  return action;
}

timelineaction_t * timelineAddDeltaAction(timeline_t *timeline,
  float start, float duration, float initial, float delta,
  easefunction_t *easing, float *destination
) {
  timelineaction_t *action;
  timeline->initials[timeline->actionCount] = initial;
  timeline->deltas[timeline->actionCount] = delta;
  timeline->easings[timeline->actionCount] = (
    easing == NULL ? &easeLinear : easing
  );
  action = timelineAddAction(timeline, start, duration);
  action->data = destination;
  action->onStart = &_timelineActionDeltaUpdateStart;
  action->onDuration = &_timelineActionDeltaUpdateDuration;
  action->onEnd = &_timelineActionDeltaUpdateEnd;
  return action;
}

timelineaction_t * timelineAddDeltaActionTo(timeline_t *timeline,
  float start, float duration, float initial, float end,
  easefunction_t *easing, float *destination
) {
  return timelineAddDeltaAction(
    timeline, start, duration, initial, end-initial, easing, destination
  );
}

float timelineActionGetTimeRaw(timeline_t *timeline, timelineaction_t *action) {
  return (timeline->current - action->start) / action->duration;
}

float timelineActionGetTime(timeline_t *tl, timelineaction_t *at) {
  return mathClamp(timelineActionGetTimeRaw(tl, at), 0, 1);
}

void timelineClear(timeline_t *timeline) {
  timeline->actionCount = 0;
}