/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "animation.h"
#include "animation_interpolate.h"
#include "animation_private.h"

#include "applib/graphics/gtypes.h"
#include "system/passert.h"
#include "util/math.h"
#include "util/size.h"

int64_t interpolate_int64_linear(int32_t normalized, int64_t from, int64_t to) {
  return from + ((normalized * (to - from)) / ANIMATION_NORMALIZED_MAX);
}

int64_t interpolate_int64(int32_t normalized, int64_t from, int64_t to) {
  InterpolateInt64Function interpolate =
      animation_private_current_interpolate_override() ?: interpolate_int64_linear;
  return interpolate(normalized, from, to);
}

int16_t interpolate_int16(int32_t normalized, int16_t from, int16_t to) {
  const int64_t interpolated = interpolate_int64(normalized, from, to);
  return (int16_t)CLIP(interpolated, INT16_MIN, INT16_MAX);
}

uint32_t interpolate_uint32(int32_t normalized, uint32_t from, uint32_t to) {
  const int64_t interpolated = interpolate_int64(normalized, from, to);
  return (uint32_t)CLIP(interpolated, 0, UINT32_MAX);
}

Fixed_S32_16 interpolate_fixed32(int32_t normalized, Fixed_S32_16 from, Fixed_S32_16 to) {
  const int64_t interpolated = interpolate_int64(normalized, from.raw_value, to.raw_value);
  const int32_t raw_value =
      (int32_t) CLIP(interpolated, INT32_MIN, INT32_MAX);
  return Fixed_S32_16(raw_value);
}

GSize interpolate_gsize(int32_t normalized, GSize from, GSize to) {
  return (GSize) {
    .w = interpolate_int16(normalized, from.w, to.w),
    .h = interpolate_int16(normalized, from.w, to.w),
  };
}

GPoint interpolate_gpoint(int32_t normalized, GPoint from, GPoint to) {
  return (GPoint) {
    .x = interpolate_int16(normalized, from.x, to.x),
    .y = interpolate_int16(normalized, from.y, to.y),
  };
}

int16_t scale_int16(int16_t value, int16_t from, int16_t to) {
  return (int16_t) ((int32_t) value * to / from);
}

int32_t scale_int32(int32_t value, int32_t from, int32_t to) {
  return (int32_t) ((int64_t) value * to / from);
}

// -------------------------------------------------------

// these values are directly taken from "easing red line 001.mov"
// _in will be added to first value (easing in, anticipation)
// _out will be added to second value (overshoot, swing-back)
static const int32_t s_delta_moook_in[] = {0, 1, 20};
static const int32_t s_delta_moook_out[] = {INTERPOLATE_MOOOK_BOUNCE_BACK, 2, 1, 0};

// TODO: export these as interpolation functions as well
uint32_t interpolate_moook_in_duration() {
  return ARRAY_LENGTH(s_delta_moook_in) * ANIMATION_TARGET_FRAME_INTERVAL_MS;
}

uint32_t interpolate_moook_out_duration() {
  return ARRAY_LENGTH(s_delta_moook_out) * ANIMATION_TARGET_FRAME_INTERVAL_MS;
}

uint32_t interpolate_moook_duration() {
  return interpolate_moook_in_duration() + interpolate_moook_out_duration();
}

uint32_t interpolate_moook_soft_duration(int32_t num_frames_mid) {
  return interpolate_moook_duration() + num_frames_mid * ANIMATION_TARGET_FRAME_INTERVAL_MS;
}

uint32_t interpolate_moook_custom_duration(const MoookConfig *config) {
  PBL_ASSERTN(config);
  return ((config->num_frames_in + config->num_frames_mid + config->num_frames_out) *
          ANIMATION_TARGET_FRAME_INTERVAL_MS);
}

static int64_t prv_interpolate_moook(
    int32_t normalized, int64_t from, int64_t to, const int32_t *frames_in, int32_t num_frames_in,
    const int32_t *frames_out, int32_t num_frames_out, int32_t num_frames_mid, bool bounce_back) {
  const int32_t direction = ((from == to) ? 0 : ((from < to) ? 1 : -1));
  if (direction == 0) {
    return from;
  }

  const int32_t direction_out = direction * (bounce_back ? 1 : -1);
  const size_t num_frames_total = num_frames_in + num_frames_mid + num_frames_out;
  int32_t frame_idx =
      ((normalized * num_frames_total + (ANIMATION_NORMALIZED_MAX / (2 * num_frames_total))) /
       ANIMATION_NORMALIZED_MAX);
  frame_idx = CLIP(frame_idx, 0, (int)num_frames_total - 1);


  if (normalized == ANIMATION_NORMALIZED_MAX) {
    return to;
  } else if (frame_idx < 0) {
    return from;
  } else if (frame_idx < num_frames_in) {
    return from + (frames_in ? (direction * frames_in[frame_idx]) : 0);
  } else if ((frame_idx < (num_frames_in + num_frames_mid)) && (num_frames_mid > 0)) {
    const int64_t shifted_normalized = normalized -
        (((int64_t) num_frames_in * ANIMATION_NORMALIZED_MAX) / num_frames_total);
    const int32_t mid_normalized = ((int64_t) num_frames_total * shifted_normalized) /
        num_frames_mid;
    return interpolate_int64_linear(mid_normalized,
                                    from + (direction * frames_in[num_frames_in - 1]),
                                    to + (direction_out * frames_out[0]));
  } else {
    return to + (frames_out ? (direction_out *
                               frames_out[frame_idx - (num_frames_in + num_frames_mid)]) : 0);
  }
}

int64_t interpolate_moook_in(int32_t normalized, int64_t from, int64_t to, int32_t num_frames_to) {
  return prv_interpolate_moook(normalized, from, to, s_delta_moook_in,
                               ARRAY_LENGTH(s_delta_moook_in), NULL, num_frames_to, 0, true);
}

int64_t interpolate_moook_in_only(int32_t normalized, int64_t from, int64_t to) {
  return prv_interpolate_moook(normalized, from, to, s_delta_moook_in,
                               ARRAY_LENGTH(s_delta_moook_in), NULL, 0, 0, true);
}

int64_t interpolate_moook_out(int32_t normalized, int64_t from, int64_t to,
                              int32_t num_frames_from, bool bounce_back) {
  return prv_interpolate_moook(normalized, from, to, NULL, num_frames_from, s_delta_moook_out,
                               ARRAY_LENGTH(s_delta_moook_out), 0, bounce_back);
}

int64_t interpolate_moook(int32_t normalized, int64_t from, int64_t to) {
  return prv_interpolate_moook(normalized, from, to,
                               s_delta_moook_in, ARRAY_LENGTH(s_delta_moook_in),
                               s_delta_moook_out, ARRAY_LENGTH(s_delta_moook_out), 0, true);
}

int64_t interpolate_moook_soft(int32_t normalized, int64_t from, int64_t to,
                               int32_t num_frames_mid) {
  return prv_interpolate_moook(normalized, from, to,
                               s_delta_moook_in, ARRAY_LENGTH(s_delta_moook_in),
                               s_delta_moook_out, ARRAY_LENGTH(s_delta_moook_out),
                               num_frames_mid, true);
}

int64_t interpolate_moook_custom(int32_t normalized, int64_t from, int64_t to,
                                 const MoookConfig *config) {
  PBL_ASSERTN(config);
  return prv_interpolate_moook(normalized, from, to,
                               config->frames_in, config->num_frames_in,
                               config->frames_out, config->num_frames_out,
                               config->num_frames_mid, !config->no_bounce_back);
}
