#pragma once

#include <array>

#include "Runtime/RetroTypes.hpp"
#include "Runtime/Collision/CCollidableSphere.hpp"
#include "Runtime/Collision/CCollisionInfoList.hpp"
#include "Runtime/Graphics/CRainSplashGenerator.hpp"
#include "Runtime/Particle/CElementGen.hpp"
#include "Runtime/Particle/CParticleSwoosh.hpp"
#include "Runtime/World/CActor.hpp"
#include "Runtime/World/CMorphBallShadow.hpp"
#include "Runtime/World/CWorldShadow.hpp"
#include "Runtime/World/ScriptObjectSupport.hpp"

#include <zeus/CTransform.hpp>
#include <zeus/CVector2f.hpp>
#include <zeus/CVector3f.hpp>

namespace metaforce {
class CActorLights;
class CDamageInfo;
class CPlayer;
class CScriptWater;
class CStateManager;

struct CFinalInput;

class CMorphBall {
public:
  enum class EBallBoostState { BoostAvailable, BoostDisabled };

  enum class ESpiderBallState { Inactive, Active };

  enum class EBombJumpState { BombJumpAvailable, BombJumpDisabled };

private:
  struct CSpiderBallElectricityManager {
    u32 x0_effectIdx;
    u32 x4_lifetime;
    u32 x8_curFrame = 0;
    CSpiderBallElectricityManager(u32 effectIdx, u32 lifetime) : x0_effectIdx(effectIdx), x4_lifetime(lifetime) {}
  };
  CPlayer& x0_player;
  s32 x4_loadedModelId = -1;
  u32 x8_ballGlowColorIdx = 0;
  float xc_radius;
  zeus::CVector3f x10_boostControlForce;
  zeus::CVector3f x1c_controlForce;
  bool x28_tireMode = false;
  float x2c_tireLeanAngle = 0.f;
  float x30_ballTiltAngle = 0.f;
  CCollidableSphere x38_collisionSphere;
  std::unique_ptr<CModelData> x58_ballModel;
  u32 x5c_ballModelShader = 0;
  std::unique_ptr<CModelData> x60_spiderBallGlassModel;
  u32 x64_spiderBallGlassModelShader = 0;
  std::unique_ptr<CModelData> x68_lowPolyBallModel;
  u32 x6c_lowPolyBallModelShader = 0;
  std::unique_ptr<CModelData> x70_frozenBallModel;
  CCollisionInfoList x74_collisionInfos;
  u32 xc78_ = 0;
  ESpiderBallState x187c_spiderBallState = ESpiderBallState::Inactive;
  zeus::CVector3f x1880_playerToSpiderNormal;
  float x188c_spiderPullMovement = 1.f;
  zeus::CVector3f x1890_spiderTrackPoint;
  zeus::CVector3f x189c_spiderInterpBetweenPoints;
  zeus::CVector3f x18a8_spiderBetweenPoints;
  float x18b4_linVelDamp = 0.f;
  float x18b8_angVelDamp = 0.f;
  bool x18bc_spiderNearby = false;
  bool x18bd_touchingSpider = false;
  bool x18be_spiderBallSwinging = false;
  bool x18bf_spiderSwingInAir = true;
  bool x18c0_isSpiderSurface = false;
  zeus::CTransform x18c4_spiderSurfaceTransform;
  float x18f4_spiderSurfacePivotAngle = 0.f;
  float x18f8_spiderSurfacePivotTargetAngle = 0.f;
  float x18fc_refPullVel = 0.f;
  float x1900_playerToSpiderTrackDist = 0.f;
  float x1904_swingControlDir = 0.f;
  float x1908_swingControlTime = 0.f;
  zeus::CVector2f x190c_normSpiderSurfaceForces;
  float x1914_spiderTrackForceMag = 0.f;
  float x1918_spiderViewControlMag = 0.f;
  float x191c_damageTimer = 0.f;
  bool x1920_spiderForcesReset = false;
  zeus::CTransform x1924_surfaceToWorld;
  bool x1954_isProjectile = false;
  std::vector<CToken> x1958_animationTokens;
  TToken<CSwooshDescription> x1968_slowBlueTailSwoosh;
  TToken<CSwooshDescription> x1970_slowBlueTailSwoosh2;
  TToken<CSwooshDescription> x1978_jaggyTrail;
  TToken<CGenDescription> x1980_wallSpark;
  TToken<CGenDescription> x1988_ballInnerGlow;
  TToken<CGenDescription> x1990_spiderBallMagnetEffect;
  TToken<CGenDescription> x1998_boostBallGlow;
  TToken<CSwooshDescription> x19a0_spiderElectric;
  TToken<CGenDescription> x19a8_morphBallTransitionFlash;
  TToken<CGenDescription> x19b0_effect_morphBallIceBreak;
  std::unique_ptr<CParticleSwoosh> x19b8_slowBlueTailSwooshGen;
  std::unique_ptr<CParticleSwoosh> x19bc_slowBlueTailSwooshGen2;
  std::unique_ptr<CParticleSwoosh> x19c0_slowBlueTailSwoosh2Gen;
  std::unique_ptr<CParticleSwoosh> x19c4_slowBlueTailSwoosh2Gen2;
  std::unique_ptr<CParticleSwoosh> x19c8_jaggyTrailGen;
  std::unique_ptr<CElementGen> x19cc_wallSparkGen;
  std::unique_ptr<CElementGen> x19d0_ballInnerGlowGen;
  std::unique_ptr<CElementGen> x19d4_spiderBallMagnetEffectGen;
  std::unique_ptr<CElementGen> x19d8_boostBallGlowGen;
  std::unique_ptr<CElementGen> x19dc_morphBallTransitionFlashGen;
  std::unique_ptr<CElementGen> x19e0_effect_morphBallIceBreakGen;
  rstl::reserved_vector<std::pair<std::unique_ptr<CParticleSwoosh>, bool>, 32> x19e4_spiderElectricGens;
  std::list<CSpiderBallElectricityManager> x1b6c_activeSpiderElectricList;
  CRandom16 x1b80_rand{99};
  rstl::reserved_vector<TToken<CGenDescription>, 8> x1b84_wakeEffects;
  rstl::reserved_vector<std::unique_ptr<CElementGen>, 8> x1bc8_wakeEffectGens;
  s32 x1c0c_wakeEffectIdx = -1;
  TUniqueId x1c10_ballInnerGlowLight = kInvalidUniqueId;
  std::unique_ptr<CWorldShadow> x1c14_worldShadow;
  std::unique_ptr<CActorLights> x1c18_actorLights;
  std::unique_ptr<CRainSplashGenerator> x1c1c_rainSplashGen;
  float x1c20_tireFactor = 0.f;
  float x1c24_maxTireFactor = 0.5f;
  float x1c28_tireInterpSpeed = 1.f;
  bool x1c2c_tireInterpolating = false;
  float x1c30_boostOverLightFactor = 0.f;
  float x1c34_boostLightFactor = 0.f;
  float x1c38_spiderLightFactor = 0.f;
  TReservedAverage<zeus::CQuaternion, 5> x1c3c_ballOrientAvg = {{}};
  TReservedAverage<zeus::CVector3f, 5> x1c90_ballPosAvg = {{}};
  TReservedAverage<float, 15> x1cd0_liftSpeedAvg = {{}};
  TReservedAverage<zeus::CVector3f, 15> x1d10_liftControlForceAvg = {{}};
  u32 x1dc8_failsafeCounter = 0;
  zeus::CVector3f x1dcc_;
  zeus::CVector3f x1dd8_;
  bool x1de4_24_inBoost : 1 = false;
  bool x1de4_25_boostEnabled : 1 = true;
  float x1de8_boostChargeTime = 0.f;
  float x1dec_timeNotInBoost = 0.f;
  float x1df0_ = 0.f;
  float x1df4_boostDrainTime = 0.f;
  bool x1df8_24_inHalfPipeMode : 1 = false;
  bool x1df8_25_inHalfPipeModeInAir : 1 = false;
  bool x1df8_26_touchedHalfPipeRecently : 1 = false;
  bool x1df8_27_ballCloseToCollision : 1 = false;
  float x1dfc_touchHalfPipeCooldown = 0.f;
  float x1e00_disableControlCooldown = 0.f;
  float x1e04_touchHalfPipeRecentCooldown = 0.f;
  zeus::CVector3f x1e08_prevHalfPipeNormal;
  zeus::CVector3f x1e14_halfPipeNormal;
  u32 x1e20_ballAnimIdx = 0;
  CSfxHandle x1e24_boostSfxHandle;
  CSfxHandle x1e28_wallHitSfxHandle;
  CSfxHandle x1e2c_rollSfxHandle;
  CSfxHandle x1e30_spiderSfxHandle;
  u16 x1e34_rollSfx = 0xffff;
  u16 x1e36_landSfx = 0xffff;
  u32 x1e38_wallSparkFrameCountdown = 0;
  EBallBoostState x1e3c_boostState = EBallBoostState::BoostAvailable;
  EBombJumpState x1e40_bombJumpState = EBombJumpState::BombJumpAvailable;
  float x1e44_damageEffect = 0.f;
  float x1e48_damageEffectDecaySpeed = 0.f;
  float x1e4c_damageTime = 0.f;
  std::unique_ptr<CMorphBallShadow> x1e50_shadow;
  void LoadAnimationTokens(std::string_view ancsName);
  void InitializeWakeEffects();
  static std::unique_ptr<CModelData> GetMorphBallModel(const char* name, float radius);
  void SelectMorphBallSounds(const CMaterialList& mat);
  void UpdateMorphBallSounds(float dt);
  static zeus::CVector3f TransformSpiderBallForcesXY(const zeus::CVector2f& forces, CStateManager& mgr);
  static zeus::CVector3f TransformSpiderBallForcesXZ(const zeus::CVector2f& forces, CStateManager& mgr);
  void ResetSpiderBallForces();
  static void PointGenerator(void* ctx, const std::vector<std::pair<zeus::CVector3f, zeus::CVector3f>>& vn);

public:
  CMorphBall(CPlayer& player, float radius);
  void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr);
  const CCollidableSphere& GetCollidableSphere() const { return x38_collisionSphere; }
  bool IsProjectile() const { return x1954_isProjectile; }
  void GetBallContactMaterials() const {}
  void GetWallBumpCounter() const {}
  void GetBoostChargeTimer() const {}
  bool IsBoosting() const { return x1de4_24_inBoost; }
  float GetBallRadius() const;
  float GetBallTouchRadius() const;
  float ForwardInput(const CFinalInput& input) const;
  float BallTurnInput(const CFinalInput& input) const;
  void ComputeBallMovement(const CFinalInput& input, CStateManager& mgr, float dt);
  bool IsMovementAllowed() const;
  void UpdateSpiderBall(const CFinalInput& input, CStateManager& mgr, float dt);
  void ApplySpiderBallSwingingForces(const CFinalInput& input, CStateManager& mgr, float dt);
  void ApplySpiderBallRollForces(const CFinalInput& input, CStateManager& mgr, float dt);
  zeus::CVector2f CalculateSpiderBallAttractionSurfaceForces(const CFinalInput& input) const;
  bool CheckForSwitchToSpiderBallSwinging(CStateManager& mgr) const;
  bool FindClosestSpiderBallWaypoint(CStateManager& mgr, const zeus::CVector3f& ballCenter,
                                     zeus::CVector3f& closestPoint, zeus::CVector3f& interpDeltaBetweenPoints,
                                     zeus::CVector3f& deltaBetweenPoints, float& distance, zeus::CVector3f& normal,
                                     bool& isSurface, zeus::CTransform& surfaceTransform) const;
  void SetSpiderBallSwingingState(bool active);
  float GetSpiderBallControllerMovement(const CFinalInput& input) const;
  void ResetSpiderBallSwingControllerMovementTimer();
  void UpdateSpiderBallSwingControllerMovementTimer(float movement, float dt);
  float GetSpiderBallSwingControllerMovementScalar() const;
  void CreateSpiderBallParticles(const zeus::CVector3f& ballPos, const zeus::CVector3f& trackPoint);
  void ComputeMarioMovement(const CFinalInput& input, CStateManager& mgr, float dt);
  void SetSpiderBallState(ESpiderBallState state) { x187c_spiderBallState = state; }
  zeus::CTransform GetSwooshToWorld() const;
  zeus::CTransform GetBallToWorld() const;
  zeus::CTransform CalculateSurfaceToWorld(const zeus::CVector3f& trackNormal, const zeus::CVector3f& trackPoint,
                                           const zeus::CVector3f& ballDir) const;
  bool CalculateBallContactInfo(zeus::CVector3f& normal, zeus::CVector3f& point) const;
  void UpdateBallDynamics(CStateManager& mgr, float dt);
  void SwitchToMarble();
  void SwitchToTire();
  void Update(float dt, CStateManager& mgr);
  void DeleteLight(CStateManager& mgr);
  void SetBallLightActive(CStateManager& mgr, bool active);
  void EnterMorphBallState(CStateManager& mgr);
  void LeaveMorphBallState(CStateManager& mgr);
  void UpdateEffects(float dt, CStateManager& mgr);
  void ComputeBoostBallMovement(const CFinalInput& input, CStateManager& mgr, float dt);
  void EnterBoosting(CStateManager& mgr);
  void LeaveBoosting();
  void CancelBoosting();
  bool UpdateMarbleDynamics(CStateManager& mgr, float dt, const zeus::CVector3f& point);
  void ApplyFriction(float);
  void DampLinearAndAngularVelocities(float linDamp, float angDamp);
  float GetMinimumAlignmentSpeed() const;
  void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum);
  void Render(const CStateManager& mgr, const CActorLights* lights) const;
  void ResetMorphBallTransitionFlash();
  void UpdateMorphBallTransitionFlash(float dt);
  void RenderMorphBallTransitionFlash(const CStateManager&) const;
  void UpdateIceBreakEffect(float dt);
  void RenderIceBreakEffect(const CStateManager& mgr) const;
  bool IsMorphBallTransitionFlashValid() const { return x19dc_morphBallTransitionFlashGen != nullptr; }
  void RenderDamageEffects(const CStateManager& mgr, const zeus::CTransform& xf) const;
  void UpdateHalfPipeStatus(CStateManager& mgr, float dt);
  bool GetIsInHalfPipeMode() const { return x1df8_24_inHalfPipeMode; }
  void SetIsInHalfPipeMode(bool b) { x1df8_24_inHalfPipeMode = b; }
  bool GetIsInHalfPipeModeInAir() const { return x1df8_25_inHalfPipeModeInAir; }
  void SetIsInHalfPipeModeInAir(bool b) { x1df8_25_inHalfPipeModeInAir = b; }
  bool GetTouchedHalfPipeRecently() const { return x1df8_26_touchedHalfPipeRecently; }
  void SetTouchedHalfPipeRecently(bool b) { x1df8_26_touchedHalfPipeRecently = b; }
  void DisableHalfPipeStatus();
  bool BallCloseToCollision(const CStateManager& mgr, float dist, const CMaterialFilter& filter) const;
  void CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr);
  bool IsInFrustum(const zeus::CFrustum& frustum) const;
  void ComputeLiftForces(const zeus::CVector3f& controlForce, const zeus::CVector3f& velocity,
                         const CStateManager& mgr);
  float CalculateSurfaceFriction() const;
  void ApplyGravity(const CStateManager& mgr);
  void SpinToSpeed(float holdMag, const zeus::CVector3f& torque, float mag);
  float ComputeMaxSpeed() const;
  void Touch(CActor& actor, CStateManager& mgr);
  bool IsClimbable(const CCollisionInfo& cinfo) const;
  void FluidFXThink(CActor::EFluidState state, CScriptWater& water, CStateManager& mgr);
  void LoadMorphBallModel(CStateManager& mgr);
  void AddSpiderBallElectricalEffect();
  void UpdateSpiderBallElectricalEffects();
  void RenderSpiderBallElectricalEffect() const;
  void RenderEnergyDrainEffects(const CStateManager& mgr) const;
  void TouchModel(const CStateManager& mgr) const;
  void SetAsProjectile() { x1954_isProjectile = true; }
  EBallBoostState GetBallBoostState() const { return x1e3c_boostState; }
  void SetBallBoostState(EBallBoostState state) { x1e3c_boostState = state; }
  EBombJumpState GetBombJumpState() const { return x1e40_bombJumpState; }
  void SetBombJumpState(EBombJumpState state) { x1e40_bombJumpState = state; }
  void TakeDamage(float dam);
  void DrawBallShadow(const CStateManager& mgr);
  void DeleteBallShadow();
  void CreateBallShadow();
  void RenderToShadowTex(CStateManager& mgr);
  void StartLandingSfx();
  ESpiderBallState GetSpiderBallState() const { return x187c_spiderBallState; }
  void SetDamageTimer(float t) { x191c_damageTimer = t; }
  void Stop();
  void StopSounds();
  void StopEffects();
  CModelData& GetMorphballModelData() const { return *x58_ballModel; }
  u32 GetMorphballModelShader() const { return x5c_ballModelShader; }
  bool GetBoostEnabled() const { return x1de4_25_boostEnabled; }
  void SetBoostEnabed(bool b) { x1de4_25_boostEnabled = b; }
  bool IsInBoost() const { return x1de4_24_inBoost; }
  float GetBoostChargeTime() const { return x1de8_boostChargeTime; }

  // Contains red, green, and blue channel values
  using ColorArray = std::array<u8, 3>;
  static const std::array<ColorArray, 9> BallGlowColors;
  static const std::array<ColorArray, 9> BallTransFlashColors;
  static const std::array<ColorArray, 9> BallAuxGlowColors;
};

} // namespace metaforce