/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "clar.h"

#include "applib/graphics/gtypes.h"
#include "applib/graphics/gtransform.h"
#include "applib/ui/layer.h"
#include "applib/ui/property_animation.h"
#include "applib/ui/property_animation_private.h"
#include "applib/ui/animation.h"
#include "applib/ui/animation_private.h"
#include "applib/legacy2/ui/animation_private_legacy2.h"
#include "util/math.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

///////////////////////////////////////////////////////////
// Stubs
#include "stubs_logging.h"
#include "stubs_passert.h"

#include "stubs_app_state.h"
#include "stubs_app_timer.h"
#include "stubs_freertos.h"
#include "stubs_heap.h"
#include "stubs_mutex.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_syscalls.h"
#include "stubs_unobstructed_area.h"

// Fakes
#include "fake_new_timer.h"
#include "fake_pebble_tasks.h"
#include "fake_pbl_malloc.h"
#include "fake_rtc.h"
#include "fake_events.h"

#define TEST_INCLUDE_BASIC
#define TEST_INCLUDE_COMPLEX

#define DEBUG_TEST
#ifdef DEBUG_TEST
#define DPRINTF(fmt, ...)                                       \
    do { printf("%s: " fmt , __func__, ## __VA_ARGS__); \
    } while (0)
#else
#define DPRINTF(fmt, ...)
#endif

// Use our own macro instead of the abs function to avoid issues with non-int values.
#define ABSOLUTE_VALUE(x) (((x) > 0) ? (x) : -(x))

#define MIN_FRAME_INTERVAL_MS  33

#define TEST_ANIMATION_NORMALIZED_HIGH 50000
#define TEST_ANIMATION_NORMALIZED_LOW  5000

static PebbleEvent s_last_event;

bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) {
  s_last_event = *e;
  return true;
}

bool process_manager_compiled_with_legacy2_sdk(void) {
  return false;
}

GDrawState graphics_context_get_drawing_state(GContext *ctx) {
  return (GDrawState) { };
}

bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
  return false;
}

void graphics_context_set_drawing_state(GContext *ctx, GDrawState draw_state) {
}

void window_schedule_render(struct Window *window) {
}

TimerID animation_service_test_get_timer_id(void);


// --------------------------------------------------------------------------------------
static void cl_assert_equal_point(const GPoint a, const GPoint b) {
  cl_assert_equal_i(a.x, b.x);
  cl_assert_equal_i(a.y, b.y);
}


// --------------------------------------------------------------------------------------
static void cl_assert_equal_rect(const GRect a, const GRect b) {
  cl_assert_equal_point(a.origin, b.origin);
  cl_assert_equal_i(a.size.w, b.size.w);
  cl_assert_equal_i(a.size.h, b.size.h);
}


// --------------------------------------------------------------------------------------
static void cl_assert_equal_gtransform(const GTransform a, const GTransform b) {
  cl_assert_equal_i(a.a.raw_value, b.a.raw_value);
  cl_assert_equal_i(a.b.raw_value, b.b.raw_value);
  cl_assert_equal_i(a.c.raw_value, b.c.raw_value);
  cl_assert_equal_i(a.d.raw_value, b.d.raw_value);
  cl_assert_equal_i(a.tx.raw_value, b.tx.raw_value);
  cl_assert_equal_i(a.ty.raw_value, b.ty.raw_value);
}

static void cl_assert_close_gtransform(const GTransform a, const GTransform b) {
  cl_assert(abs(a.a.integer - b.a.integer) < 10);
  cl_assert(abs(a.b.integer - b.b.integer) < 10);
  cl_assert(abs(a.c.integer - b.c.integer) < 10);
  cl_assert(abs(a.d.integer - b.d.integer) < 10);
  cl_assert(abs(a.tx.integer - b.tx.integer) < 10);
  cl_assert(abs(a.ty.integer - b.ty.integer) < 10);
}


// --------------------------------------------------------------------------------------
static void cl_assert_equal_fixed_s32_16(const Fixed_S32_16 a, const Fixed_S32_16 b) {
  cl_assert_equal_i(a.raw_value, b.raw_value);
}

static void cl_assert_close_fixed_s32_16(const Fixed_S32_16 a, const Fixed_S32_16 b) {
  cl_assert(abs(a.integer - b.integer) < 10);
}


