#include "algos/rand.hpp"
#include "graphics/animation.hpp"
#include <fmt/core.h>
#include <unordered_map>
#include "dbc.hpp"

namespace ease2 {
  using namespace animation;

  double none(float tick) {
    return 0.0;
  }

  double linear(float tick) {
    return tick;
  }

  double sine(double x) {
    // old one? return std::abs(std::sin(seq.subframe * ease_rate));
    return (std::sin(x) + 1.0) / 2.0;
  }

  double out_circle(double x) {
    return std::sqrt(1.0f - ((x - 1.0f) * (x - 1.0f)));
  }

  double out_bounce(double x) {
    constexpr const double n1 = 7.5625;
    constexpr const double d1 = 2.75;

    if (x < 1 / d1) {
      return n1 * x * x;
    } else if (x < 2 / d1) {
      x -= 1.5;
      return n1 * (x / d1) * x + 0.75;
    } else if (x < 2.5 / d1) {
      x -= 2.25;
      return n1 * (x / d1) * x + 0.9375;
    } else {
      x -= 2.625;
      return n1 * (x / d1) * x + 0.984375;
    }
  }

  double in_out_back(double x) {
    constexpr const double c1 = 1.70158;
    constexpr const double c2 = c1 * 1.525;

    return x < 0.5
      ? (std::pow(2.0 * x, 2.0) * ((c2 + 1.0) * 2.0 * x - c2)) / 2.0
      : (std::pow(2.0 * x - 2.0, 2.0) * ((c2 + 1.0) * (x * 2.0 - 2.0) + c2) + 2.0) / 2.0;
  }

  double random(double tick) {
    return Random::uniform_real(0.0001f, 1.0f);
  }

  double normal_dist(double tick) {
    return Random::normal(0.5f, 0.1f);
  }

  void move_shake(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    pos_out.x = std::lerp(tr.min_x, tr.max_x, tick) + (pos_out.x * relative);
  }

  void move_bounce(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    pos_out.y = std::lerp(tr.min_y, tr.max_y, tick) + (pos_out.y * relative);
  }

  void move_rush(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    scale_out.x = std::lerp(tr.min_x, tr.max_x, tick) + (scale_out.x * relative);
    scale_out.y = std::lerp(tr.min_y, tr.max_y, tick) + (scale_out.y * relative);
    pos_out.y = pos_out.y - (pos_out.y * scale_out.y - pos_out.y) + (pos_out.y * relative);
  }

  void scale_squeeze(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    scale_out.x = std::lerp(tr.min_x, tr.max_x, tick) + (scale_out.x * relative);
  }

  void scale_squash(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    scale_out.y = std::lerp(tr.min_y, tr.max_y, tick) + (scale_out.y * relative);
  }

  void scale_stretch(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    scale_out.x = std::lerp(tr.min_x, tr.max_x, tick) + (scale_out.x * relative);
  }

  void scale_grow(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    scale_out.y = std::lerp(tr.min_y, tr.max_y, tick) + (scale_out.y * relative);
  }

  void move_slide(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    pos_out.x = std::lerp(tr.min_x, tr.max_x, tick) + (pos_out.x * relative);
    pos_out.y = std::lerp(tr.min_y, tr.max_y, tick) + (pos_out.y * relative);
  }

  void move_none(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
  }

  void scale_both(Transform &tr, sf::Vector2f& pos_out, sf::Vector2f& scale_out, float tick, bool relative) {
    scale_out.x = std::lerp(scale_out.x * tr.min_x, scale_out.x * tr.max_x, tick) + (scale_out.x * relative);
    scale_out.y = std::lerp(scale_out.y * tr.min_y, scale_out.y * tr.max_y, tick) + (scale_out.y * relative);
  }

  std::unordered_map<std::string, EaseFunc> map_of_easings{
    {"sine", sine},
    {"out_circle", out_circle},
    {"out_bounce", out_bounce},
    {"in_out_back", in_out_back},
    {"random", random},
    {"normal_dist", normal_dist},
    {"none", none},
    {"linear", linear},
  };

  std::unordered_map<std::string, MotionFunc> map_of_motions{
    {"move_bounce", move_bounce},
    {"move_rush", move_rush},
    {"scale_squeeze", scale_squeeze},
    {"scale_squash", scale_squash},
    {"scale_stretch", scale_stretch},
    {"scale_grow", scale_grow},
    {"move_slide", move_slide},
    {"move_none", move_none},
    {"scale_both", scale_both},
    {"move_shake", move_shake},
  };

  EaseFunc get_easing(const std::string& name) {
    dbc::check(map_of_easings.contains(name),
        $F("easing name {} does not exist", name));
    return map_of_easings.at(name);
  }

  MotionFunc get_motion(const std::string& name) {
    dbc::check(map_of_motions.contains(name),
        $F("motion name {} does not exist", name));
    return map_of_motions.at(name);
  }

}
