#include "MetroidPrime/BodyState/CBodyController.hpp"

#include "MetroidPrime/BodyState/CAdditiveBodyState.hpp"
#include "MetroidPrime/BodyState/CBodyState.hpp"
#include "MetroidPrime/CActor.hpp"
#include "MetroidPrime/CAnimData.hpp"
#include "MetroidPrime/CAnimPlaybackParms.hpp"
#include "MetroidPrime/CModelData.hpp"
#include "MetroidPrime/CPhysicsActor.hpp"
#include "MetroidPrime/TCastTo.hpp"

#include "rstl/math.hpp"

#include "Kyoto/Animation/CPASDatabase.hpp"

CBodyController::CBodyController(CActor& actor, float turnSpeed, EBodyType bodyType)
: x0_actor(&actor)
, x2a4_bodyStateInfo(actor, bodyType)
, x2dc_rot(CQuaternion::NoRotation())
, x2ec_locomotionType(pas::kLT_Relaxed)
, x2f0_fallState(pas::kFS_Zero)
, x2f4_bodyType(bodyType)
, x2f8_curAnim(-1)
, x2fc_turnSpeed(turnSpeed)
, x300_24_animationOver(false)
, x300_25_active(false)
, x300_26_frozen(false)
, x300_27_hasBeenFrozen(false)
, x300_28_playDeathAnims(true)
, x304_intoFreezeDur(0.f)
, x308_frozenDur(0.f)
, x30c_breakoutDur(0.f)
, x310_timeFrozen(0.f)
, x314_backedUpForce(CVector3f::Zero())
, x320_fireDur(0.f)
, x324_electrocutionDur(0.f)
, x328_timeOnFire(0.f)
, x32c_timeElectrocuting(0.f)
, x330_restrictedFlyerMoveSpeed(0.f) {
  x2a4_bodyStateInfo.SetBodyController(this);
}

void CBodyController::Activate(CStateManager& mgr) {
  x300_25_active = true;
  x2a4_bodyStateInfo.SetState(pas::EAnimationState(GetPASDatabase().GetDefaultState()));
  x2a4_bodyStateInfo.GetCurrentState()->Start(*this, mgr);
  x2a4_bodyStateInfo.GetCurrentAdditiveState()->Start(*this, mgr);
}

void CBodyController::Update(float dt, CStateManager& mgr) {
  SetPlaybackRate(1.f);

  if (!x300_25_active) {
    return;
  }
  x300_24_animationOver = !GetOwner().GetModelData()->GetAnimationData()->IsAnimTimeRemaining(
      dt, rstl::string_l("Whole Body"));
  x4_cmdMgr.BlendSteeringCmds();
  x2dc_rot = CQuaternion::NoRotation();
  UpdateBody(dt, mgr);
  if (CPhysicsActor* act = TCastToPtr< CPhysicsActor >(x0_actor))
    act->RotateInOneFrameOR(x2dc_rot, dt);
  x4_cmdMgr.Reset();
}

bool CBodyController::HasBodyState(pas::EAnimationState state) const {
  return GetOwner().GetModelData()->GetAnimationData()->GetPASDatabase().HasState((int)state);
}

pas::EFallState CBodyController::GetFallState() const { return x2f0_fallState; }

void CBodyController::SetFallState(pas::EFallState state) { x2f0_fallState = state; }

void CBodyController::UpdateBody(float dt, CStateManager& mgr) {
  UpdateFrozenInfo(dt, mgr);
  if (x320_fireDur > 0.f) {
    if (x328_timeOnFire > x320_fireDur) {
      x328_timeOnFire = 0.f;
      x320_fireDur = 0.f;
    } else {
      x328_timeOnFire += dt;
    }
  } else if (x324_electrocutionDur > 0.f) {
    if (x32c_timeElectrocuting > x324_electrocutionDur) {
      x32c_timeElectrocuting = 0.f;
      x324_electrocutionDur = 0.f;
    } else {
      x32c_timeElectrocuting += dt;
    }
  }

  if (GetPercentageFrozen() < 1.f && x300_28_playDeathAnims) {
    pas::EAnimationState nextState =
        x2a4_bodyStateInfo.GetCurrentState()->UpdateBody(dt, *this, mgr);
    if (nextState != pas::kAS_Invalid) {
      x2a4_bodyStateInfo.GetCurrentState()->Shutdown(*this);
      x2a4_bodyStateInfo.SetState(nextState);
      x2a4_bodyStateInfo.GetCurrentState()->Start(*this, mgr);
    }

    nextState = x2a4_bodyStateInfo.GetCurrentAdditiveState()->UpdateBody(dt, *this, mgr);
    if (nextState != pas::kAS_Invalid) {
      x2a4_bodyStateInfo.GetCurrentAdditiveState()->Shutdown(*this);
      x2a4_bodyStateInfo.SetAdditiveState(nextState);
      x2a4_bodyStateInfo.GetCurrentAdditiveState()->Start(*this, mgr);
    }
  }
}

void CBodyController::SetLocomotionType(pas::ELocomotionType type) { x2ec_locomotionType = type; }

void CBodyController::SetTurnSpeed(float speed) { x2fc_turnSpeed = rstl::max_val(0.f, speed); }

void CBodyController::EnableAnimation(bool enable) {
  GetOwner().ModelData()->AnimationData()->SetIsAnimating(enable);
}

