#include "MetroidPrime/BodyState/CBSProjectileAttack.hpp"

#include "MetroidPrime/BodyState/CBodyController.hpp"
#include "MetroidPrime/CActor.hpp"
#include "MetroidPrime/CAnimPlaybackParms.hpp"
#include "MetroidPrime/CStateManager.hpp"

#include "Kyoto/Animation/CPASAnimParmData.hpp"
#include "Kyoto/Animation/CPASDatabase.hpp"
#include "Kyoto/Math/CAbsAngle.hpp"

CBSProjectileAttack::CBSProjectileAttack() {}

void CBSProjectileAttack::Start(CBodyController& bc, CStateManager& mgr) {
  const CBCProjectileAttackCmd* cmd =
      static_cast< const CBCProjectileAttackCmd* >(bc.CommandMgr().GetCmd(kBSC_ProjectileAttack));

  CActor& actor = bc.GetOwner();
  CVector3f diff = cmd->GetTargetPosition() - actor.GetTranslation();
  CVector3f localDelta = actor.GetTransform().TransposeRotate(diff);

  CAbsAngle angle = CAbsAngle::FromRadians(atan2(localDelta.GetY(), localDelta.GetX()));
  const float attackAngle = angle.AsDegrees();

  const CPASDatabase& db = bc.GetPASDatabase();

  const CPASAnimParmData parms(
      pas::kAS_ProjectileAttack, CPASAnimParm::FromEnum(cmd->GetAttackSeverity()),
      CPASAnimParm::FromReal32(angle.AsDegrees()), CPASAnimParm::FromEnum(bc.GetLocomotionType()));

  rstl::pair< float, int > best1 = db.FindBestAnimation(parms, *mgr.Random(), -1);
  if (cmd->BlendTwoClosest()) {
    rstl::pair< float, int > best2 = db.FindBestAnimation(parms, *mgr.Random(), best1.second);
    const CPASAnimState* projAttackState = db.GetAnimState(pas::kAS_ProjectileAttack);

    CPASAnimParm parmData1(projAttackState->GetAnimParmData(best1.second, 1));
    CPASAnimParm parmData2(projAttackState->GetAnimParmData(best2.second, 1));

    float angle1 = parmData1.GetReal32Value();
    float angle2 = parmData2.GetReal32Value();
    if (angle1 - angle2 > 180.f) {
      angle2 = angle2 + 360.f;
    } else if (angle2 - angle1 > 180.f) {
      angle1 = angle1 + 360.f;
    }
    const CAnimPlaybackParms playParms(best1.second, best2.second,
                                       (angle1 - attackAngle) / (angle1 - angle2), true);
    bc.SetCurrentAnimation(playParms, false, false);
  } else {
    const CAnimPlaybackParms playParms(best1.second, -1, 1.f, true);
    bc.SetCurrentAnimation(playParms, false, false);
  }
}

pas::EAnimationState CBSProjectileAttack::UpdateBody(float dt, CBodyController& bc,
                                                     CStateManager& mgr) {
  const pas::EAnimationState st = GetBodyStateTransition(dt, bc);
  if (st == pas::kAS_Invalid) {
    CBodyStateCmdMgr& cmdMgr = bc.CommandMgr();
    if (cmdMgr.GetTargetVector().IsNonZero()) {
      bc.FaceDirection(cmdMgr.GetTargetVector(), dt);
    }
  }
  return st;
}

void CBSProjectileAttack::Shutdown(CBodyController&) {}

pas::EAnimationState CBSProjectileAttack::GetBodyStateTransition(float dt, CBodyController& bc) {
  CBodyStateCmdMgr& cmdMgr = bc.CommandMgr();
  if (cmdMgr.GetCmd(kBSC_Hurled)) {
    return pas::kAS_Hurled;
  }
  if (cmdMgr.GetCmd(kBSC_KnockDown)) {
    return pas::kAS_Fall;
  }
  if (cmdMgr.GetCmd(kBSC_LoopHitReaction)) {
    return pas::kAS_LoopReaction;
  }
  if (cmdMgr.GetCmd(kBSC_KnockBack)) {
    return pas::kAS_KnockBack;
  }
  if (cmdMgr.GetCmd(kBSC_Locomotion)) {
    return pas::kAS_Locomotion;
  }
  if (bc.IsAnimationOver() || cmdMgr.GetCmd(kBSC_NextState)) {
    return pas::kAS_Locomotion;
  }
  return pas::kAS_Invalid;
}

bool CBSProjectileAttack::CanShoot() const { return true; }

CBSProjectileAttack::~CBSProjectileAttack() {}
