#pragma once

#include <vector>

#include "Runtime/RetroTypes.hpp"
#include "Runtime/Character/CSegId.hpp"

#include <zeus/CAABox.hpp>
#include <zeus/CQuaternion.hpp>
#include <zeus/CVector3f.hpp>

namespace metaforce {
class CCharLayoutInfo;
class CHierarchyPoseBuilder;
class CModelData;
class CStateManager;

class CRagDoll {
protected:
  class CRagDollParticle {
    friend class CRagDoll;
    CSegId x0_id;
    zeus::CVector3f x4_curPos;
    float x10_radius;
    zeus::CVector3f x14_prevPos;
    zeus::CVector3f x20_velocity;
    zeus::CVector3f x2c_impactResponseDelta;
    float x38_impactFrameVel = 0.f;
    bool x3c_24_impactPending : 1 = false;
    bool x3c_25_ : 1 = false;

  public:
    CRagDollParticle(CSegId id, const zeus::CVector3f& curPos, float radius, const zeus::CVector3f& prevPos)
    : x0_id(id), x4_curPos(curPos), x10_radius(radius), x14_prevPos(prevPos) {}
    CSegId GetBone() const { return x0_id; }
    const zeus::CVector3f& GetPosition() const { return x4_curPos; }
    zeus::CVector3f& Position() { return x4_curPos; }
    const zeus::CVector3f& GetVelocity() const { return x20_velocity; }
    zeus::CVector3f& Velocity() { return x20_velocity; }
    float GetRadius() const { return x10_radius; }
  };
  class CRagDollLengthConstraint {
    friend class CRagDoll;
    CRagDollParticle* x0_p1;
    CRagDollParticle* x4_p2;
    float x8_length;
    int xc_ineqType;

  public:
    CRagDollLengthConstraint(CRagDollParticle* p1, CRagDollParticle* p2, float f1, int i1)
    : x0_p1(p1), x4_p2(p2), x8_length(f1), xc_ineqType(i1) {}
    void Update();
    float GetLength() const { return x8_length; }
  };
  class CRagDollJointConstraint {
    friend class CRagDoll;
    CRagDollParticle* x0_p1;  // Shoulder plane 0
    CRagDollParticle* x4_p2;  // Shoulder plane 1
    CRagDollParticle* x8_p3;  // Shoulder plane 2
    CRagDollParticle* xc_p4;  // Shoulder
    CRagDollParticle* x10_p5; // Elbow
    CRagDollParticle* x14_p6; // Wrist
  public:
    CRagDollJointConstraint(CRagDollParticle* p1, CRagDollParticle* p2, CRagDollParticle* p3, CRagDollParticle* p4,
                            CRagDollParticle* p5, CRagDollParticle* p6)
    : x0_p1(p1), x4_p2(p2), x8_p3(p3), xc_p4(p4), x10_p5(p5), x14_p6(p6) {}
    void Update();
  };
  class CRagDollPlaneConstraint {
    friend class CRagDoll;
    CRagDollParticle* x0_p1;
    CRagDollParticle* x4_p2;
    CRagDollParticle* x8_p3;
    CRagDollParticle* xc_p4;
    CRagDollParticle* x10_p5;

  public:
    CRagDollPlaneConstraint(CRagDollParticle* p1, CRagDollParticle* p2, CRagDollParticle* p3, CRagDollParticle* p4,
                            CRagDollParticle* p5)
    : x0_p1(p1), x4_p2(p2), x8_p3(p3), xc_p4(p4), x10_p5(p5) {}
    void Update();
  };
  std::vector<CRagDollParticle> x4_particles;
  std::vector<CRagDollLengthConstraint> x14_lengthConstraints;
  std::vector<CRagDollJointConstraint> x24_jointConstraints;
  std::vector<CRagDollPlaneConstraint> x34_planeConstraints;
  float x44_normalGravity;
  float x48_floatingGravity;
  u32 x4c_impactCount = 0;
  float x50_overTimer;
  float x54_impactVel = 0.f;
  zeus::CVector3f x58_averageVel;
  float x64_angTimer = 0.f;
  bool x68_24_prevMovingSlowly : 1 = false;
  bool x68_25_over : 1 = false;
  bool x68_26_primed : 1 = false;
  bool x68_27_continueSmallMovements : 1;
  bool x68_28_noOverTimer : 1;
  bool x68_29_noAiCollision : 1;
  void AccumulateForces(float dt, float waterTop);
  void SetNumParticles(int num) { x4_particles.reserve(num); }
  void AddParticle(CSegId id, const zeus::CVector3f& prevPos, const zeus::CVector3f& curPos, float radius);
  void SetNumLengthConstraints(int num) { x14_lengthConstraints.reserve(num); }
  void AddLengthConstraint(int i1, int i2);
  void AddMaxLengthConstraint(int i1, int i2, float length);
  void AddMinLengthConstraint(int i1, int i2, float length);
  void SetNumJointConstraints(int num) { x24_jointConstraints.reserve(num); }
  void AddJointConstraint(int i1, int i2, int i3, int i4, int i5, int i6);
  zeus::CQuaternion BoneAlign(CHierarchyPoseBuilder& pb, const CCharLayoutInfo& charInfo, int i1, int i2,
                              const zeus::CQuaternion& q);
  void CheckStatic(float dt);
  void ClearForces();
  void SatisfyConstraints(CStateManager& mgr);
  bool SatisfyWorldConstraints(CStateManager& mgr, int pass);
  void SatisfyWorldConstraintsOnConstruction(CStateManager& mgr);
  void Verlet(float dt);

public:
  virtual ~CRagDoll() = default;
  CRagDoll(float normalGravity, float floatingGravity, float overTime, u32 flags);

  virtual void PreRender(const zeus::CVector3f& v, CModelData& mData);
  virtual void Update(CStateManager& mgr, float dt, float waterTop);
  virtual void Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData);

  zeus::CAABox CalculateRenderBounds() const;
  bool IsPrimed() const { return x68_26_primed; }
  bool WillContinueSmallMovements() const { return x68_27_continueSmallMovements; }
  bool IsOver() const { return x68_25_over; }
  void SetNoOverTimer(bool b) { x68_28_noOverTimer = b; }
  void SetContinueSmallMovements(bool b) { x68_27_continueSmallMovements = b; }
  u32 GetImpactCount() const { return x4c_impactCount; }
};

} // namespace metaforce