#include "Runtime/MP1/World/CSpankWeed.hpp"

#include <array>

#include "Runtime/CStateManager.hpp"
#include "Runtime/Collision/CCollisionActor.hpp"
#include "Runtime/World/CPatternedInfo.hpp"
#include "Runtime/World/CPlayer.hpp"

#include <logvisor/logvisor.hpp>

namespace metaforce::MP1 {
logvisor::Module SpankLog("metaforce::MP1::SpankWeed");

constexpr std::array<SSphereJointInfo, 7> kArmCollision{{
    {"Arm_4", 1.5f},
    {"Arm_6", 1.f},
    {"Arm_7", 1.f},
    {"Arm_8", 1.f},
    {"Arm_9", 1.f},
    {"Arm_11", 1.f},
    {"Swoosh_LCTR", 1.5f},
}};

CSpankWeed::CSpankWeed(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,
                       CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,
                       float maxDetectionRange, float maxHearingRange, float maxSightRange, float hideTime)
: CPatterned(ECharacter::SpankWeed, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,
             EMovementType::Flyer, EColliderType::One, EBodyType::Restricted, actParms, EKnockBackVariant::Medium)
, x568_maxDetectionRange(maxDetectionRange)
, x56c_detectionHeightRange(pInfo.GetDetectionHeightRange())
, x570_maxHearingRange(maxHearingRange)
, x574_maxSightRange(maxSightRange)
, x578_hideTime(hideTime)
, x584_retreatOrigin(xf.origin) {
  SetCallTouch(false);
  CreateShadow(false);

  const zeus::CVector3f modelScale = GetModelData()->GetScale();
  if (modelScale.x() != modelScale.y() || modelScale.x() != modelScale.z()) {
    float scale = modelScale.magnitude() / std::sqrt(3.f);

    GetModelData()->SetScale(zeus::CVector3f(scale));
    SpankLog.report(logvisor::Level::Warning,
                    FMT_STRING("WARNING: Non-uniform scale {} applied to Spank Weed"
                        "...changing scale to ({} {} {})\n"),
                    modelScale, scale, scale, scale);
  }
  CMaterialList list = GetMaterialFilter().GetExcludeList();
  list.Add(EMaterialTypes::Character);
  list.Add(EMaterialTypes::Player);
  SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(GetMaterialFilter().GetIncludeList(), list));

  const CSegId segId = GetModelData()->GetAnimationData()->GetLocatorSegId("lockon_target_LCTR"sv);
  if (segId.IsValid()) {
    const zeus::CTransform locatorXf = GetTransform() * zeus::CTransform::Scale(GetModelData()->GetScale()) *
                                       GetModelData()->GetAnimationData()->GetLocatorTransform(segId, nullptr);
    x5a8_lockonTarget = locatorXf.origin;
    x59c_lockonOffset = locatorXf.origin - GetTranslation();
  }
  x460_knockBackController.SetAutoResetImpulse(false);
}

void CSpankWeed::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {
  bool oldActive = GetActive();
  if (msg == EScriptObjectMessage::Activate) {
    if (x90_actorLights)
      x90_actorLights->SetDirty();
  } else if (msg == EScriptObjectMessage::Decrement) {
    if (x5b4_ != 0 && x5b4_ != 5 && x5b4_ != 6 && x5b4_ != 4) {
      x400_24_hitByPlayerProjectile = true;
      x428_damageCooldownTimer = x424_damageWaitTime;
    }
  } else if (msg == EScriptObjectMessage::Registered) {
    if (!x450_bodyController->GetActive()) {
      x450_bodyController->Activate(mgr);
      zeus::CVector3f extents = GetBoundingBox().extents();

      SetBoundingBox({-extents, extents});
    }

    std::vector<CJointCollisionDescription> joints;
    joints.reserve(12);

    for (const SSphereJointInfo& joint : kArmCollision) {
      const CSegId id = GetModelData()->GetAnimationData()->GetLocatorSegId(joint.name);
      if (id.IsValid()) {
        joints.push_back(CJointCollisionDescription::SphereCollision(id, joint.radius, joint.name, 0.001f));
      }
    }

    x594_collisionMgr =
        std::make_unique<CCollisionActorManager>(mgr, GetUniqueId(), GetAreaIdAlways(), joints, GetActive());
    CMaterialList list;
    list.Add(EMaterialTypes::CameraPassthrough);
    list.Add(EMaterialTypes::Immovable);
    x594_collisionMgr->AddMaterial(mgr, list);
    if (x90_actorLights) {
      x90_actorLights->SetDirty();
      zeus::CVector3f swooshOrigin = GetScaledLocatorTransform("swoosh_LCTR"sv).origin;
      x90_actorLights->SetActorPositionBias(GetTransform().buildMatrix3f() * swooshOrigin);
    }
  } else if (msg == EScriptObjectMessage::Touched) {
    if (TCastToPtr<CCollisionActor> colAct = mgr.ObjectById(uid)) {
      if (TCastToPtr<CPlayer> player = mgr.ObjectById(colAct->GetLastTouchedObject())) {
        if (x420_curDamageRemTime <= 0.f && x5b4_ != 4 && x5b4_ != 6) {
          mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), GetContactDamage(),
                          CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});
          x420_curDamageRemTime = x424_damageWaitTime;
        }
      }
    }
  } else if (msg == EScriptObjectMessage::Deleted) {
    mgr.FreeScriptObject(x590_);
    x594_collisionMgr->Destroy(mgr);
  } else if (msg == EScriptObjectMessage::SuspendedMove) {
    x594_collisionMgr->SetMovable(mgr, false);
  }

  CPatterned::AcceptScriptMsg(msg, uid, mgr);

  bool active = GetActive();
  if (active != oldActive && x594_collisionMgr)
    x594_collisionMgr->SetActive(mgr, active);
}