// --------------------------------------------------------------------------------------
// Get current time in ms.
static uint64_t prv_now_ms(void) {
  RtcTicks ticks = rtc_get_ticks();
  return (ticks * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ;
}

// --------------------------------------------------------------------------------------
// Advance current time by N ms. This does NOT check to see if the timer should fire
// If you want to advance time and fire all timers that would have fired during that time
// call prv_advance_to_ms_with_timers()
static void prv_advance_by_ms_no_timers(uint64_t ms_delta) {
  uint64_t target_ms = prv_now_ms() + ms_delta;

  // Comppensate for rounding errors
  uint64_t new_ticks = rtc_get_ticks() + (ms_delta * RTC_TICKS_HZ + 500 ) / 1000;
  uint64_t new_ms = (new_ticks * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ;
  if (new_ms == target_ms - 1) {
    new_ticks++;
  } else if (new_ms == target_ms + 1) {
    new_ticks--;
  }
  new_ms = (new_ticks * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ;
  cl_assert(new_ms == target_ms);
  fake_rtc_set_ticks(new_ticks);
}


// --------------------------------------------------------------------------------------
// Fire the timer used by the animation service. Before doing so, advance the time to when
// the timer would fire next.
static void prv_fire_animation_timer(void) {
  TimerID sys_timer_id = animation_service_test_get_timer_id();
  if (!sys_timer_id) {
    DPRINTF("timer not scheduled\n");
    return;
  }
  if (!stub_new_timer_is_scheduled(sys_timer_id)) {
    DPRINTF("timer not scheduled\n");
    return;
  }

  // Advance time
  uint32_t ms_delta = stub_new_timer_timeout(sys_timer_id);
  prv_advance_by_ms_no_timers(ms_delta);

  // This posts a callback event to the KernelMain event queue
  stub_new_timer_fire(sys_timer_id);

  // Get the callback event and process it
  PebbleEvent evt = fake_event_get_last();
  cl_assert_equal_i(evt.type, PEBBLE_CALLBACK_EVENT);
  evt.callback.callback(evt.callback.data);
}


// --------------------------------------------------------------------------------------
// Advance to the given time, firing all timers that are scheduled along the way
static void prv_advance_to_ms_with_timers(uint64_t dst_time) {
  uint64_t  now = prv_now_ms();

  while (now < dst_time) {
    TimerID sys_timer_id = animation_service_test_get_timer_id();
    if (!sys_timer_id) {
      DPRINTF("timer not created\n");
      prv_advance_by_ms_no_timers(dst_time - now);
      return;
    }

    if (!stub_new_timer_is_scheduled(sys_timer_id)) {
      DPRINTF("timer not scheduled\n");
      prv_advance_by_ms_no_timers(dst_time - now);
      return;
    }

    // Advance time to when timer would fire and fire it
    uint32_t ms_delta = stub_new_timer_timeout(sys_timer_id);
    if (ms_delta < dst_time - now) {
      prv_fire_animation_timer();
    } else {
      prv_advance_by_ms_no_timers(dst_time - now);
      return;
    }

    now = prv_now_ms();
  }
}


// =============================================================================================
// Started. stopped, setup, and teardown handler call histories. Every time a handler runs, we
// append the time animation handle and timestamp to the history list.
typedef struct {
  uint64_t  fired_time_ms;
  uint32_t fire_order;
  bool finished;        // only applicable for stopped handlers
  void *context;        // For update handler, this is the distance arg
  Animation *animation; // which animation
} AnimTestHandlerEntry;
#define MAX_HANDLER_CALLS   500
typedef struct {
  uint32_t  num_calls;
  AnimTestHandlerEntry entries[MAX_HANDLER_CALLS];
} AnimTestHandlerHistory;

static AnimTestHandlerHistory s_started_handler_calls;
static AnimTestHandlerHistory s_stopped_handler_calls;
static AnimTestHandlerHistory s_setup_handler_calls;
static AnimTestHandlerHistory s_teardown_handler_calls;
static AnimTestHandlerHistory s_update_handler_calls;
static uint32_t s_fire_order_index;

// -------------------------------------------------------------------------
// Clear all handler history
static void prv_clear_handler_histories(void) {
  memset(&s_started_handler_calls, 0, sizeof(AnimTestHandlerHistory));
  memset(&s_stopped_handler_calls, 0, sizeof(AnimTestHandlerHistory));
  memset(&s_setup_handler_calls, 0, sizeof(AnimTestHandlerHistory));
  memset(&s_teardown_handler_calls, 0, sizeof(AnimTestHandlerHistory));
  memset(&s_update_handler_calls, 0, sizeof(AnimTestHandlerHistory));
}

// -------------------------------------------------------------------------
// Add an entry to the history
static void prv_add_handler_entry(AnimTestHandlerHistory *history, Animation *animation,
                                  bool finished, void *context) {
  cl_assert(history->num_calls < MAX_HANDLER_CALLS);
  history->entries[history->num_calls++] = (AnimTestHandlerEntry) {
    .fired_time_ms = prv_now_ms(),
    .fire_order = s_fire_order_index++,
    .finished = finished,
    .context = context,
    .animation = animation
  };
}

// -------------------------------------------------------------------------
// Count how many entries were entered for the given animation
static uint32_t prv_count_handler_entries(AnimTestHandlerHistory *history, Animation *animation) {
  uint32_t  count = 0;

  for (int i = 0; i < history->num_calls; i++) {
    if (!animation || history->entries[i].animation == animation) {
      count++;
    }
  }
  return count;
}

// -------------------------------------------------------------------------
// Get the last entry for the given handle
static AnimTestHandlerEntry *prv_last_handler_entry(AnimTestHandlerHistory *history,
          Animation *animation) {
  int last_entry=-1;
  for (int i = 0; i < history->num_calls; i++) {
    if (!animation || history->entries[i].animation == animation) {
      last_entry = i;
    }
  }
  if (last_entry == -1) {
    return NULL;
  } else {
    return &history->entries[last_entry];
  }
}

// -------------------------------------------------------------------------
// Get the last distance from an update handler
static uint32_t prv_last_update_distance(Animation *animation) {
  AnimTestHandlerEntry *entry = prv_last_handler_entry(&s_update_handler_calls, animation);
  if (entry) {
    return (uint32_t)entry->context;
  } else {
    return 0;
  }
}


// =============================================================================================
// Handlers

// --------------------------------------------------------------------------------------
// Started handler
static void prv_started_handler(Animation *animation, void *context) {
  prv_add_handler_entry(&s_started_handler_calls, animation, false, context);
  DPRINTF("%"PRIu64" ms: Executing started handler for %d\n", prv_now_ms(), (int)animation);
}

// --------------------------------------------------------------------------------------
// Stopped handler
static void prv_stopped_handler(Animation *animation, bool finished, void *context) {
  prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
  DPRINTF("%"PRIu64" ms: Executing stopped handler for %d\n", prv_now_ms(), (int)animation);
}

// --------------------------------------------------------------------------------------
// Stopped handler with check for finish
static void prv_stopped_handler_check_finished(Animation *animation, bool finished, void *context) {
  prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
  DPRINTF("%"PRIu64" ms: Executing stopped handler for %d\n", prv_now_ms(), (int)animation);
  cl_assert(finished);
  AnimationPrivate *animation_private = animation_private_animation_find(animation);
  if (animation_private) {
    // Flag should now get reset to false before entering stopped handler
    cl_assert(animation_private->is_completed == false);
  }
}

// --------------------------------------------------------------------------------------
// Stopped handler that calls reschedule the first time it is called
static void prv_stopped_handler_reschedule(Animation *animation, bool finished, void *context) {
  prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);

  if (s_stopped_handler_calls.num_calls == 1) {
    DPRINTF("%"PRIu64" ms: Rescheduling from stopped handler for %d\n", prv_now_ms(),
            (int)animation);
    animation_schedule(animation);
  } else {
    DPRINTF("%"PRIu64" ms: NOT rescheduling from stopped handler for %d\n", prv_now_ms(),
            (int)animation);
  }

}

// --------------------------------------------------------------------------------------
// setup handler
void prv_setup_handler(Animation *animation) {
  prv_add_handler_entry(&s_setup_handler_calls, animation, false, NULL);
  DPRINTF("%"PRIu64" ms: Executing setup handler for %d\n", prv_now_ms(), (int)animation);
}

// --------------------------------------------------------------------------------------
// teardown handler
void prv_teardown_handler(Animation *animation) {
  prv_add_handler_entry(&s_teardown_handler_calls, animation, false, NULL);
  DPRINTF("%"PRIu64" ms: Executing teardown handler for %d\n", prv_now_ms(), (int)animation);
}

// --------------------------------------------------------------------------------------
// update handler

// implemented in Animation.c
AnimationPrivate *prv_animation_get_current(void);

void prv_update_handler(Animation *animation, const AnimationProgress distance) {
  // always ensure that animation state gives access to the current animation
  cl_assert_equal_p(animation_private_animation_find(animation), prv_animation_get_current());

  prv_add_handler_entry(&s_update_handler_calls, animation, false,
                        (void *)(uintptr_t)distance /*context*/);
  DPRINTF("%"PRIu64" ms: Executing update handler for %d, distance: %d\n", prv_now_ms(),
          (int)animation, (int)distance);
}


// --------------------------------------------------------------------------------------
static int s_custom_curve_call_count;
static AnimationProgress prv_custom_curve(AnimationProgress distance) {
  // Input is a value from 0 to 65535 (ANIMATION_NORMALIZED_MAX)
  // Output is a value from 0 to 65535
  s_custom_curve_call_count++;
  return distance;
}


// --------------------------------------------------------------------------------------
// Count how many animations have been allocated
static uint32_t prv_count_animations(void) {
  AnimationState *state = kernel_applib_get_animation_state();
  return list_count(state->unscheduled_head) + list_count(state->scheduled_head);
}

// --------------------------------------------------------------------------------------
// Count how many animations have been scheduled
static uint32_t prv_count_scheduled_animations(void) {
  AnimationState *state = kernel_applib_get_animation_state();
  return list_count(state->scheduled_head);
}

// --------------------------------------------------------------------------------------
static void prv_int16_setter(int16_t *p, int16_t value) {
  *p = value;
}

static int16_t prv_int16_getter(int16_t *p) {
  return *p;
}

// --------------------------------------------------------------------------------------
static void prv_gpoint_setter(GPoint *p, GPoint value) {
  *p = value;
}

static GPoint prv_gpoint_getter(GPoint *p) {
  return *p;
}


// --------------------------------------------------------------------------------------
static void prv_gtransform_setter(GTransform *p, GTransform value) {
  *p = value;
}

static GTransform prv_gtransform_getter(GTransform *p) {
  return *p;
}


// --------------------------------------------------------------------------------------
static void prv_gcolor8_setter(GColor8 *p, GColor8 value) {
  *p = value;
}

static GColor8 prv_gcolor8_getter(GColor8 *p) {
  return *p;
}


// --------------------------------------------------------------------------------------
static void prv_fixed_s32_16_setter(Fixed_S32_16 *p, Fixed_S32_16 value) {
  *p = value;
}

static Fixed_S32_16 prv_fixed_s32_16_getter(Fixed_S32_16 *p) {
  return *p;
}

// --------------------------------------------------------------------------------------
static void prv_uint32_setter(int32_t *p, uint32_t value) {
  *p = value;
}

static uint32_t prv_uint32_getter(uint32_t *p) {
  return *p;
}



// --------------------------------------------------------------------------------------
// Helper function for creating a int16 property animation
static Animation *prv_create_test_animation(void) {
  Animation *h;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  static const AnimationImplementation implementation = {
    .setup = prv_setup_handler,
    .update = prv_update_handler,
    .teardown = prv_teardown_handler
  };

  h = animation_create();
  cl_assert(h != NULL);
  void *context = h;
  animation_set_handlers(h, handlers, context);
  animation_set_implementation(h, &implementation);

  return h;
} 


// --------------------------------------------------------------------------------------
// Setup. Called before each of the tests execute
void test_animation__initialize(void) {
  fake_rtc_init(1024*200 /*ticks */, 200 /*seconds */);

  AnimationState *state = kernel_applib_get_animation_state();
  animation_private_state_init(state);

  // Insure that at least some time elapsed after init so that state->last_frame_time is
  // in the past.
  prv_advance_by_ms_no_timers(10);

  // Clear handler histories
  prv_clear_handler_histories();
}


// -------------------------------------------------------------------------------------
// Cleanup, called after each test executes
void test_animation__cleanup(void) {
  // Make sure no animations were left over
  cl_assert_equal_i(prv_count_animations(), 0);
}



// --------------------------------------------------------------------------------------
// Test a basic layer_frame property animation
// Test that the started and stopped handlers get called at the right time
void test_animation__property_layer_frame(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Layer layer;
  GRect from_r;
  GRect to_r;
  const int duration = 100;
  GRect r;
  void *subject;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  memset(&layer, 0, sizeof(layer));
  from_r = GRect(0, 0, 100, 200);     // x, y, width, height
  to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
  prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &layer;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);
  animation_set_auto_destroy(h, false);


  // Clone it and make sure the clone is correct
  PropertyAnimation *clone_h = (PropertyAnimation *)animation_clone((Animation *)prop_h);
  property_animation_get_from_grect(clone_h, &r);
  cl_assert_equal_rect(from_r, r);
  property_animation_get_to_grect(clone_h, &r);
  cl_assert_equal_rect(to_r, r);
  property_animation_get_subject(prop_h, &subject);
  cl_assert(subject == &layer);
  property_animation_destroy(clone_h);


  prv_clear_handler_histories();

  animation_schedule(h);
  int max_loops = 20;
  uint64_t  start_ms = prv_now_ms();
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls == 0) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // Make sure the frame reached the "to" state
  cl_assert_equal_point(layer.frame.origin, to_r.origin);

  // Make sure our started and stopped handlers got called
  cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
  cl_assert(s_started_handler_calls.entries[0].fired_time_ms - start_ms <= 1);
  cl_assert(s_started_handler_calls.entries[0].context == context);

  cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
  cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
  cl_assert(s_stopped_handler_calls.entries[0].context == context);
  cl_assert(s_stopped_handler_calls.entries[0].finished);


  // -------------------------------------------------------------------------------------------
  // Test the accessor functions
  property_animation_get_from_grect(prop_h, &r);
  cl_assert_equal_rect(from_r, r);

  property_animation_get_to_grect(prop_h, &r);
  cl_assert_equal_rect(to_r, r);

  property_animation_get_subject(prop_h, &subject);
  cl_assert(subject == &layer);


  GRect set_r = GRect(1, 2, 3, 4);
  property_animation_set_from_grect(prop_h, &set_r);
  r = GRect(0, 0, 0, 0);
  property_animation_get_from_grect(prop_h, &r);
  cl_assert_equal_rect(set_r, r);

  set_r = GRect(5, 6, 7, 8);
  property_animation_set_to_grect(prop_h, &set_r);
  r = GRect(0, 0, 0, 0);
  property_animation_get_to_grect(prop_h, &r);
  cl_assert_equal_rect(set_r, r);

  subject = (void *)0x11223344;
  property_animation_set_subject(prop_h, &subject);
  subject = NULL;
  property_animation_get_subject(prop_h, &subject);
  cl_assert(subject == (void *)0x11223344);


  // Destroy it
  animation_destroy(h);
#endif
}


// --------------------------------------------------------------------------------------
// Test a basic int16 property animation
// Test that the started and stopped handlers get called at the right time
// Test that the setup and teardown handlers get called at the right time
// Test that delay works
// Test that duration works
// Test that custom curve works
void test_animation__property_int16(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  int16_t value = 0;
  int16_t start_value = 0, end_value = 100;
  const int duration = 200;
  const int delay = 25;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  static const PropertyAnimationImplementation implementation = {
    .base = {
      .setup = prv_setup_handler,
      .update = (AnimationUpdateImplementation) property_animation_update_int16,
      .teardown = prv_teardown_handler
    },
    .accessors = {
      .setter = { .int16 = (const Int16Setter) prv_int16_setter, },
      .getter = { .int16 = (const Int16Getter) prv_int16_getter },
    },
  };

  prop_h = property_animation_create(&implementation, &value, &start_value, &end_value);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);
  animation_set_delay(h, delay);
  animation_set_custom_curve(h, prv_custom_curve);
  animation_set_auto_destroy(h, false);

  prv_clear_handler_histories();
  s_custom_curve_call_count = 0;

  animation_schedule(h);

  int max_loops = 20;
  int num_loops = 0;
  uint64_t  start_ms = prv_now_ms();
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls == 0) {
    num_loops++;
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": value at: %d\n", time_ms - start_ms, value);

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // Make sure the frame reached the "to" state
  cl_assert_equal_i(value, 100);

  // Make sure our started and stopped handlers got called
  cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
  cl_assert(ABSOLUTE_VALUE(s_started_handler_calls.entries[0].fired_time_ms - start_ms - delay) <= 1);

  cl_assert_equal_i(s_setup_handler_calls.num_calls, 1);;
  cl_assert(s_setup_handler_calls.entries[0].fired_time_ms - start_ms <= 1);

  cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
  cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
  cl_assert(s_stopped_handler_calls.entries[0].finished);

  cl_assert_equal_i(s_teardown_handler_calls.num_calls, 1);;
  cl_assert(s_teardown_handler_calls.entries[0].fired_time_ms - start_ms >= duration);

  // Make sure the custom curve function got called
  cl_assert_equal_i(num_loops, s_custom_curve_call_count);


  // -------------------------------------------------------------------------------------------
  // Test the int16 accessor functions
  int16_t test_value;
  property_animation_get_from_int16(prop_h, &test_value);
  cl_assert_equal_i(test_value, start_value);

  property_animation_get_to_int16(prop_h, &test_value);
  cl_assert_equal_i(test_value, end_value);


  int16_t set_value;
  set_value = 42;
  property_animation_set_from_int16(prop_h, &set_value);
  property_animation_get_from_int16(prop_h, &test_value);
  cl_assert_equal_i(test_value, set_value);

  set_value = 43;
  property_animation_set_to_int16(prop_h, &set_value);
  property_animation_get_to_int16(prop_h, &test_value);
  cl_assert_equal_i(test_value, set_value);

  // Destroy it
  animation_destroy(h);
