#include "player_actor.h"

#include "../core/asset_paths.h"
#include "../core/game_config.h"

#include <algorithm>
#include <cmath>

namespace ns_game {

namespace {

constexpr float kSpriteOffsetX = -32.0f;
constexpr float kSpriteOffsetY = -24.0f;

} // namespace

PlayerActor::PlayerActor() {
  SetSize(64.0f, 104.0f);
  SetTopLeftPosition(160.0f, 380.0f);
  loadSprites();
}

void PlayerActor::SetMovementWorld(const PlatformWorld* world) {
  movementWorld_ = world;
}

void PlayerActor::loadSprites() {
  idleSprite_ = frostbite2D::Sprite::createFromFile(assets::kMaleIdle);
  runSprite_ = frostbite2D::Sprite::createFromFile(assets::kMaleRun);
  punchSprite_ = frostbite2D::Sprite::createFromFile(assets::kMalePunch);

  auto configure = [this](frostbite2D::Ptr<frostbite2D::Sprite> sprite) {
    if (!sprite) {
      return;
    }
    sprite->SetSize(config::kPlayerFrameSize, config::kPlayerFrameSize);
    sprite->SetSourceRect(frostbite2D::Rect(0.0f, 0.0f,
                                            config::kPlayerFrameSize,
                                            config::kPlayerFrameSize));
    sprite->SetTopLeftPosition(kSpriteOffsetX, kSpriteOffsetY);
    sprite->SetVisible(false);
    AddChild(sprite);
  };

  configure(idleSprite_);
  configure(runSprite_);
  configure(punchSprite_);
  setAnimState(AnimState::Idle);
}

void PlayerActor::OnUpdate(float deltaTime) {
  deltaTime = std::min(deltaTime, 1.0f / 30.0f);
  updateInput();
  updatePhysics(deltaTime);
  updateAnimation(deltaTime);
}

void PlayerActor::updateInput() {
  const InputState input = inputProvider_.Sample();
  velocity_.x = input.moveX * config::kPlayerMoveSpeed;
  if (input.moveX < 0.0f) {
    facingLeft_ = true;
  } else if (input.moveX > 0.0f) {
    facingLeft_ = false;
  }

  if (input.jumpPressed && grounded_) {
    velocity_.y = config::kPlayerJumpSpeed;
    grounded_ = false;
  }

  if (input.attackPressed) {
    attackTimer_ = config::kAttackDuration;
    setAnimState(AnimState::Punch);
  }
}

void PlayerActor::updatePhysics(float deltaTime) {
  if (!movementWorld_) {
    return;
  }

  MovementBody body;
  body.position = GetTopLeftPosition();
  body.size = GetSize();
  body.velocity = velocity_;
  body.grounded = grounded_;

  body = platformMover_.Step(body, *movementWorld_, config::kGravity, deltaTime);
  velocity_ = body.velocity;
  grounded_ = body.grounded;
  SetTopLeftPosition(body.position);
}

void PlayerActor::updateAnimation(float deltaTime) {
  if (attackTimer_ > 0.0f) {
    attackTimer_ = std::max(0.0f, attackTimer_ - deltaTime);
    setAnimState(AnimState::Punch);
  } else if (std::abs(velocity_.x) > 1.0f) {
    setAnimState(AnimState::Run);
  } else {
    setAnimState(AnimState::Idle);
  }

  int frameCount = 10;
  float frameDuration = 0.1f;
  if (animState_ == AnimState::Idle) {
    frameDuration = 0.16f;
  } else if (animState_ == AnimState::Punch) {
    frameDuration = 0.07f;
  }

  animTimer_ += deltaTime;
  while (animTimer_ >= frameDuration) {
    animTimer_ -= frameDuration;
    frameIndex_ = (frameIndex_ + 1) % frameCount;
  }

  auto sprite = activeSprite();
  if (sprite) {
    sprite->SetSourceRect(
        frostbite2D::Rect(frameIndex_ * config::kPlayerFrameSize, 0.0f,
                          config::kPlayerFrameSize,
                          config::kPlayerFrameSize));
    sprite->SetFlippedX(facingLeft_);
  }
}

void PlayerActor::setAnimState(AnimState state) {
  if (animState_ != state) {
    animState_ = state;
    animTimer_ = 0.0f;
    frameIndex_ = 0;
  }

  if (idleSprite_) {
    idleSprite_->SetVisible(state == AnimState::Idle);
  }
  if (runSprite_) {
    runSprite_->SetVisible(state == AnimState::Run);
  }
  if (punchSprite_) {
    punchSprite_->SetVisible(state == AnimState::Punch);
  }
}

frostbite2D::Ptr<frostbite2D::Sprite> PlayerActor::activeSprite() const {
  switch (animState_) {
    case AnimState::Run:
      return runSprite_;
    case AnimState::Punch:
      return punchSprite_;
    case AnimState::Idle:
    default:
      return idleSprite_;
  }
}

} // namespace ns_game