void CSpankWeed::Think(float dt, CStateManager& mgr) {
  if (!GetActive())
    return;

  HealthInfo(mgr)->SetHP(1000000.0f);

  if (!x598_isHiding) {
    zeus::CVector3f eyeOrigin = GetLocatorTransform("Eye"sv).origin;
    MoveCollisionPrimitive(GetTransform().rotate(GetModelData()->GetScale() * eyeOrigin));
    x594_collisionMgr->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace);
    xe4_27_notInSortedLists = true;
  }

  CPatterned::Think(dt, mgr);
}

zeus::CVector3f CSpankWeed::GetOrbitPosition(const CStateManager& mgr) const {
  zeus::CVector3f ret = CPatterned::GetOrbitPosition(mgr);
  float delay = std::max(1.f, x330_stateMachineState.GetDelay());
  if (x5b4_ == 3 && x5b8_ == 2) {
    return (ret * (1.f - delay) + ((GetTranslation() + x59c_lockonOffset) * delay));
  } else if (x5b4_ == 2 && x5b8_ == 3) {
    return (GetTranslation() + x59c_lockonOffset) * (1.f - delay) + (ret * delay);
  }
  return ret;
}

zeus::CVector3f CSpankWeed::GetAimPosition(const CStateManager&, float dt) const {
  zeus::CVector3f pos = (dt > 0.f ? PredictMotion(dt).x0_translation : zeus::skZero3f);

  const CSegId id = GetModelData()->GetAnimationData()->GetLocatorSegId("lockon_target_LCTR"sv);
  if (id.IsValid()) {
    const zeus::CVector3f lockonOff = GetModelData()->GetAnimationData()->GetLocatorTransform(id, nullptr).origin;
    return pos + (GetTransform() * (GetModelData()->GetScale() * lockonOff));
  }

  return pos + GetBoundingBox().center();
}

void CSpankWeed::Flinch(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x5bc_ = 0;
    x5b4_ = 0;
    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);
  } else if (msg == EStateMsg::Update) {
    if (x5bc_ == 0) {
      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::KnockBack)
        x5bc_ = 2;
      else
        x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockBackCmd({}, pas::ESeverity::Zero));
    } else if (x5bc_ == 2 &&
               x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::KnockBack)
      x5bc_ = 3;
  } else if (msg == EStateMsg::Deactivate) {
    x5b8_ = 4;
  }
}

bool CSpankWeed::Delay(CStateManager&, float) {
  if (x400_24_hitByPlayerProjectile) {
    if (x330_stateMachineState.GetTime() > x578_hideTime) {
      x400_24_hitByPlayerProjectile = false;
      return true;
    }
    return false;
  }

  return true;
}

bool CSpankWeed::InRange(CStateManager& mgr, float) {
  float playerDist = GetPlayerDistance(mgr);
  if (x56c_detectionHeightRange > 0.f) {
    return std::fabs(mgr.GetPlayer().GetTranslation().z() - GetTranslation().z()) < x56c_detectionHeightRange &&
           playerDist < (x574_maxSightRange * x574_maxSightRange);
  }

  return playerDist < (x574_maxSightRange * x574_maxSightRange);
}