#endif
}


// --------------------------------------------------------------------------------------
// Test a basic gpoint property animation
void test_animation__property_gpoint(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  GPoint value;
  GPoint start_value, end_value;
  const int duration = 200;
  const int delay = 25;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  static const PropertyAnimationImplementation implementation = {
    .base = {
      .update = (AnimationUpdateImplementation) property_animation_update_gpoint,
    },
    .accessors = {
      .setter = { .gpoint = (const GPointSetter) prv_gpoint_setter, },
      .getter = { .gpoint = (const GPointGetter) prv_gpoint_getter },
    },
  };

  start_value = GPoint(0, 0);
  end_value = GPoint(100, 200);
  prop_h = property_animation_create(&implementation, &value, &start_value, &end_value);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);
  animation_set_delay(h, delay);
  animation_set_auto_destroy(h, false);

  prv_clear_handler_histories();

  animation_schedule(h);

  int max_loops = 20;
  int num_loops = 0;
  uint64_t  start_ms = prv_now_ms();
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls == 0) {
    num_loops++;
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": value at: (%d, %d)\n", time_ms - start_ms, value.x, value.y);

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // Make sure the frame reached the "to" state
  cl_assert_equal_point(value, end_value);


  // -------------------------------------------------------------------------------------------
  // Test the GPoint accessor functions
  GPoint test_value;
  property_animation_get_from_gpoint(prop_h, &test_value);
  cl_assert_equal_point(test_value, start_value);

  property_animation_get_to_gpoint(prop_h, &test_value);
  cl_assert_equal_point(test_value, end_value);

  GPoint set_value;
  set_value = GPoint(42, 43);
  property_animation_set_from_gpoint(prop_h, &set_value);
  property_animation_get_from_gpoint(prop_h, &test_value);
  cl_assert_equal_point(test_value, set_value);

  set_value = GPoint(44, 45);
  property_animation_set_to_gpoint(prop_h, &set_value);
  property_animation_get_to_gpoint(prop_h, &test_value);
  cl_assert_equal_point(test_value, set_value);

  // Destroy it
  animation_destroy(h);
#endif
}



// --------------------------------------------------------------------------------------
// Test a basic gtransform property animation
void test_animation__property_gtransform(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  GTransform value;
  GTransform start_value, end_value, mid_value;
  const int duration = 1000;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  // NOTE: We are not exposing the GTransform in the public SDK, so the setter and getter
  // must be typecast
  static const PropertyAnimationImplementation implementation = {
    .base = {
      .update = (AnimationUpdateImplementation) property_animation_update_gtransform,
    },
    .accessors = {
      .setter = { .int16 = (const Int16Setter) prv_gtransform_setter, },
      .getter = { .int16 = (const Int16Getter) prv_gtransform_getter },
    },
  };

  start_value = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
  end_value = GTransformFromNumbers(100, 200, 300, 400, 500, 600);
  mid_value = GTransformFromNumbers(50, 101, 151, 202, 252, 303);
  value = end_value;
  prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(h);


  // Test the accessor functions
  GTransform test_value;
  property_animation_get_from_gtransform(prop_h, &test_value);
  cl_assert_equal_gtransform(test_value, start_value);

  property_animation_get_to_gtransform(prop_h, &test_value);
  cl_assert_equal_gtransform(test_value, end_value);

  GTransform set_value;
  set_value = GTransformIdentity();
  property_animation_set_from_gtransform(prop_h, &set_value);
  property_animation_get_from_gtransform(prop_h, &test_value);
  cl_assert_equal_gtransform(test_value, GTransformIdentity());
  property_animation_set_from_gtransform(prop_h, &start_value);


  // Start, we should start at the start values
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_gtransform(value, start_value);

  // Halfway through
  prv_advance_to_ms_with_timers(start_ms + duration/2);
  cl_assert_close_gtransform(value, mid_value);

  // End
  prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
  cl_assert_equal_gtransform(value, end_value);

#endif
}


// --------------------------------------------------------------------------------------
// Test a basic Fixed_S32_16 property animation
void test_animation__property_fixed_s32_16(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Fixed_S32_16 value;
  Fixed_S32_16 start_value, end_value, mid_value;
  const int duration = 1000;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  // NOTE: We are not exposing the GTransform in the public SDK, so the setter and getter
  // must be typecast
  static const PropertyAnimationImplementation implementation = {
    .base = {
      .update = (AnimationUpdateImplementation) property_animation_update_fixed_s32_16,
    },
    .accessors = {
      .setter = { .int16 = (const Int16Setter) prv_fixed_s32_16_setter },
      .getter = { .int16 = (const Int16Getter) prv_fixed_s32_16_getter },
    },
  };

  start_value = ((Fixed_S32_16){ .integer = 1, .fraction = 0 });
  end_value = ((Fixed_S32_16){ .integer = 100, .fraction = 0 });
  mid_value = ((Fixed_S32_16){ .integer = 50, .fraction = 0 });
  value = end_value;
  prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(h);


  // Test the accessor functions
  Fixed_S32_16 test_value;
  property_animation_get_from_fixed_s32_16(prop_h, &test_value);
  cl_assert_equal_fixed_s32_16(test_value, start_value);

  property_animation_get_to_fixed_s32_16(prop_h, &test_value);
  cl_assert_equal_fixed_s32_16(test_value, end_value);

  Fixed_S32_16 set_value;
  set_value = FIXED_S32_16_ONE;
  property_animation_set_from_fixed_s32_16(prop_h, &set_value);
  property_animation_get_from_fixed_s32_16(prop_h, &test_value);
  cl_assert_equal_fixed_s32_16(test_value, FIXED_S32_16_ONE);
  property_animation_set_from_fixed_s32_16(prop_h, &start_value);


  // Start, we should start at the start values
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_fixed_s32_16(value, start_value);

  // Halfway through
  prv_advance_to_ms_with_timers(start_ms + duration/2);
  cl_assert_close_fixed_s32_16(value, mid_value);

  // End
  prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
  cl_assert_equal_fixed_s32_16(value, end_value);

#endif
}


// --------------------------------------------------------------------------------------
// Test a basic uint32_t property animation
void test_animation__property_uint32(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  uint32_t value;
  uint32_t start_value, end_value, mid_value;
  const int duration = 1000;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  // NOTE: We are not exposing the GTransform in the public SDK, so the setter and getter
  // must be typecast
  static const PropertyAnimationImplementation implementation = {
    .base = {
      .update = (AnimationUpdateImplementation) property_animation_update_uint32,
    },
    .accessors = {
      .setter = { .uint32 = (const UInt32Setter) prv_uint32_setter },
      .getter = { .uint32 = (const UInt32Getter) prv_uint32_getter },
    },
  };

  start_value = 1;
  end_value = 100;
  mid_value = 50;
  value = end_value;
  prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(h);


  // Test the accessor functions
  uint32_t test_value;
  property_animation_get_from_uint32(prop_h, &test_value);
  cl_assert_equal_i(test_value, start_value);

  property_animation_get_to_uint32(prop_h, &test_value);
  cl_assert_equal_i(test_value, end_value);

  uint32_t set_value = 1;
  property_animation_set_from_uint32(prop_h, &set_value);
  property_animation_get_from_uint32(prop_h, &test_value);
  cl_assert_equal_i(test_value, 1);
  property_animation_set_from_uint32(prop_h, &start_value);


  // Start, we should start at the start values
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(value, start_value);

  // Halfway through
  prv_advance_to_ms_with_timers(start_ms + duration/2);
  cl_assert(abs((int32_t)value - (int32_t)mid_value) < 10);

  // End
  prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
  cl_assert_equal_i(value, end_value);

#endif
}


// --------------------------------------------------------------------------------------
// Test a basic gcolor8 property animation
void test_animation__property_gcolor8(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  GColor8 value;
  GColor8 start_value, end_value, mid_value;
  const int duration = 1000;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  static const PropertyAnimationImplementation implementation = {
    .base = {
      .update = (AnimationUpdateImplementation) property_animation_update_gcolor8,
    },
    .accessors = {
      .setter = { .gcolor8 = (const GColor8Setter) prv_gcolor8_setter, },
      .getter = { .gcolor8 = (const GColor8Getter) prv_gcolor8_getter },
    },
  };

  start_value = (GColor8) {.a=0, .r=0, .g=0, .b=0};
  end_value = (GColor8) {.a=3, .r=3, .g=3, .b=3};
  mid_value = (GColor8) {.a=1, .r=1, .g=1, .b=1};
  value = end_value;
  prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(h);


  // Test the accessor functions
  GColor8 test_value;
  property_animation_get_from_gcolor8(prop_h, &test_value);
  cl_assert(gcolor_equal(test_value, start_value));

  property_animation_get_to_gcolor8(prop_h, &test_value);
  cl_assert(gcolor_equal(test_value, end_value));

  GColor8 set_value;
  set_value = (GColor8) {.a=0, .r=1, .g=2, .b=3};
  property_animation_set_from_gcolor8(prop_h, &set_value);
  property_animation_get_from_gcolor8(prop_h, &test_value);
  cl_assert(gcolor_equal(test_value, set_value));
  property_animation_set_from_gcolor8(prop_h, &start_value);


  // Start, we should start at the start values
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert(gcolor_equal(value, start_value));

  // Halfway through
  prv_advance_to_ms_with_timers(start_ms + duration/2);
  cl_assert(gcolor_equal(value, mid_value));

  // End
  prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
  cl_assert(gcolor_equal(value, end_value));
#endif
}



// --------------------------------------------------------------------------------------
// Test that the schedule/unschedule calls work correctly.
// We should be able to unschedule an amimation parthway through
void test_animation__unschedule(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Layer layer;
  GRect from_r;
  GRect to_r;
  GRect stopped_at_r;
  const int duration = 500;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  memset(&layer, 0, sizeof(layer));
  from_r = GRect(0, 0, 100, 200);     // x, y, width, height
  to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
  prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &layer;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);
  animation_set_auto_destroy(h, false);

  prv_clear_handler_histories();

  animation_schedule(h);
  uint64_t  start_ms = prv_now_ms();
  uint64_t  unschedule_time = 0;
  uint64_t  time_ms;
  for (int num_loops = 0; num_loops < 10; num_loops++) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    // Unschedule after 2 iterations
    if (num_loops == 2) {
        DPRINTF("%"PRIu64": Unscheduling now\n", prv_now_ms());
        animation_unschedule(h);
        stopped_at_r = layer.frame;
        unschedule_time = prv_now_ms();
    }
  }

  // Make sure the frame stopped at the state it was in when we unscheduled it
  cl_assert_equal_point(layer.frame.origin, stopped_at_r.origin);

  // Make sure our started and stopped handlers got called at
  cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
  cl_assert(s_started_handler_calls.entries[0].fired_time_ms - start_ms <= 1);
  cl_assert(s_started_handler_calls.entries[0].context == context);

  cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
  cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms < duration);
  cl_assert(ABSOLUTE_VALUE(s_stopped_handler_calls.entries[0].fired_time_ms - unschedule_time) < 1);
  cl_assert(!s_stopped_handler_calls.entries[0].finished);

  // Destroy it
  animation_destroy(h);
