#include "Runtime/Character/CBoneTracking.hpp"

#include "Runtime/CStateManager.hpp"
#include "Runtime/Character/CAnimData.hpp"
#include "Runtime/Character/CBodyController.hpp"
#include "Runtime/Character/CHierarchyPoseBuilder.hpp"
#include "Runtime/World/CPatterned.hpp"

#include "TCastTo.hpp" // Generated file, do not modify include path

namespace metaforce {

CBoneTracking::CBoneTracking(const CAnimData& animData, std::string_view bone,
                             float maxTrackingAngle, float angSpeed, EBoneTrackingFlags flags)
: x14_segId(animData.GetCharLayoutInfo().GetSegIdFromString(bone))
, x1c_maxTrackingAngle(maxTrackingAngle)
, x20_angSpeed(angSpeed)
, x36_26_noParent(True(flags & EBoneTrackingFlags::NoParent))
, x36_27_noParentOrigin(True(flags & EBoneTrackingFlags::NoParentOrigin))
, x36_28_noHorizontalAim(True(flags & EBoneTrackingFlags::NoHorizontalAim))
, x36_29_parentIk(True(flags & EBoneTrackingFlags::ParentIk)) {}

void CBoneTracking::Update(float dt) { x18_time += dt; }

void CBoneTracking::PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& xf,
                              const zeus::CVector3f& vec, const CBodyController& bodyController) {
  TCastToPtr<CPatterned> patterned = bodyController.GetOwner();

  PreRender(mgr, animData, xf, vec,
            (bodyController.GetBodyStateInfo().ApplyHeadTracking() && patterned && patterned->ApplyBoneTracking()));
}

void CBoneTracking::PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& worldXf,
                              const zeus::CVector3f& localOffsetScale, bool tracking) {
  if (x14_segId == 0)
    return;
  CHierarchyPoseBuilder& pb = animData.PoseBuilder();
  TCastToConstPtr<CActor> targetAct = mgr.GetObjectById(x34_target);
  if (x36_24_active && tracking && (targetAct || x24_targetPosition)) {
    x36_25_hasTrackedRotation = true;
    const auto& layoutInfo = pb.CharLayoutInfo();
    CSegId bone;
    if (x36_26_noParent)
      bone = x14_segId;
    else
      bone = layoutInfo->GetRootNode()->GetBoneMap()[x14_segId].x0_parentId;
    zeus::CTransform parentBoneXf;
    pb.BuildTransform(bone, parentBoneXf);
    zeus::CVector3f pos = parentBoneXf.origin;
    if (x36_27_noParentOrigin && !x36_26_noParent) {
      zeus::CTransform thisBoneXf;
      pb.BuildTransform(x14_segId, thisBoneXf);
      pos = thisBoneXf.origin;
    }
    parentBoneXf.origin = pos * localOffsetScale;
    zeus::CTransform finalXf = worldXf * parentBoneXf;
    zeus::CVector3f localDir = finalXf.transposeRotate(
      (targetAct ? targetAct->GetAimPosition(mgr, 0.f) : *x24_targetPosition) - finalXf.origin).normalized();
    if (x36_28_noHorizontalAim)
      localDir = zeus::CVector3f(0.f, localDir.toVec2f().magnitude(), localDir.z());
    if (x36_29_parentIk) {
      float negElev = -parentBoneXf.basis[1].z();
      zeus::CVector3f ikBase(0.f, std::sqrt(1.f - negElev * negElev), negElev);
      float angle = zeus::CVector3f::getAngleDiff(ikBase, localDir);
      angle = std::min(angle, x1c_maxTrackingAngle);
      localDir = zeus::CVector3f::slerp(ikBase, localDir, angle);
    } else {
      float angle = zeus::CVector3f::getAngleDiff(zeus::skForward, localDir);
      angle = std::min(angle, x1c_maxTrackingAngle);
      localDir = zeus::CVector3f::slerp(zeus::skForward, localDir, angle);
    }
    float angle = zeus::CVector3f::getAngleDiff(x0_curRotation.transform(zeus::skForward), localDir);
    float clampedAngle = std::min(angle, x18_time * x20_angSpeed);
    if (clampedAngle > 1.0e-05f) {
      x0_curRotation = zeus::CQuaternion::slerpShort(x0_curRotation,
      zeus::CQuaternion::lookAt(zeus::skForward, zeus::CUnitVector3f(localDir), 2.f * M_PIF),
      clampedAngle / angle);
    }
    pb.GetTreeMap()[x14_segId].x4_rotation = x0_curRotation;
    animData.MarkPoseDirty();
  } else if (x36_25_hasTrackedRotation) {
    zeus::CQuaternion qb = pb.GetTreeMap()[x14_segId].x4_rotation;
    float angle = zeus::CVector3f::getAngleDiff(x0_curRotation.transform(zeus::skForward),
                                                qb.transform(zeus::skForward));
    float maxAngDelta = x18_time * x20_angSpeed;
    float clampedAngle = std::min(angle, maxAngDelta);
    if (clampedAngle > 0.5f * maxAngDelta) {
      x0_curRotation = zeus::CQuaternion::slerpShort(x0_curRotation, qb, clampedAngle / angle);
      pb.GetTreeMap()[x14_segId].x4_rotation = x0_curRotation;
      animData.MarkPoseDirty();
    } else {
      x36_25_hasTrackedRotation = false;
      x0_curRotation = qb;
    }
  } else {
    x0_curRotation = pb.GetTreeMap()[x14_segId].x4_rotation;
  }
  x18_time = 0.f;
}

void CBoneTracking::SetActive(bool active) { x36_24_active = active; }

void CBoneTracking::SetTarget(TUniqueId target) { x34_target = target; }

void CBoneTracking::UnsetTarget() { x34_target = kInvalidUniqueId; }

void CBoneTracking::SetTargetPosition(const zeus::CVector3f& targetPos) { x24_targetPosition = targetPos; }

void CBoneTracking::SetNoHorizontalAim(bool b) { x36_28_noHorizontalAim = b; }
} // namespace metaforce