void CBodyController::SetCurrentAnimation(const CAnimPlaybackParms& parms, bool loop,
                                          bool noTrans) {
  GetOwner().ModelData()->AnimationData()->SetAnimation(parms, noTrans);
  GetOwner().ModelData()->EnableLooping(loop);
  x2f8_curAnim = parms.GetAnimationId();
}

float CBodyController::GetAnimTimeRemaining() const {
  return GetOwner().GetModelData()->GetAnimationData()->GetAnimTimeRemaining(
      rstl::string_l("Whole Body"));
}

void CBodyController::SetPlaybackRate(float rate) {
  GetOwner().ModelData()->AnimationData()->SetPlaybackRate(rate);
}

void CBodyController::MultiplyPlaybackRate(float mul) {
  GetOwner().ModelData()->AnimationData()->MultiplyPlaybackRate(mul);
}

void CBodyController::SetDeltaRotation(const CQuaternion& q) { x2dc_rot = x2dc_rot * q; }

void CBodyController::FaceDirection(const CVector3f& v0, float dt) {}

void CBodyController::FaceDirection3D(const CVector3f& v0, float dt) {}

const CPASDatabase& CBodyController::GetPASDatabase() const {
  return GetOwner().GetModelData()->GetAnimationData()->GetPASDatabase();
}

void CBodyController::PlayBestAnimation(const CPASAnimParmData& parms, CRandom16& r) {
  rstl::pair< float, int > best = GetPASDatabase().FindBestAnimation(parms, r, -1);
  CAnimPlaybackParms playParms(best.second, -1, 1.f, true);
  SetCurrentAnimation(playParms, false, false);
}

void CBodyController::LoopBestAnimation(const CPASAnimParmData& parms, CRandom16& r) {
  rstl::pair< float, int > best = GetPASDatabase().FindBestAnimation(parms, r, -1);
  CAnimPlaybackParms playParms(best.second, -1, 1.f, true);
  SetCurrentAnimation(playParms, true, false);
}

void CBodyController::Freeze(float intoFreezeDur, float frozenDur, float breakoutDur) {
  x304_intoFreezeDur = intoFreezeDur;
  x308_frozenDur = frozenDur;
  x30c_breakoutDur = breakoutDur;
  x300_26_frozen = true;
  x300_27_hasBeenFrozen = true;

  CPhysicsActor* act = TCastToPtr< CPhysicsActor >(GetOwner());
  x314_backedUpForce = act->GetConstantForceWR();
  act->SetConstantForceWR(CVector3f::Zero());
  act->SetMomentumWR(CVector3f::Zero());

  x320_fireDur = 0.f;
  x328_timeOnFire = 0.f;
  x310_timeFrozen = 0.f;
}

void CBodyController::FrozenBreakout() {
  if (x300_26_frozen) {
    float timeToBreakout = x304_intoFreezeDur + x308_frozenDur;
    if (x310_timeFrozen < timeToBreakout)
      x310_timeFrozen = timeToBreakout;
  }
}

void CBodyController::UnFreeze() {
  SetPlaybackRate(1.f);
  x300_26_frozen = false;
  x304_intoFreezeDur = 0.f;
  x308_frozenDur = 0.f;
  x30c_breakoutDur = 0.f;
  x310_timeFrozen = 0.f;
  x0_actor->SetVolume(127);
  CPhysicsActor* act = TCastToPtr< CPhysicsActor >(GetOwner());
  act->SetConstantForceWR(x314_backedUpForce);
  act->SetVelocityWR(x314_backedUpForce / act->GetMass());
}

float CBodyController::GetPercentageFrozen() const {
  float sum = x304_intoFreezeDur + x308_frozenDur + x30c_breakoutDur;
  if (x310_timeFrozen == 0.f || sum == 0.f)
    return 0.f;

  if (x310_timeFrozen <= x304_intoFreezeDur && x304_intoFreezeDur > 0.f)
    return x310_timeFrozen / x304_intoFreezeDur;

  if (x310_timeFrozen < sum - x30c_breakoutDur)
    return 1.f;
  if (x30c_breakoutDur <= 0.f)
    return 1.f;

  return 1.f - (x310_timeFrozen - (x308_frozenDur + x304_intoFreezeDur)) / x30c_breakoutDur;
}

void CBodyController::SetOnFire(float duration) {
  x320_fireDur = duration;
  x328_timeOnFire = 0.f;
  if (IsFrozen()) {
    UnFreeze();
  }
}

void CBodyController::DouseFlames() {
  if (x320_fireDur > 0.f) {
    x320_fireDur = 0.f;
    x328_timeOnFire = 0.f;
  }
}

void CBodyController::SetElectrocuting(float duration) {
  if (!IsElectrocuting()) {
    CBCAdditiveReactionCmd reaction(pas::kART_Electrocution, 1.f, true);
    x4_cmdMgr.DeliverCmd(reaction);
  }
  x324_electrocutionDur = duration;
  x32c_timeElectrocuting = 0.f;
  if (IsFrozen())
    UnFreeze();
  else if (IsOnFire())
    DouseFlames();
}

void CBodyController::StopElectrocution() {
  x324_electrocutionDur = 0.f;
  x32c_timeElectrocuting = 0.f;
  CBodyStateCmd cmd(kBSC_StopReaction);
  x4_cmdMgr.DeliverCmd(cmd);
}