#endif
}


// --------------------------------------------------------------------------------------
// Test that we can reschedule an animation after it completes and have it run again
void test_animation__reschedule(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Layer layer;
  GRect from_r;
  GRect to_r;
  const int duration = 100;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  memset(&layer, 0, sizeof(layer));
  from_r = GRect(0, 0, 100, 200);     // x, y, width, height
  to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
  prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &layer;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);
  animation_set_auto_destroy(h, false);

  prv_clear_handler_histories();

  animation_schedule(h);
  int max_loops = 20;
  uint64_t  start_ms = prv_now_ms();
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls == 0) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // Make sure the frame reached the "to" state
  cl_assert_equal_point(layer.frame.origin, to_r.origin);

  // -------------------------------------------------------------------------------
  // Now, reschedule it
  prv_advance_by_ms_no_timers(10);
  prv_clear_handler_histories();

  animation_schedule(h);
  max_loops = 20;
  start_ms = prv_now_ms();
  while (s_stopped_handler_calls.num_calls == 0) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    cl_assert(max_loops > 0);
    max_loops--;
  }


  // Make sure our started and stopped handlers got called
  cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
  cl_assert(s_started_handler_calls.entries[0].fired_time_ms - start_ms <= 1);

  cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
  cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
  cl_assert(s_stopped_handler_calls.entries[0].finished);

  // Destroy it
  animation_destroy(h);
#endif
}


// --------------------------------------------------------------------------------------
// Test that we can reschedule an animation from the stopped handler
void test_animation__reschedule_from_stopped_handler(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Layer layer;
  GRect from_r;
  GRect to_r;
  const int duration = 100;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler_reschedule
  };

  memset(&layer, 0, sizeof(layer));
  from_r = GRect(0, 0, 100, 200);     // x, y, width, height
  to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
  prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &layer;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();

  animation_schedule(h);
  int max_loops = 20;
  uint64_t  start_ms = prv_now_ms();
  bool detected_reset_of_elapsed = false;
  bool reached_end_elapsed = false;
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls < 2) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("rescheduled count: %d\n", s_stopped_handler_calls.num_calls);
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    if (layer.frame.origin.x == to_r.origin.x && layer.frame.origin.y == to_r.origin.y) {
      reached_end_elapsed = true;
    }
    if (reached_end_elapsed && s_stopped_handler_calls.num_calls == 1
        && layer.frame.origin.x < to_r.origin.x) {
      detected_reset_of_elapsed = true;
    }

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // Make sure we detected a reset of the elapsed after rescheduling
  cl_assert(s_stopped_handler_calls.num_calls == 2);
  cl_assert(detected_reset_of_elapsed);

  // Make sure the frame reached the "to" state
  cl_assert_equal_point(layer.frame.origin, to_r.origin);

#endif
}


// --------------------------------------------------------------------------------------
// Test that auto-destroy works correctly
void test_animation__auto_destroy(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Layer layer;
  GRect from_r;
  GRect to_r;
  const int duration = 100;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  memset(&layer, 0, sizeof(layer));
  from_r = GRect(0, 0, 100, 200);     // x, y, width, height
  to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
  prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &layer;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();

  // Before we run, the context should be non NULL
  cl_assert(animation_get_context(h) == context);

  animation_schedule(h);
  int max_loops = 20;
  uint64_t  start_ms = prv_now_ms();
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls == 0) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // This should return a NULL context now if the animation got destroyed
  cl_assert(animation_get_context(h) == NULL);

  // Make sure no animations exist
  cl_assert_equal_i(prv_count_animations(), 0);
#endif
}


// --------------------------------------------------------------------------------------
// Test that we can reschedule an animation from the stopped handler that has auto-destroy on
void test_animation__auto_destroy_reschedule(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  Layer layer;
  GRect from_r;
  GRect to_r;
  const int duration = 100;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler_reschedule
  };

  memset(&layer, 0, sizeof(layer));
  from_r = GRect(0, 0, 100, 200);     // x, y, width, height
  to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
  prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
  Animation *h = property_animation_get_animation(prop_h);
  void *context = &layer;
  animation_set_handlers(h, handlers, context);
  animation_set_duration(h, duration);

  prv_clear_handler_histories();

  animation_schedule(h);
  int max_loops = 20;
  uint64_t  start_ms = prv_now_ms();
  bool detected_reset_of_elapsed = false;
  bool reached_end_elapsed = false;
  uint64_t  time_ms;
  while (s_stopped_handler_calls.num_calls < 2) {
    prv_fire_animation_timer();
    time_ms = prv_now_ms();
    DPRINTF("rescheduled count: %d\n", s_stopped_handler_calls.num_calls);
    DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
            layer.frame.origin.x, layer.frame.origin.y,
            layer.frame.size.w, layer.frame.size.h);

    if (layer.frame.origin.x == to_r.origin.x && layer.frame.origin.y == to_r.origin.y) {
      reached_end_elapsed = true;
    }
    if (reached_end_elapsed && s_stopped_handler_calls.num_calls == 1
        && layer.frame.origin.x < to_r.origin.x) {
      detected_reset_of_elapsed = true;
    }

    cl_assert(max_loops > 0);
    max_loops--;
  }

  // Make sure we detected a reset of the elapsed after rescheduling
  cl_assert(s_stopped_handler_calls.num_calls == 2);
  cl_assert(detected_reset_of_elapsed);

  // Make sure the frame reached the "to" state
  cl_assert_equal_point(layer.frame.origin, to_r.origin);

  // This should return a NULL context now if the animation got destroyed
  cl_assert(animation_get_context(h) == NULL);

  // Make sure no animations exist
  cl_assert_equal_i(prv_count_animations(), 0);
#endif
}


// --------------------------------------------------------------------------------------
// Stopped handler that calls destroy
static void prv_stopped_handler_destroy(Animation *animation, bool finished, void *context) {
  prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
  animation_destroy(animation);
}


// --------------------------------------------------------------------------------------
// Test that animation_destroy can be called from the stopped handler
static void prv_test_destroy_from_stopped_handler(bool auto_destroy) {
  Animation *h;
  const int duration = 100;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler_destroy
  };

  static const AnimationImplementation implementation = {
    .setup = prv_setup_handler,
    .update = prv_update_handler,
    .teardown = prv_teardown_handler
  };

  h = animation_create();
  cl_assert(h != NULL);
  void *context = h;
  animation_set_handlers(h, handlers, context);
  animation_set_implementation(h, &implementation);

  animation_set_duration(h, duration);
  animation_set_auto_destroy(h, auto_destroy);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(h);
  prv_advance_to_ms_with_timers(start_ms + duration + 2 * MIN_FRAME_INTERVAL_MS);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, h), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, h), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, h), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, h), 1);

  // Make sure the frame reached the "to" state
  cl_assert_equal_i(prv_last_update_distance(h), ANIMATION_NORMALIZED_MAX);

  // This should return NULL now if the animation got destroyed
  cl_assert(animation_private_animation_find(h) == NULL);

  // Make sure no animations exist
  cl_assert_equal_i(prv_count_animations(), 0);
}


// --------------------------------------------------------------------------------------
// Test that animation_destroy can be called from the stopped handler
void test_animation__destroy_from_stopped_handler_with_auto_destroy(void) {
#ifdef TEST_INCLUDE_BASIC
  prv_test_destroy_from_stopped_handler(true);
#endif
}


// --------------------------------------------------------------------------------------
// Test that animation_destroy can be called from the stopped handler
void test_animation__destroy_from_stopped_handler_without_auto_destroy(void) {
#ifdef TEST_INCLUDE_BASIC
  prv_test_destroy_from_stopped_handler(false);
#endif
}


// --------------------------------------------------------------------------------------
// Stopped handler that calls unschedule
static void prv_stopped_handler_unschedule(Animation *animation, bool finished, void *context) {
  prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
  animation_unschedule(animation);
}


// --------------------------------------------------------------------------------------
// Test that animation_unschedule can be called from the stopped handler
void test_animation__unschedule_from_stopped_handler(void) {
  Animation *h;
  const int duration = 100;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler_unschedule
  };

  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration);
  animation_set_handlers(a, handlers, a);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(a);
  prv_advance_to_ms_with_timers(start_ms + duration + 2 * MIN_FRAME_INTERVAL_MS);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  // Make sure no animations exist
  cl_assert_equal_i(prv_count_animations(), 0);
}



// --------------------------------------------------------------------------------------
// Test setting a play count of 0
void test_animation__basic_play_count_0(void) {
#ifdef TEST_INCLUDE_BASIC

  const int duration_a = 300;
  Animation *a = prv_create_test_animation();
  animation_set_play_count(a, 0);
  animation_set_duration(a, duration_a);

  prv_clear_handler_histories();
  animation_schedule(a);

  uint64_t  start_ms = prv_now_ms();
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);

  // Should not have run at all
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);

  // Should have been deleted automatically
  cl_assert_equal_i(prv_count_animations(), 0);

#endif
}


// --------------------------------------------------------------------------------------
// Test setting a duration of infinite duration
void test_animation__basic_infinite_duration(void) {
#ifdef TEST_INCLUDE_BASIC
  // A long time, but not so long as to use up our 500 capacity callback history limit
  const uint32_t test_duration = 10000;
  const uint32_t duration_a = ANIMATION_DURATION_INFINITE;
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  prv_clear_handler_histories();
  animation_schedule(a);

  uint64_t  start_ms = prv_now_ms();
  prv_advance_to_ms_with_timers(start_ms + test_duration);

  // Should still be running
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert(prv_count_handler_entries(&s_update_handler_calls, a)
            >= test_duration/MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);

  // The distance should always be at 0
  cl_assert_equal_i(prv_last_update_distance(a), 0);

  // Destroy it
  animation_destroy(a);

#endif
}


// --------------------------------------------------------------------------------------
// Test a simple sequence animation
void test_animation__simple_sequence(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int play_count_b = 2;
  int duration_total = duration_a + play_count_b * duration_b;

  // Create 2 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_play_count(b, play_count_b);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);

  // Setup started/stopped handlers for the sequence
  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };
  animation_set_handlers(seq, handlers, seq);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);


  // Just before A completes
  prv_advance_to_ms_with_timers(start_ms + duration_a - 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);


  // Complete A and start B
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);

  // The stopped handler for A should fire before the started handler for B
  cl_assert(prv_last_handler_entry(&s_stopped_handler_calls, a)->fire_order
            < prv_last_handler_entry(&s_started_handler_calls, b)->fire_order);


  // Just before B completes the 2nd play
  prv_advance_to_ms_with_timers(start_ms + duration_total - 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);


  // Complete B
  prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
#endif
}


static Animation *s_parent_for_sequence_unschedule_from_child;
static void prv_unschedule_parent(Animation *animation, bool finished, void *context) {
  DPRINTF("%"PRIu64" ms: Executing prv_unschedule_parent handler for %d\n", prv_now_ms(),
          (int)animation);
  animation_unschedule(s_parent_for_sequence_unschedule_from_child);
}

