#include "animator.h"

#include "../base/interpolation.h"
#include "../base/log.h"
#include "animatable.h"
#include "engine.h"

using namespace base;

namespace eng {

Animator::Animator() {
  Engine::Get().AddAnimator(this);
}

Animator::~Animator() {
  Engine::Get().RemoveAnimator(this);
}

void Animator::Attach(Animatable* animatable) {
  elements_.push_back({animatable,
                       {0, 0},
                       0,
                       animatable->GetColor(),
                       (int)animatable->GetFrame()});
}

void Animator::Play(int animation, bool loop) {
  play_flags_ |= animation;
  if (loop)
    loop_flags_ |= animation;
  else
    loop_flags_ &= ~animation;
}

void Animator::Pause(int animation) {
  play_flags_ &= ~animation;
}

void Animator::Stop(int animation) {
  if ((animation & kMovement) != 0)
    movement_time_ = 0;
  if ((animation & kRotation) != 0)
    rotation_time_ = 0;
  if ((animation & kBlending) != 0)
    blending_time_ = 0;
  if ((animation & kFrames) != 0)
    frame_time_ = 0;
  if ((animation & kTimer) != 0)
    timer_time_ = 0;

  play_flags_ |= animation;
  Update(0);
  play_flags_ &= ~animation;
  loop_flags_ &= ~animation;
}

void Animator::PauseOrResumeAll(bool pause) {
  if (pause) {
    resume_flags_ = play_flags_;
    play_flags_ = 0;
  } else {
    play_flags_ = resume_flags_;
    resume_flags_ = 0;
  }
}

float Animator::GetTime(int animation) {
  if ((animation & kMovement) != 0)
    return movement_time_;
  if ((animation & kRotation) != 0)
    return rotation_time_;
  if ((animation & kBlending) != 0)
    return blending_time_;
  if ((animation & kFrames) != 0)
    return frame_time_;
  return timer_time_;
}

void Animator::SetTime(int animation, float time, bool force_update) {
  DCHECK(time >= 0 && time <= 1);

  if ((animation & kMovement) != 0)
    movement_time_ = time;
  if ((animation & kRotation) != 0)
    rotation_time_ = time;
  if ((animation & kBlending) != 0)
    blending_time_ = time;
  if ((animation & kFrames) != 0)
    frame_time_ = time;
  if ((animation & kTimer) != 0)
    timer_time_ = time;

  if (force_update) {
    unsigned play_flags = play_flags_;
    play_flags_ = animation;
    Update(0);
    play_flags_ = play_flags;
  }
}

void Animator::SetEndCallback(int animation, base::Closure cb) {
  if ((inside_cb_ & animation) != 0) {
    has_pending_cb_ = true;
    pending_cb_ = std::move(cb);
  }
  if ((animation & kMovement) != 0 && inside_cb_ != kMovement)
    movement_cb_ = std::move(cb);
  if ((animation & kRotation) != 0 && inside_cb_ != kRotation)
    rotation_cb_ = std::move(cb);
  if ((animation & kBlending) != 0 && inside_cb_ != kBlending)
    blending_cb_ = std::move(cb);
  if ((animation & kFrames) != 0 && inside_cb_ != kFrames)
    frame_cb_ = std::move(cb);
  if ((animation & kTimer) != 0 && inside_cb_ != kTimer)
    timer_cb_ = std::move(cb);
}

void Animator::SetMovement(Vector2 direction,
                           float duration,
                           Interpolator interpolator) {
  movement_direction_ = direction;
  movement_speed_ = 1.0f / duration;
  movement_interpolator_ = std::move(interpolator);

  for (auto& a : elements_)
    a.movement_last_pos = {0, 0};
}

void Animator::SetRotation(float trget,
                           float duration,
                           Interpolator interpolator) {
  rotation_target_ = trget;
  rotation_speed_ = 1.0f / duration;
  rotation_interpolator_ = std::move(interpolator);

  for (auto& a : elements_)
    a.rotation_last_theta = 0;
}

void Animator::SetBlending(Vector4 target,
                           float duration,
                           Interpolator interpolator) {
  blending_target_ = target;
  blending_speed_ = 1.0f / duration;
  for (auto& a : elements_)
    a.blending_start = a.animatable->GetColor();
  blending_interpolator_ = std::move(interpolator);
}

void Animator::SetFrames(int count,
                         int frames_per_second,
                         Interpolator interpolator) {
  frame_count_ = count;
  frame_speed_ = (float)frames_per_second / (float)count;
  for (auto& a : elements_)
    a.frame_start_ = a.animatable->GetFrame();
  frame_interpolator_ = std::move(interpolator);
}

void Animator::SetTimer(float duration) {
  timer_speed_ = 1.0f / duration;
}

void Animator::SetVisible(bool visible) {
  for (auto& a : elements_)
    a.animatable->SetVisible(visible);
}

void Animator::Update(float delta_time) {
  if (play_flags_ & kMovement)
    UpdateMovement(delta_time);
  if (play_flags_ & kRotation)
    UpdateRotation(delta_time);
  if (play_flags_ & kBlending)
    UpdateBlending(delta_time);
  if (play_flags_ & kFrames)
    UpdateFrame(delta_time);
  if (play_flags_ & kTimer)
    UpdateTimer(delta_time);
}

void Animator::EvalAnim(float frame_time) {
  for (auto& a : elements_) {
    if (play_flags_ & kMovement) {
      float time = movement_time_ + movement_speed_ * frame_time;
      float interpolated_time =
          movement_interpolator_ ? movement_interpolator_(time) : time;
      Vector2 pos = base::Lerp({0, 0}, movement_direction_, interpolated_time);
      a.animatable->Translate(pos - a.movement_last_pos);
      a.movement_last_pos = pos;
    }

    if (play_flags_ & kRotation) {
      float time = rotation_time_ + rotation_speed_ * frame_time;
      float interpolated_time =
          rotation_interpolator_ ? rotation_interpolator_(time) : time;
      float theta = base::Lerp(0.0f, rotation_target_, interpolated_time);
      a.animatable->Rotate(theta - a.rotation_last_theta);
      a.rotation_last_theta = theta;
    }

    if (play_flags_ & kBlending) {
      float time = blending_time_ + blending_speed_ * frame_time;
      float interpolated_time =
          blending_interpolator_ ? blending_interpolator_(time) : time;
      Vector4 r =
          base::Lerp(a.blending_start, blending_target_, interpolated_time);
      a.animatable->SetColor(r);
    }

    if (play_flags_ & kFrames) {
      float time = frame_time_ + frame_speed_ * frame_time;
      float interpolated_time =
          frame_interpolator_ ? frame_interpolator_(time) : time;
      int target = a.frame_start_ + frame_count_;
      int r = base::Lerp(a.frame_start_, target, interpolated_time);
      if (r < target)
        a.animatable->SetFrame(r);
    }
  }
}

void Animator::UpdateMovement(float delta_time) {
  if ((loop_flags_ & kMovement) == 0 && movement_time_ == 1.0f) {
    movement_time_ = 0;
    play_flags_ &= ~kMovement;
    if (movement_cb_) {
      inside_cb_ = kMovement;
      movement_cb_();
      inside_cb_ = kNone;
      if (has_pending_cb_) {
        has_pending_cb_ = false;
        movement_cb_ = std::move(pending_cb_);
      }
    }
    return;
  }

  movement_time_ += movement_speed_ * delta_time;
  if (movement_time_ > 1)
    movement_time_ =
        (loop_flags_ & kMovement) == 0 ? 1 : fmod(movement_time_, 1.0f);
}

void Animator::UpdateRotation(float delta_time) {
  if ((loop_flags_ & kRotation) == 0 && rotation_time_ == 1.0f) {
    rotation_time_ = 0;
    play_flags_ &= ~kRotation;
    if (rotation_cb_) {
      inside_cb_ = kRotation;
      rotation_cb_();
      inside_cb_ = kNone;
      if (has_pending_cb_) {
        has_pending_cb_ = false;
        rotation_cb_ = std::move(pending_cb_);
      }
    }
    return;
  }

  rotation_time_ += rotation_speed_ * delta_time;
  if (rotation_time_ > 1)
    rotation_time_ =
        (loop_flags_ & kRotation) == 0 ? 1 : fmod(rotation_time_, 1.0f);
}

void Animator::UpdateBlending(float delta_time) {
  if ((loop_flags_ & kBlending) == 0 && blending_time_ == 1.0f) {
    blending_time_ = 0;
    play_flags_ &= ~kBlending;
    if (blending_cb_) {
      inside_cb_ = kBlending;
      blending_cb_();
      inside_cb_ = kNone;
      if (has_pending_cb_) {
        has_pending_cb_ = false;
        blending_cb_ = std::move(pending_cb_);
      }
    }
    return;
  }

  blending_time_ += blending_speed_ * delta_time;
  if (blending_time_ > 1)
    blending_time_ =
        (loop_flags_ & kBlending) == 0 ? 1 : fmod(blending_time_, 1.0f);
}

void Animator::UpdateFrame(float delta_time) {
  if ((loop_flags_ & kFrames) == 0 && frame_time_ == 1.0f) {
    frame_time_ = 0;
    play_flags_ &= ~kFrames;
    if (frame_cb_) {
      inside_cb_ = kFrames;
      frame_cb_();
      inside_cb_ = kNone;
      if (has_pending_cb_) {
        has_pending_cb_ = false;
        frame_cb_ = std::move(pending_cb_);
      }
    }
    return;
  } else if ((loop_flags_ & kFrames) != 0 && frame_time_ == 1.0f) {
    frame_time_ = 0;
  }

  frame_time_ += frame_speed_ * delta_time;
  if (frame_time_ > 1)
    frame_time_ = 1;
}

void Animator::UpdateTimer(float delta_time) {
  if (timer_time_ == 1.0f) {
    timer_time_ = 0;
    play_flags_ &= ~kTimer;
    if (timer_cb_) {
      inside_cb_ = kTimer;
      timer_cb_();
      inside_cb_ = kNone;
      if (has_pending_cb_) {
        has_pending_cb_ = false;
        timer_cb_ = std::move(pending_cb_);
      }
    }
    return;
  }

  timer_time_ += timer_speed_ * delta_time;
  if (timer_time_ > 1)
    timer_time_ = 1;
}

}  // namespace eng