bool CSpankWeed::HearPlayer(CStateManager& mgr, float) {
  float playerDist = GetPlayerDistance(mgr);
  if (x56c_detectionHeightRange > 0.f) {
    return std::fabs(mgr.GetPlayer().GetTranslation().z() - GetTranslation().z()) < x56c_detectionHeightRange &&
           playerDist < (x570_maxHearingRange * x570_maxHearingRange);
  }

  return playerDist < (x570_maxHearingRange * x570_maxHearingRange);
}

bool CSpankWeed::InDetectionRange(CStateManager& mgr, float) {
  float playerDist = GetPlayerDistance(mgr);
  if (x56c_detectionHeightRange > 0.f) {
    return std::fabs(mgr.GetPlayer().GetTranslation().z() - GetTranslation().z()) < x56c_detectionHeightRange &&
           playerDist < (x568_maxDetectionRange * x568_maxDetectionRange);
  }

  return playerDist < (x568_maxDetectionRange * x568_maxDetectionRange);
}

void CSpankWeed::Attack(CStateManager&, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));
  } else if (msg == EStateMsg::Update) {
    if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::MeleeAttack)
      return;

    x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Zero));
  } else if (msg == EStateMsg::Deactivate) {
    x5b8_ = 3;
  }
}

void CSpankWeed::TargetPatrol(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Combat);
    RemoveMaterial(EMaterialTypes::Solid, mgr);
    x5b4_ = 2;
  } else if (msg == EStateMsg::Deactivate) {
    x5b8_ = 2;
  }
}

void CSpankWeed::Lurk(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x460_knockBackController.SetEnableFreeze(true);
    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk);
    RemoveMaterial(EMaterialTypes::Solid, mgr);
    x5b4_ = 1;
  } else if (msg == EStateMsg::Deactivate) {
    x5b8_ = 1;
  }
}

void CSpankWeed::FadeOut(CStateManager&, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x5bc_ = 0;
    x57c_canKnockBack = false;
    x5b4_ = 6;
  } else if (msg == EStateMsg::Update) {
    if (x5bc_ == 0) {
      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)
        x5bc_ = 2;
      else
        x450_bodyController->GetCommandMgr().DeliverCmd(
            CBCStepCmd(pas::EStepDirection::Backward, pas::EStepType::Normal));

    } else if (x5bc_ == 2) {
      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step)
        x5bc_ = 3;
    }
  } else if (msg == EStateMsg::Deactivate) {
    x5b8_ = 6;
  }
}

void CSpankWeed::FadeIn(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x5bc_ = 0;
    x57c_canKnockBack = true;
    x5b4_ = 5;
  } else if (msg == EStateMsg::Update) {
    if (x5bc_ == 0) {
      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() == pas::EAnimationState::Step)
        x5bc_ = 2;
      else
        x450_bodyController->GetCommandMgr().DeliverCmd(
            CBCStepCmd(pas::EStepDirection::Forward, pas::EStepType::Normal));

    } else if (x5bc_ == 2) {
      if (x450_bodyController->GetBodyStateInfo().GetCurrentStateId() != pas::EAnimationState::Step)
        x5bc_ = 3;
    }
  } else if (msg == EStateMsg::Deactivate) {
    x5b8_ = 5;
  }

  xe7_28_worldLightingDirty = true;
}

void CSpankWeed::Patrol(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x460_knockBackController.SetEnableFreeze(false);
    x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);
    RemoveMaterial(EMaterialTypes::Solid, EMaterialTypes::Scannable, mgr);
    RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr);
    x594_collisionMgr->SetActive(mgr, false);
    x598_isHiding = true;
    x5b4_ = 0;
  } else if (msg == EStateMsg::Deactivate) {
    AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, EMaterialTypes::Scannable, mgr);
    SetTranslation(x584_retreatOrigin);
    x594_collisionMgr->SetActive(mgr, true);
    x598_isHiding = false;
    x460_knockBackController.SetEnableFreeze(true);
    x5b8_ = 0;
  }
}

void CSpankWeed::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info,
                           EKnockBackType type, bool inDeferred, float magnitude) {
  if (!x57c_canKnockBack)
    return;
  CPatterned::KnockBack(backVec, mgr, info, type, inDeferred, magnitude);
  x57c_canKnockBack = false;
}

float CSpankWeed::GetPlayerDistance(CStateManager& mgr) const {
  return (mgr.GetPlayer().GetTranslation() - x5a8_lockonTarget).magSquared();
}
} // namespace metaforce::MP1