// --------------------------------------------------------------------------------------
// Test calling unschedule on the sequence from the stopped handler of one of its children
void test_animation__sequence_unschedule_from_child(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int play_count_b = 2;
  int duration_total = duration_a + play_count_b * duration_b;

  // Create 2 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  // Setup stopped handler for the first child that unschedules the parent
  const AnimationHandlers special_handlers = {
    .started = NULL,
    .stopped = prv_unschedule_parent
  };
  animation_set_handlers(a, special_handlers, NULL);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_play_count(b, play_count_b);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };
  animation_set_handlers(seq, handlers, seq);
  s_parent_for_sequence_unschedule_from_child = seq;

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);


  // Complete A and start B. This should unschedule the parent
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);


  // Everything should have been freed
  cl_assert_equal_i(prv_count_animations(), 0);

#endif
}


// --------------------------------------------------------------------------------------
// Test a seeking in a basic sequence animation
void test_animation__simple_sequence_set_elapsed(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int duration_c = 400;
  const int play_count_b = 2;
  int duration_total = duration_a + play_count_b * duration_b;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };


  // Create 2 property animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_play_count(b, play_count_b);

  // Create a sequence out of them.
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);


  // Create a shorter animation to play in parallel
  Animation *c = prv_create_test_animation();
  cl_assert(c != NULL);
  animation_set_duration(c, duration_c);

  Animation *complex = animation_spawn_create(seq, c, NULL);
  cl_assert(complex != NULL);
  animation_set_handlers(complex, handlers, complex);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(complex);

  // -------------------------------------------------------------------------------------
  // Start A
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);


  // -------------------------------------------------------------------------------------
  // Execute about half of A
  prv_advance_to_ms_with_timers(start_ms + duration_a/2);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);


  // -------------------------------------------------------------------------------------
  // Seek to about the middle of B
  // Save the current update elapsed
  animation_set_elapsed(complex, duration_a + duration_b/2);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);

  // A should be at the end
  int32_t update_a_after = prv_last_update_distance(a);
  cl_assert_equal_i(update_a_after, ANIMATION_NORMALIZED_MAX);

  // B should be in the middle
  int32_t update_b_after = prv_last_update_distance(b);
  cl_assert(abs(update_b_after - ANIMATION_NORMALIZED_MAX/2) < 5000);

  // C should be at the end
  int32_t update_c_after = prv_last_update_distance(c);
  cl_assert_equal_i(update_c_after, ANIMATION_NORMALIZED_MAX);


  // -------------------------------------------------------------------------------------
  // Seek to just before the end of the second B
  // Save the current update elapsed
  animation_set_elapsed(complex, duration_total - 2 * MIN_FRAME_INTERVAL_MS);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);


  // -------------------------------------------------------------------------------------
  // animation a has completed, but it shouldn't be deleted yet until the top-level
  // animation is done.
  uint32_t duration = animation_get_duration(a, false, false);
  cl_assert_equal_i(duration, duration_a);


  // -------------------------------------------------------------------------------------
  // Advance to the end
  animation_set_elapsed(complex, duration_total);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, complex), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);

  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_last_update_distance(c), ANIMATION_NORMALIZED_MAX);

  // Make sure each animation got to the end
  AnimTestHandlerEntry *entry;
  entry = prv_last_handler_entry(&s_update_handler_calls, c);
  cl_assert_equal_i((uint32_t)entry->context, ANIMATION_NORMALIZED_MAX);
  
#endif
}


// --------------------------------------------------------------------------------------
// Test unscheduling a complex animation
void test_animation__sequence_unschedule(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 200;
  const int duration_c = 500;
  const int repeat_count = 5;
  int duration_total = duration_a + MAX(duration_b, duration_c);

  // Create 3 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);

  Animation *c = prv_create_test_animation();
  animation_set_duration(c, duration_c);

  // Create a spawn out of b and c
  Animation *spawn = animation_spawn_create(b, c, NULL);
  cl_assert(spawn != NULL);

  // Create a sequence by putting a in front
  // We now have a -> (b | c)
  Animation *seq = animation_sequence_create(a, spawn, NULL);

  // Make it repeat
  animation_set_play_count(seq, repeat_count);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, c), 0);


  // Execute to the start of B and C
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);


  // Execute to the end of B & C
  prv_advance_to_ms_with_timers(start_ms + duration_total + 1 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);


  // If we keep going, we should repeat the whole sequence
  prv_advance_to_ms_with_timers(start_ms + 2 * (duration_total + 4 * MIN_FRAME_INTERVAL_MS));
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 3);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 2);

  // Unschedule the top-level
  animation_unschedule(seq);


  // Keep going, nothing new should happen except the stop handler for a (which we started)
  prv_advance_to_ms_with_timers(start_ms + 5 * (duration_total + 3 * MIN_FRAME_INTERVAL_MS));
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 3);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 3);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, c), 1);
#endif
}


// --------------------------------------------------------------------------------------
// Test using clone and reverse in a complex animation
void test_animation__complex_reverse(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_total = 2 * duration_a;
  const int repeat_count = 2;
  uint32_t distance;

  // Create animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = animation_clone(a);
  animation_set_reverse(b, true);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  animation_set_play_count(seq, repeat_count);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // A should start out low
  distance = prv_last_update_distance(a);
  cl_assert(distance < TEST_ANIMATION_NORMALIZED_LOW);


  // Execute to the start of B
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  // A should end high
  distance = prv_last_update_distance(a);
  cl_assert(distance > TEST_ANIMATION_NORMALIZED_HIGH);

  // B should start high
  distance = prv_last_update_distance(b);
  cl_assert(distance > TEST_ANIMATION_NORMALIZED_HIGH);


  // Execute to the end of B
  prv_advance_to_ms_with_timers(start_ms + duration_total + 1 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);

  // B should end low
  distance = prv_last_update_distance(b);
  cl_assert(distance < TEST_ANIMATION_NORMALIZED_LOW);


  // If we keep going, we should repeat the whole sequence
  prv_advance_to_ms_with_timers(start_ms + 2 * (duration_total + 10 * MIN_FRAME_INTERVAL_MS));
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);

  // A should end up at max
  // A should end high
  distance = prv_last_update_distance(a);
  cl_assert_equal_i(distance, ANIMATION_NORMALIZED_MAX);

  // B should start high
  distance = prv_last_update_distance(b);
  cl_assert_equal_i(distance, ANIMATION_NORMALIZED_MIN);

#endif
}


// --------------------------------------------------------------------------------------
// Test cloning complex animation
void test_animation__complex_clone(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 200;
  const int duration_c = 500;
  const int repeat_count = 5;
  int duration_total = duration_a + MAX(duration_b, duration_c);

  // Create 3 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);

  Animation *c = prv_create_test_animation();
  animation_set_duration(c, duration_c);

  // Create a spawn out of b and c
  Animation *spawn = animation_spawn_create(b, c, NULL);
  cl_assert(spawn != NULL);

  // Create a sequence by putting a in front and repeat it 5 times
  // We now have a -> (b | c)
  Animation *seq = animation_sequence_create(a, spawn, NULL);
  animation_set_play_count(seq, repeat_count);


  // Now, clone it
  Animation *clone = animation_clone(seq);

  // Destroy the original
  animation_destroy(seq);

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(clone);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 0);

  // Execute to the start of B and C
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 3);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 1);

  // Execute to the end of B & C
  prv_advance_to_ms_with_timers(start_ms + duration_total + 1 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 3);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 3);

  // If we keep going, we should repeat the whole sequence another time
  prv_advance_to_ms_with_timers(start_ms + 2 * (duration_total + 4 * MIN_FRAME_INTERVAL_MS));
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 7);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 6);

  // Unschedule the top-level
  animation_unschedule(clone);


  // Keep going, nothing new should happen except stop handlers for each component
  prv_advance_to_ms_with_timers(start_ms + 5 * (duration_total + 3 * MIN_FRAME_INTERVAL_MS));
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 7);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 7);
#endif
}


// --------------------------------------------------------------------------------------
// Test scheduling a sequence of 2 spawns. Insure that ALL of the primitives in the first spawn,
// finish before the primitives from the 2nd spawn start.
static void prv_test_sequence_of_spawns(int create_order[4]) {
  const int duration_a = 150;
  const int duration_total = 2 * duration_a;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  // Simulate some delay incurred on every call to rtc_get_ticks()
  fake_rtc_auto_increment_ticks(3);

  Animation *a0, *a1, *a2, *a3;
  Animation *b0, *b1, *b2, *b3;
  Animation *spawn_a, *spawn_b;

  for (int i=0; i<4; i++) {
    switch(create_order[i]) {
      case 0:
        a0 = prv_create_test_animation();
        animation_set_duration(a0, duration_a);
        a1 = animation_clone(a0);
        a2 = animation_clone(a0);
        a3 = animation_clone(a0);
        break;

      case 1:
        b0 = prv_create_test_animation();
        animation_set_duration(b0, duration_a);
        b1 = animation_clone(b0);
        b2 = animation_clone(b0);
        b3 = animation_clone(b0);
        break;

      case 2:
        spawn_a = animation_spawn_create(a0, a1, a2, a3, NULL);
        animation_set_handlers(spawn_a, handlers, (void *)spawn_a);
        break;

      case 3:
        spawn_b = animation_spawn_create(b0, b1, b2, b3, NULL);
        animation_set_handlers(spawn_b, handlers, (void *)spawn_a);
        break;
    }
  }

  // Create the sequence
  Animation *seq = animation_sequence_create(spawn_a, spawn_b, NULL);

  // Schedule it
  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  fake_rtc_auto_increment_ticks(0);

  // Let the first spawn finish
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a0), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a0), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a1), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a1), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a2), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a2), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a3), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a3), 1);

  // None of the b's should finish yet
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b0), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b1), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b2), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b3), 0);


  // Let it finish completely
  prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS);

  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b0), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b1), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b2), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b3), 1);


  // Make sure the all the spawn a stopped handlers got called before any of the spawn b
  // started handlers
  uint32_t last_fire_a = 0;
  last_fire_a = MAX(last_fire_a,
                    prv_last_handler_entry(&s_stopped_handler_calls, a0)->fire_order);
  last_fire_a = MAX(last_fire_a,
                    prv_last_handler_entry(&s_stopped_handler_calls, a1)->fire_order);
  last_fire_a = MAX(last_fire_a,
                    prv_last_handler_entry(&s_stopped_handler_calls, a2)->fire_order);
  last_fire_a = MAX(last_fire_a,
                    prv_last_handler_entry(&s_stopped_handler_calls, a3)->fire_order);
  last_fire_a = MAX(last_fire_a,
                    prv_last_handler_entry(&s_stopped_handler_calls, spawn_a)->fire_order);

  uint32_t first_fire_b = prv_last_handler_entry(&s_started_handler_calls, b0)->fire_order;
  first_fire_b = MIN(first_fire_b,
                    prv_last_handler_entry(&s_started_handler_calls, b1)->fire_order);
  first_fire_b = MIN(first_fire_b,
                    prv_last_handler_entry(&s_started_handler_calls, b2)->fire_order);
  first_fire_b = MIN(first_fire_b,
                    prv_last_handler_entry(&s_started_handler_calls, b3)->fire_order);
  first_fire_b = MIN(first_fire_b,
                    prv_last_handler_entry(&s_started_handler_calls, spawn_b)->fire_order);

  cl_assert(last_fire_a < first_fire_b);

  cl_assert_equal_i(prv_count_animations(), 0);
}

// --------------------------------------------------------------------------------------
// Test scheduling a sequence of 2 spawns. Insure that ALL of the primitives in the first spawn,
// finish before the primitives from the 2nd spawn start.
void test_animation__sequence_of_spawns(void) {
#ifdef TEST_INCLUDE_COMPLEX
  int order_a[4] = {0, 1, 2, 3};
  prv_test_sequence_of_spawns(order_a);

  int order_b[4] = {1, 0, 2, 3};
  prv_test_sequence_of_spawns(order_b);

  int order_c[4] = {1, 0, 3, 2};
  prv_test_sequence_of_spawns(order_c);
#endif
}


// --------------------------------------------------------------------------------------
// Test delays in sequence animation
void test_animation__sequence_delay(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int delay_a = 100;
  const int duration_b = 500;
  const int delay_b = 200;
  const int delay_seq = 150;
  int duration_total = duration_a + duration_b + delay_a + delay_b + delay_seq;


  // Create 2 test animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);
  animation_set_delay(seq, delay_seq);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);


  // Test the elapsed
  int32_t elapsed_ms;
  animation_get_elapsed(seq, &elapsed_ms);
  cl_assert_equal_i(elapsed_ms, -1 * (delay_seq));

  animation_get_elapsed(a, &elapsed_ms);
  cl_assert_equal_i(elapsed_ms, -1 * (delay_seq + delay_a));

  animation_get_elapsed(b, &elapsed_ms);
  cl_assert_equal_i(elapsed_ms, -1 * (delay_seq + delay_a + duration_a + delay_b));

  // Start
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Start A after delay
  prv_advance_to_ms_with_timers(start_ms + delay_seq + delay_a + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Just before A completes
  prv_advance_to_ms_with_timers(start_ms + delay_seq + delay_a + duration_a - 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Complete A and start B
  prv_advance_to_ms_with_timers(start_ms + duration_a + delay_seq + delay_a + delay_b
                                 + 2 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);

  // Complete B
  prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);

#endif
}


// --------------------------------------------------------------------------------------
// Test delays in spawn animation
void test_animation__spawn_delay(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int delay_a = 100;
  const int duration_b = 500;
  const int delay_b = 200;
  const int delay_spawn = 150;
  int duration_total = MAX(duration_a + delay_a, duration_b + delay_b) + delay_spawn;


  // Create 2 test animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);

  // Create a spawn
  Animation *spawn = animation_spawn_create(a, b, NULL);
  cl_assert(spawn != NULL);
  animation_set_delay(spawn, delay_spawn);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(spawn);


  // Test the elapsed
  int32_t elapsed_ms;
  animation_get_elapsed(spawn, &elapsed_ms);
  cl_assert_equal_i(elapsed_ms, -1 * (delay_spawn));

  animation_get_elapsed(a, &elapsed_ms);
  cl_assert_equal_i(elapsed_ms, -1 * (delay_spawn + delay_a));

  animation_get_elapsed(b, &elapsed_ms);
  cl_assert_equal_i(elapsed_ms, -1 * (delay_spawn + delay_b));

  // Start
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + delay_spawn + delay_a + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  // Start B
  prv_advance_to_ms_with_timers(start_ms + delay_spawn + MAX(delay_a, delay_b)
                                + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  // Complete A and start B
  prv_advance_to_ms_with_timers(start_ms + delay_spawn + duration_a + delay_a
                                + 2 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);

  // Complete B
  prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);

#endif
}


// --------------------------------------------------------------------------------------
// Test a sequence animation with a component that has a play count of 0
void test_animation__sequence_with_0_component(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int play_count_seq = 2;
  int duration_total = play_count_seq * duration_a;

  // Create 2 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_play_count(b, 0);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);
  animation_set_play_count(seq, play_count_seq);

  // Check the duration
  cl_assert_equal_i(animation_get_duration(seq, true, true), play_count_seq * duration_a);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Just before A completes
  prv_advance_to_ms_with_timers(start_ms + duration_a - 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Complete A the first time
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  // Complete sequence
  prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);

  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);

#endif
}


// --------------------------------------------------------------------------------------
// Test a spawn animation with a component that has a play count of 0
void test_animation__spawn_with_0_component(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int play_count_spawn = 2;
  int duration_total = play_count_spawn * duration_a;

  // Create 2 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_play_count(b, 0);

  // Create a spawn that repeats
  Animation *spawn = animation_spawn_create(a, b, NULL);
  cl_assert(spawn != NULL);
  animation_set_play_count(spawn, play_count_spawn);

  // Check the duration
  cl_assert_equal_i(animation_get_duration(spawn, true, true), play_count_spawn * duration_a);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(spawn);

  // Start A
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Just before A completes
  prv_advance_to_ms_with_timers(start_ms + duration_a - 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Complete A the first time
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  // Complete spawn
  prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);

  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);

#endif
}


// --------------------------------------------------------------------------------------
// Test a sequence animation with a play count of 0
void test_animation__sequence_with_0_play_count(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int play_count_seq = 0;

  // Create 2 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);
  animation_set_play_count(seq, play_count_seq);

  // Check the duration
  cl_assert_equal_i(animation_get_duration(seq, true, true), 0);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  // Start
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Complete sequence
  prv_advance_to_ms_with_timers(start_ms + duration_a + duration_b + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);

  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MIN);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MIN);

#endif
}


// --------------------------------------------------------------------------------------
// Test a sequence within a sequence where the imbedded one has a play count of 0
void test_animation__nested_sequence_with_0_play_count(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int duration_c = 200;
  const int duration_d = 400;
  const int total_duration = duration_c + duration_d;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  // Create the inner sequence with a play count of 0
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);

  Animation *inner_seq = animation_sequence_create(a, b, NULL);
  animation_set_play_count(inner_seq, 0);
  animation_set_handlers(inner_seq, handlers, inner_seq);

  // Create the outer sequence
  Animation *c = prv_create_test_animation();
  animation_set_duration(c, duration_c);

  Animation *d = prv_create_test_animation();
  animation_set_duration(d, duration_d);

  Animation *seq = animation_sequence_create(inner_seq, c, d, NULL);
  animation_set_handlers(seq, handlers, seq);


  // Play it
  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();
  animation_schedule(seq);

  prv_advance_to_ms_with_timers(start_ms + total_duration + 5 * MIN_FRAME_INTERVAL_MS);

  // Make sure neither inner_seq, a, nor b played
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, inner_seq), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, inner_seq), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);


  // Make sure seq, c, and d completed
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, c), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, d), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, d), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, d), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, d), 1);
#endif
}


// --------------------------------------------------------------------------------------
// Test a spawn animation with a play count of 0
void test_animation__spawn_with_0_play_count(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int duration_b = 500;
  const int play_count_spawn = 0;

  // Create 2 animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);

  // Create a spawn
  Animation *spawn = animation_sequence_create(a, b, NULL);
  cl_assert(spawn != NULL);
  animation_set_play_count(spawn, play_count_spawn);

  // Check the duration
  cl_assert_equal_i(animation_get_duration(spawn, true, true), 0);

  prv_clear_handler_histories();

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(spawn);

  // Start
  prv_advance_to_ms_with_timers(start_ms + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);

  // Complete sequence
  prv_advance_to_ms_with_timers(start_ms + duration_a + duration_b + 5 * MIN_FRAME_INTERVAL_MS + 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MIN);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MIN);

#endif
}


// --------------------------------------------------------------------------------------
// Test the get_duration call on a sequence animation
void test_animation__sequence_get_duration(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int delay_a = 100;
  const int play_count_a = 1;
  const int total_duration_a = play_count_a * (delay_a + duration_a);

  const int duration_b = 500;
  const int delay_b = 200;
  const int play_count_b = 3;
  const int total_duration_b = play_count_b * (delay_b + duration_b);

  const int delay_seq = 150;
  const int play_count_seq = 2;

  int duration_total = play_count_seq * (total_duration_a + total_duration_b + delay_seq);


  // Create 2 test animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);
  animation_set_play_count(a, play_count_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);
  animation_set_play_count(b, play_count_b);

  // Create a sequence
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);
  animation_set_delay(seq, delay_seq);
  animation_set_play_count(seq, play_count_seq);


  // Check durations
  cl_assert_equal_i(animation_get_duration(a, false, false), duration_a);
  cl_assert_equal_i(animation_get_duration(a, false, true), play_count_a * duration_a);
  cl_assert_equal_i(animation_get_duration(a, true, false), delay_a + duration_a);
  cl_assert_equal_i(animation_get_duration(a, true, true), total_duration_a);

  cl_assert_equal_i(animation_get_duration(b, false, false), duration_b);
  cl_assert_equal_i(animation_get_duration(b, false, true), play_count_b * duration_b);
  cl_assert_equal_i(animation_get_duration(b, true, false), delay_b + duration_b);
  cl_assert_equal_i(animation_get_duration(b, true, true), total_duration_b);

  cl_assert_equal_i(animation_get_duration(seq, false, false), total_duration_a + total_duration_b);
  cl_assert_equal_i(animation_get_duration(seq, false, true),
                    play_count_seq * (total_duration_a + total_duration_b));
  cl_assert_equal_i(animation_get_duration(seq, true, false),
                    delay_seq + total_duration_a + total_duration_b);
  cl_assert_equal_i(animation_get_duration(seq, true, true), duration_total);

  animation_destroy(seq);

#endif
}


// --------------------------------------------------------------------------------------
// Test the get_duration call on a spawn animation
void test_animation__spawn_get_duration(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int delay_a = 100;
  const int play_count_a = 1;
  const int total_duration_a = play_count_a * (delay_a + duration_a);

  const int duration_b = 500;
  const int delay_b = 200;
  const int play_count_b = 3;
  const int total_duration_b = play_count_b * (delay_b + duration_b);

  const int delay_spawn = 150;
  const int play_count_spawn = 2;

  int duration_total = play_count_spawn * (MAX(total_duration_a, total_duration_b) + delay_spawn);


  // Create 2 test animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);
  animation_set_play_count(a, play_count_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);
  animation_set_play_count(b, play_count_b);

  // Create a spawn
  Animation *spawn = animation_spawn_create(a, b, NULL);
  cl_assert(spawn != NULL);
  animation_set_delay(spawn, delay_spawn);
  animation_set_play_count(spawn, play_count_spawn);


  // Check durations
  cl_assert_equal_i(animation_get_duration(a, false, false), duration_a);
  cl_assert_equal_i(animation_get_duration(a, false, true), play_count_a * duration_a);
  cl_assert_equal_i(animation_get_duration(a, true, false), delay_a + duration_a);
  cl_assert_equal_i(animation_get_duration(a, true, true), total_duration_a);

  cl_assert_equal_i(animation_get_duration(b, false, false), duration_b);
  cl_assert_equal_i(animation_get_duration(b, false, true), play_count_b * duration_b);
  cl_assert_equal_i(animation_get_duration(b, true, false), delay_b + duration_b);
  cl_assert_equal_i(animation_get_duration(b, true, true), total_duration_b);

  cl_assert_equal_i(animation_get_duration(spawn, false, false),
                    MAX(total_duration_a, total_duration_b));
  cl_assert_equal_i(animation_get_duration(spawn, false, true),
                    play_count_spawn * (MAX(total_duration_a, total_duration_b)));
  cl_assert_equal_i(animation_get_duration(spawn, true, false),
                    delay_spawn + MAX(total_duration_a, total_duration_b));
  cl_assert_equal_i(animation_get_duration(spawn, true, true), duration_total);

  animation_destroy(spawn);

#endif
}


// --------------------------------------------------------------------------------------
// Test unschedule all when we have multiple animations, some complex
void test_animation__unschedule_all(void) {
#ifdef TEST_INCLUDE_COMPLEX

  // Create animations

  // Create a sequence
  Animation *a = prv_create_test_animation();
  Animation *b = prv_create_test_animation();
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);

  // Create a spawn
  Animation *c = prv_create_test_animation();
  Animation *d = prv_create_test_animation();
  Animation *spawn = animation_spawn_create(c, d, NULL);
  cl_assert(spawn != NULL);

  // Create a primitive one
  Animation *e = prv_create_test_animation();

  // Schedule them all
  animation_schedule(seq);
  animation_schedule(spawn);
  animation_schedule(e);

  // Verify count
  cl_assert_equal_i(prv_count_scheduled_animations(), 7);

  // Unschedule all
  animation_unschedule_all();
  cl_assert_equal_i(prv_count_scheduled_animations(), 0);


  // Make sure just the setup and teardown handlers were called
  cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);

#endif
}


// --------------------------------------------------------------------------------------
// Test that we fail if we try and put a component in more than 1 complex animation
void test_animation__reuse_components(void) {
#ifdef TEST_INCLUDE_COMPLEX

  // Create animations

  // Create a sequence out of a and b
  Animation *a = prv_create_test_animation();
  Animation *b = prv_create_test_animation();
  Animation *seq = animation_sequence_create(a, b, NULL);
  cl_assert(seq != NULL);

  // Try to create a spawn out of b and c
  Animation *c = prv_create_test_animation();
  Animation *spawn = animation_spawn_create(c, b, NULL);
  cl_assert(spawn == NULL);

  // We should be able to create one out of c and d
  Animation *d = prv_create_test_animation();
  spawn = animation_spawn_create(c, d, NULL);
  cl_assert(spawn != NULL);

  animation_destroy(seq);
  animation_destroy(spawn);

#endif
}

// Test all the accessors
void test_animation__accessors(void) {
#ifdef TEST_INCLUDE_BASIC
  PropertyAnimation *prop_h;
  int16_t value = 0;
  int16_t start_value = 0, end_value = 100;
  const int duration = 200;
  const int delay = 25;

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  static const PropertyAnimationImplementation implementation = {
    .base = {
      .setup = prv_setup_handler,
      .update = (AnimationUpdateImplementation) property_animation_update_int16,
      .teardown = prv_teardown_handler
    },
    .accessors = {
      .setter = { .int16 = (const Int16Setter) prv_int16_setter, },
      .getter = { .int16 = (const Int16Getter) prv_int16_getter },
    },
  };

  prop_h = property_animation_create(&implementation, &value, &start_value, &end_value);
  Animation *h = property_animation_get_animation(prop_h);

  cl_assert(animation_set_auto_destroy(h, false) == true);

  // Handlers
  void *context = &value;
  animation_set_handlers(h, handlers, context);
  AnimationHandlers c_handlers = animation_get_handlers(h);
  cl_assert(memcmp(&c_handlers, &handlers, sizeof(AnimationHandlers)) == 0);

  // Context
  cl_assert(animation_get_context(h) == context);

  // Duration
  cl_assert(animation_get_duration(h, true, true) == 250); // default value
  animation_set_duration(h, duration);
  cl_assert(animation_get_duration(h, true, true) == duration);

  // Delay
  cl_assert(animation_get_delay(h) == 0);
  animation_set_delay(h, delay);
  cl_assert(animation_get_delay(h) == delay);
  cl_assert(animation_get_duration(h, true, true) == (duration + delay));

  // Play count
  cl_assert_equal_i(animation_get_play_count(h), 1);
  animation_set_play_count(h, 2);
  cl_assert_equal_i(animation_get_play_count(h), 2);
  cl_assert(animation_get_duration(h, true, true) == 2*(duration + delay));

  // Curve
  cl_assert(animation_get_curve(h) == AnimationCurveDefault);
  animation_set_curve(h, AnimationCurveEaseOut);
  cl_assert(animation_get_curve(h) == AnimationCurveEaseOut);
  
  static const PropertyAnimationImplementation implementation2 = {
    .base = {
      .setup = prv_setup_handler,
      .update = (AnimationUpdateImplementation) property_animation_update_gpoint,
      .teardown = prv_teardown_handler
    },
    .accessors = {
      .setter = { .int16 = (const Int16Setter) prv_gpoint_setter, },
      .getter = { .int16 = (const Int16Getter) prv_gpoint_getter },
    },
  };

  // Implementation
  cl_assert(animation_get_implementation(h) == &implementation.base);
  animation_set_implementation(h, &implementation2.base);
  cl_assert(animation_get_implementation(h) != &implementation.base);
  cl_assert(animation_get_implementation(h) == &implementation2.base);

  // Custom Curve
  cl_assert(animation_get_custom_curve(h) != prv_custom_curve);
  animation_set_custom_curve(h, prv_custom_curve);
  cl_assert(animation_get_custom_curve(h) == prv_custom_curve);
  
  // Reverse
  cl_assert(animation_get_reverse(h) == false);
  animation_set_reverse(h, true);
  cl_assert(animation_get_reverse(h) == true);
  animation_set_reverse(h, false);

  // Position
  int32_t elapsed_ms = 0;
  AnimationProgress progress = 0;
  animation_schedule(h);
  cl_must_pass(animation_get_elapsed(h, &elapsed_ms));
  cl_assert_equal_i(elapsed_ms, -delay);
  cl_assert_passert(animation_get_progress(h, &progress));
  animation_set_elapsed(h, 0);
  cl_must_pass(animation_get_elapsed(h, &elapsed_ms));
  cl_assert_equal_i(elapsed_ms, 0);
  cl_must_pass(animation_get_progress(h, &progress));
  cl_assert_equal_i(progress, 0);
  animation_set_elapsed(h, duration / 2);
  cl_must_pass(animation_get_elapsed(h, &elapsed_ms));
  cl_assert_equal_i(elapsed_ms, duration / 2);
  cl_must_pass(animation_get_progress(h, &progress));
  cl_assert_equal_i(progress, 32768); // Rounding occurs within, this is close to MAX / 2


  animation_destroy(h);
#endif
}

void test_animation__completed(void) {
#ifdef TEST_INCLUDE_BASIC
  const int duration_a = 300;

  // Create 1 property animations
  Animation *a = prv_create_test_animation();

  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler_check_finished
  };
  animation_set_handlers(a, handlers, animation_get_context(a));
  animation_set_duration(a, duration_a);

  prv_clear_handler_histories();
  uint64_t start_ms = prv_now_ms();
  animation_schedule(a);

  // Seek to just after the end of the second A
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
#endif
}



// --------------------------------------------------------------------------------------
// Test creating a sequence where the first argument is already scheduled and started
//
// Here's a graph of what we are doing
//
// 0      60      310  360           380           880
// |       |       |    |             |             |
// -----------------------------------------------------------------------------------
// delay_a | duration_a |
//                      |   delay_b   | duration_b  |
//                 | seq scheduled
//
void test_animation__sequence_of_already_scheduled_started(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;
  const int delay_a = 60;
  const int leftover_a = 50;

  const int duration_b = 500;
  const int delay_b = 20;

  const int leftover_seq = 40;
  const int delay_seq = 30;


  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();

  // Create a property animation and advance it
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);

  animation_schedule(a);

  // -------------------------------------------------------------------------------------
  // Start A and advance it
  prv_advance_to_ms_with_timers(start_ms + delay_a + MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
  animation_set_elapsed(a, duration_a - leftover_a);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);


  // -------------------------------------------------------------------------------------
  // Build up a sequence out of the leftover a + b
  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);

  // Should be an error trying to use a scheduled animation not in the first position
  Animation *seq = animation_sequence_create(b, a, NULL);
  cl_assert(seq == NULL);

  seq = animation_sequence_create(a, b, NULL);
  animation_set_delay(seq, delay_seq); // This delay not applicable since a was already scheduled
  animation_set_handlers(seq, handlers, seq);
  animation_schedule(seq);

  // The duration of seq should include all of a and b
  uint32_t duration = animation_get_duration(seq, true, true);
  cl_assert_equal_i(duration, duration_a + delay_a + duration_b + delay_b);

  // The position of seq should be the amount we already played of a, including the 'a' delay
  //  since a is embedded within seq
  int32_t position;
  animation_get_elapsed(seq, &position);
  cl_assert_equal_i(position, duration_a + delay_a - leftover_a);

  // Now, advance sequence to almost the end of seq. Positions don't include the delay, so
  // pass false for 'include_delay'
  animation_set_elapsed(seq, animation_get_duration(seq, false /*delay*/, true /*play_count*/)
                              - leftover_seq);


  // Verify that a finished and that a's stop handler got called before B's start handler
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);

  cl_assert(prv_last_handler_entry(&s_stopped_handler_calls, a)->fire_order
            < prv_last_handler_entry(&s_started_handler_calls, b)->fire_order);

  // Finish the sequence
  prv_advance_to_ms_with_timers(prv_now_ms() + leftover_seq + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);

#endif
}


// --------------------------------------------------------------------------------------
// Test creating a sequence where the first argument is already scheduled, but not started
//  yet (still in the delay portion).
//
// Here's a graph of what we are doing
//
// 0  100   200          360           380           880
// |   |     |            |             |             |
// -----------------------------------------------------------------------------------
// delay_a   | duration_a |
//                        |   delay_b   | duration_b  |
//     | seq scheduled
//
void test_animation__sequence_of_already_scheduled_not_started(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 160;
  const int delay_a = 200;

  const int duration_b = 500;
  const int delay_b = 20;
  const int leftover_seq = 50;


  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  prv_clear_handler_histories();

  // Create a property animation and advance it
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);

  uint64_t  start_ms = prv_now_ms();
  animation_schedule(a);

  // -------------------------------------------------------------------------------------
  // Got partway through the delayof a
  prv_advance_to_ms_with_timers(start_ms + 100);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);


  // -------------------------------------------------------------------------------------
  // Build up a sequence out of the leftover a + b
  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);

  Animation *seq = animation_sequence_create(a, b, NULL);
  animation_set_handlers(seq, handlers, seq);
  animation_schedule(seq);

  // The duration of seq should include all of a and b
  uint32_t duration = animation_get_duration(seq, true, true);
  cl_assert_equal_i(duration, duration_a + delay_a + duration_b + delay_b);

  // The position of seq should be the amount we already played of a, including the 'a' delay
  //  since a is embedded within seq
  int32_t position;
  animation_get_elapsed(seq, &position);
  cl_assert_equal_i(position, 100);

  // Now, advance sequence to almost the end of seq. Positions don't include the delay, so
  // pass false for 'include_delay'
  animation_set_elapsed(seq, animation_get_duration(seq, false /*delay*/, true /*play_count*/)
                              - leftover_seq);


  // Verify that a finished and that a's stop handler got called before B's start handler
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);

  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);

  cl_assert(prv_last_handler_entry(&s_stopped_handler_calls, a)->fire_order
            < prv_last_handler_entry(&s_started_handler_calls, b)->fire_order);

  // Finish the sequence
  prv_advance_to_ms_with_timers(prv_now_ms() + leftover_seq + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);

#endif
}



// --------------------------------------------------------------------------------------
// Test creating a sequence where the first argument is already completed.
// We will first create animation 'a' and run it to the end
// We will then create a sequence out of 'a' + 'b' and verify that if we advance that 'b' runs
//  correctly.
void test_animation__sequence_of_already_completed(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;

  const int duration_b = 500;
  const int delay_b = 20;

  const int delay_seq = 30;


  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();

  // Create a property animation and play it to the end
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);

  animation_schedule(a);

  // -------------------------------------------------------------------------------------
  // Start A and play to the end
  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);


  // -------------------------------------------------------------------------------------
  // Build up a sequence out of a + b
  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);

  Animation *seq = animation_sequence_create(a, b, NULL);
  animation_set_delay(seq, delay_seq);
  animation_set_handlers(seq, handlers, seq);
  animation_schedule(seq);

  // The duration of seq should include all of b
  uint32_t duration = animation_get_duration(seq, true, true);
  cl_assert_equal_i(duration, duration_b + delay_b + delay_seq);

  // The position of seq should be at -delay_seq
  int32_t position;
  animation_get_elapsed(seq, &position);
  cl_assert_equal_i(position, -delay_seq);

  // Finish the sequence
  prv_advance_to_ms_with_timers(prv_now_ms() + delay_b + duration_b + delay_seq
                                +  2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);

#endif
}



// --------------------------------------------------------------------------------------
// Test creating a spawn where where some children are already scheduled and some have
// already completed.
//
// Here's a graph of what we are doing
//
// 0      10           310  320  330  500   680   730  850   950     1080           1300
// |       |            |    |    |    |     |     |    |     |       |              |
// -----------------------------------------------------------------------------------
// delay_a | duration a |                    |
//                      | delay_b | duration_b     |
//                                     | delay_c   |   duration_c     |
//                                           | delay_s  | delay_d     | duration_d   |
// -----------------------------------------------------------------------------------
//                                           | spawn scheduled here
void test_animation__spawn_of_already_scheduled(void) {
#ifdef TEST_INCLUDE_COMPLEX
  const int duration_a = 300;   // This one will complete
  const int delay_a = 10;

  const int duration_b = 400;   // This one will have 50 ms left on it
  const int delay_b = 20;

  const int duration_c = 350;   
  const int delay_c = 230;

  const int duration_d = 220;   // This one won't be scheduled yet
  const int delay_d = 230;

  const int delay_spawn = 170;


  const AnimationHandlers handlers = {
    .started = prv_started_handler,
    .stopped = prv_stopped_handler
  };

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();

  // Create the animations
  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_b);
  animation_set_delay(b, delay_b);

  Animation *c = prv_create_test_animation();
  animation_set_duration(c, duration_c);
  animation_set_delay(c, delay_c);

  Animation *d = prv_create_test_animation();
  animation_set_duration(d, duration_d);
  animation_set_delay(d, delay_d);


  // -------------------------------------------------------------------------------------
  // Run A to completion
  animation_schedule(a);
  prv_advance_to_ms_with_timers(start_ms + 20);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
  prv_advance_to_ms_with_timers(start_ms + 310);

  // Schedule B now and run for a little
  animation_schedule(b);
  prv_advance_to_ms_with_timers(start_ms + 330);

  // Schedule C now and run for a while
  prv_advance_to_ms_with_timers(start_ms + 500);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
  animation_schedule(c);

  // Create the spawn using a, b, c, and d
  prv_advance_to_ms_with_timers(start_ms + 680);
  Animation *spawn = animation_spawn_create(a, b, c, d, NULL);
  animation_set_delay(spawn, delay_spawn);
  animation_set_handlers(spawn, handlers, spawn);
  animation_schedule(spawn);


  // Check the duration and position of the spawn
  uint32_t duration = animation_get_duration(spawn, true /*delay*/, true /*play_count*/);
  cl_assert_equal_i(duration, 1300 - 310);

  int32_t position;
  animation_get_elapsed(spawn, &position);
  cl_assert_equal_i(position, (680 - 310));


  // Run to the completion of B, start of C
  prv_advance_to_ms_with_timers(start_ms + 730 + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, spawn), 1);


  // Run to the completion of C, start of D
  prv_advance_to_ms_with_timers(start_ms + 1080 + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, d), 1);

  // Run to the completion of D
  prv_advance_to_ms_with_timers(start_ms + 1300 + 2 * MIN_FRAME_INTERVAL_MS);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, d), 1);
  cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, spawn), 1);

#endif
}

void prv_update_unschedule_all_handler(Animation *animation, const AnimationProgress distance) {
  prv_add_handler_entry(&s_update_handler_calls, animation, false,
                        (void *)(uintptr_t)distance /*context*/);
  DPRINTF("%"PRIu64" ms: Executing update handler for %d, distance: %d\n", prv_now_ms(),
          (int)animation, (int)distance);
  if (distance > ANIMATION_NORMALIZED_MAX / 2) {
    animation_unschedule_all();
  }
}

// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in an update handler.
static void prv_unschedule_all_in_update_handler(bool auto_destroy) {
  const int duration_a = 300;   // This one will complete
  const int delay_a = 10;

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();

  static const AnimationImplementation implementation = {
    .setup = prv_setup_handler,
    .update = prv_update_unschedule_all_handler,
    .teardown = prv_teardown_handler
  };

  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);
  animation_set_auto_destroy(a, auto_destroy);
  animation_schedule(a);

  Animation *b = prv_create_test_animation();
  animation_set_implementation(b, &implementation);
  animation_set_duration(b, duration_a);
  animation_set_delay(b, delay_a);
  animation_set_auto_destroy(b, auto_destroy);
  animation_schedule(b);

  Animation *c = prv_create_test_animation();
  animation_set_duration(c, duration_a);
  animation_set_delay(c, delay_a);
  animation_set_auto_destroy(c, auto_destroy);
  animation_schedule(c);

  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);

  animation_destroy(a);
  animation_destroy(b);
  animation_destroy(c);
}

// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in an update handler with auto destroy.
void test_animation__unschedule_all_in_update_handler_with_auto_destroy(void) {
  prv_unschedule_all_in_update_handler(true);
}

// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in an update handler without auto destroy.
void test_animation__unschedule_all_in_update_handler_without_auto_destroy(void) {
  prv_unschedule_all_in_update_handler(false);
}

static void prv_stopped_unschedule_all_handler(Animation *animation, bool finished, void *context) {
  prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
  DPRINTF("%"PRIu64" ms: Executing stopped handler for %d\n", prv_now_ms(), (int)animation);
  animation_unschedule_all();
}

// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in a stopped handler with/without auto destroy.
void prv_test_unschedule_all_in_stopped_handler(bool auto_destroy) {
  const int duration_a = 300;   // This one will complete
  const int delay_a = 10;

  prv_clear_handler_histories();
  uint64_t  start_ms = prv_now_ms();

  const AnimationHandlers handlers = {
    .stopped = prv_stopped_unschedule_all_handler,
  };

  void *context = NULL;

  Animation *a = prv_create_test_animation();
  animation_set_duration(a, duration_a);
  animation_set_delay(a, delay_a);
  animation_set_handlers(a, handlers, context);
  animation_set_auto_destroy(a, auto_destroy);
  animation_schedule(a);

  Animation *b = prv_create_test_animation();
  animation_set_duration(b, duration_a);
  animation_set_delay(b, delay_a);
  animation_set_handlers(b, handlers, context);
  animation_set_auto_destroy(b, auto_destroy);
  animation_schedule(b);

  Animation *c = prv_create_test_animation();
  animation_set_duration(c, duration_a);
  animation_set_delay(c, delay_a);
  animation_set_handlers(c, handlers, context);
  animation_set_auto_destroy(c, auto_destroy);
  animation_schedule(c);

  prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);

  animation_destroy(a);
  animation_destroy(b);
  animation_destroy(c);
}

// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in a stopped handler with auto destroy.
void test_animation__unschedule_all_in_stopped_handler_with_auto_destroy(void) {
  prv_test_unschedule_all_in_stopped_handler(true);
}

// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in a stopped handler without auto destroy.
void test_animation__unschedule_all_in_stopped_handler_without_auto_destroy(void) {
  prv_test_unschedule_all_in_stopped_handler(false);
}

void test_animation__custom_functions(void) {
  // just some pointer to compare against
  AnimationCurveFunction curve = (void*)1;
  InterpolateInt64Function interpolation = (void*)2;

  Animation *a = prv_create_test_animation();
  cl_assert_equal_p(animation_get_custom_curve(a), NULL);
  cl_assert_equal_p(animation_get_custom_interpolation(a), NULL);
  cl_assert_equal_i(animation_get_curve(a), AnimationCurveDefault);

  animation_set_custom_curve(a, curve);
  cl_assert_equal_p(animation_get_custom_curve(a), curve);
  cl_assert_equal_p(animation_get_custom_interpolation(a), NULL);
  cl_assert_equal_i(animation_get_curve(a), AnimationCurveCustomFunction);

  animation_set_custom_interpolation(a, interpolation);
  cl_assert_equal_p(animation_get_custom_curve(a), NULL);
  cl_assert_equal_p(animation_get_custom_interpolation(a), interpolation);
  cl_assert_equal_i(animation_get_curve(a), AnimationCurveCustomInterpolationFunction);

  animation_set_curve(a, AnimationCurveDefault);
  cl_assert_equal_p(animation_get_custom_curve(a), NULL);
  cl_assert_equal_p(animation_get_custom_interpolation(a), NULL);
  cl_assert_equal_i(animation_get_curve(a), AnimationCurveDefault);

  animation_destroy(a);
}

void test_animation__current_interpolate_override(void) {
  // just some pointer to compare against
  AnimationCurveFunction curve = (void*)1;
  InterpolateInt64Function interpolation = (void*)2;

  AnimationState *state = kernel_applib_get_animation_state();
  cl_assert_equal_p(state->aux->current_animation, NULL);
  cl_assert_equal_p(animation_private_current_interpolate_override(), NULL);

  Animation *a = prv_create_test_animation();
  AnimationPrivate *a_p = animation_private_animation_find(a);
  state->aux->current_animation = a_p;
  cl_assert_equal_p(animation_private_current_interpolate_override(), NULL);

  animation_set_custom_interpolation(a, interpolation);
  cl_assert_equal_p(animation_private_current_interpolate_override(), interpolation);

  animation_set_custom_curve(a, curve);
  cl_assert_equal_p(animation_private_current_interpolate_override(), NULL);

  animation_destroy(a);
}
