#include "MetroidPrime/CDamageVulnerability.hpp"

#include "Kyoto/Streams/CInputStream.hpp"
#include "rstl/math.hpp"

const CDamageVulnerability CDamageVulnerability::sNormalVulnerability(
    kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal,
    kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kVN_Normal, kDT_None);

const CDamageVulnerability
    CDamageVulnerability::sImmuneVulnerability(kVN_Deflect, kVN_Deflect, kVN_Deflect, kVN_Deflect,
                                               kVN_Deflect, kVN_Deflect, kVN_Deflect, kVN_Deflect,
                                               kVN_Deflect, kVN_Deflect, kVN_Deflect, kVN_Deflect,
                                               kVN_Deflect, kVN_Deflect, kVN_Deflect, kDT_None);

const CDamageVulnerability
    CDamageVulnerability::sReflectVulnerability(kVN_Deflect, kVN_Deflect, kVN_Deflect, kVN_Deflect,
                                                kVN_Deflect, kVN_Deflect, kVN_Deflect, kVN_Deflect,
                                                kVN_Deflect, kVN_Deflect, kVN_Deflect, kVN_Deflect,
                                                kVN_Deflect, kVN_Deflect, kVN_Deflect, kDT_One);

const CDamageVulnerability CDamageVulnerability::sPassThroughVulnerability(
    kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kVN_PassThrough,
    kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kVN_PassThrough,
    kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kVN_PassThrough, kDT_None);

static inline bool is_deflect_direct(EVulnerability vuln) {
  return vuln == kVN_Deflect || (EVulnerability(uint(vuln) - uint(kVN_DirectWeak)) < kVN_Deflect) ||
         vuln == kVN_DirectImmune;
}

static inline bool is_normal_or_weak(EVulnerability vuln) {
  return vuln == kVN_Weak || vuln == kVN_Normal;
}
static inline bool is_not_deflect(EVulnerability vuln) { return vuln != kVN_Deflect; }

CDamageVulnerability::CDamageVulnerability(CInputStream& in) {
  uint propCount = in.ReadLong();
  if (propCount == 11) {
    for (int i = 0; i < 15; ++i) {
      x0_normal[i] = EVulnerability(in.ReadLong());
    }

    x5c_deflect = EDeflectionType(in.ReadLong());
    x3c_charged[0] = x0_normal[0];
    x4c_combo[0] = x0_normal[0];
    x3c_charged[1] = x0_normal[1];
    x4c_combo[1] = x0_normal[1];
    x3c_charged[2] = x0_normal[2];
    x4c_combo[2] = x0_normal[2];
    x3c_charged[3] = x0_normal[3];
    x4c_combo[3] = x0_normal[3];
  } else {
    LoadData(in, propCount);
  }
}

void CDamageVulnerability::LoadData(CInputStream& in, int propCount) {
  propCount -= 3;

  for (int i = 0; i < rstl::min_val(propCount, 15); ++i) {
    x0_normal[i] = EVulnerability(in.ReadLong());
  }

  if (propCount < 15) {
    for (int i = propCount; i < 15; ++i) {
      x0_normal[i] = kVN_Deflect;
    }
  }

  for (int i = 15; i < propCount; ++i) {
    in.ReadLong();
  }

  x5c_deflect = EDeflectionType(in.ReadLong());
  in.ReadLong();
  for (int i = 0; i < 4; ++i) {
    x3c_charged[i] = EVulnerability(in.ReadLong());
  }

  x60_chargedDeflect = EDeflectionType(in.ReadLong());
  in.ReadLong();
  for (int i = 0; i < 4; ++i) {
    x4c_combo[i] = EVulnerability(in.ReadLong());
  }

  x64_comboDeflect = EDeflectionType(in.ReadLong());
}

CDamageVulnerability::CDamageVulnerability(EVulnerability power, EVulnerability ice,
                                           EVulnerability wave, EVulnerability plasma,
                                           EVulnerability bomb, EVulnerability powerBomb,
                                           EVulnerability missile, EVulnerability boostBall,
                                           EVulnerability phazon, EVulnerability enemyWp1,
                                           EVulnerability enemyWp2, EVulnerability enemyWp3,
                                           EVulnerability enemyWp4, EVulnerability v1,
                                           EVulnerability v2, EDeflectionType deflectType) {
  x0_normal[0] = power;
  x0_normal[1] = ice;
  x0_normal[2] = wave;
  x0_normal[3] = plasma;
  x0_normal[4] = bomb;
  x0_normal[5] = powerBomb;
  x0_normal[6] = missile;
  x0_normal[7] = boostBall;
  x0_normal[8] = phazon;
  x0_normal[9] = enemyWp1;
  x0_normal[10] = enemyWp2;
  x0_normal[11] = enemyWp3;
  x0_normal[12] = enemyWp4;
  x0_normal[13] = v1;
  x0_normal[14] = v2;
  x3c_charged[0] = x0_normal[0];
  x4c_combo[0] = x0_normal[0];
  x3c_charged[1] = x0_normal[1];
  x4c_combo[1] = x0_normal[1];
  x3c_charged[2] = x0_normal[2];
  x4c_combo[2] = x0_normal[2];
  x3c_charged[3] = x0_normal[3];
  x4c_combo[3] = x0_normal[3];
  x5c_deflect = deflectType;
}

static inline bool check_hurts(EVulnerability vuln, int direct) {
  return direct == 0
             ? (is_normal_or_weak(vuln) || vuln == kVN_DirectWeak || vuln == kVN_DirectNormal)
             : is_normal_or_weak(vuln);
}

bool CDamageVulnerability::WeaponHurts(const CWeaponMode& mode, int ignoreDirect) const {
  if (mode.GetType() < kWT_Power || mode.GetType() > kWT_OrangePhazon) {
    return false;
  }

  if (mode.IsInstantKill()) {
    return true;
  }

  bool normalHurts = check_hurts(x0_normal[uint(mode.GetType())], ignoreDirect);
  bool chargedHurts = (mode.GetType() < kWT_Bomb)
                          ? check_hurts(x3c_charged[uint(mode.GetType())], ignoreDirect)
                          : true;
  bool comboedHurts = (mode.GetType() < kWT_Bomb)
                          ? check_hurts(x4c_combo[uint(mode.GetType())], ignoreDirect)
                          : true;

  return (normalHurts && !mode.IsCharged() && !mode.IsComboed()) ||
         (chargedHurts && mode.IsCharged()) || (comboedHurts && mode.IsComboed());
}

static inline bool check_hits(EVulnerability vuln, int direct) {
  if (!direct) {
    return is_not_deflect(vuln);
  }
  if (vuln == kVN_Deflect || ((uint)vuln - kVN_DirectWeak <= kVN_Normal) ||
      vuln == kVN_DirectImmune) {
    return false;
  }
  return true;
}

bool CDamageVulnerability::WeaponHits(const CWeaponMode& mode, int checkDirect) const {
  if (mode.GetType() < kWT_Power || mode.GetType() > kWT_OrangePhazon) {
    return false;
  }
  if (mode.IsInstantKill()) {
    return true;
  }

  bool normalHits = check_hits(x0_normal[uint(mode.GetType())], checkDirect);
  bool chargedHits =
      mode.GetType() < kWT_Bomb ? check_hits(x3c_charged[uint(mode.GetType())], checkDirect) : true;
  bool comboedHits =
      mode.GetType() < kWT_Bomb ? check_hits(x4c_combo[uint(mode.GetType())], checkDirect) : true;
  bool result = false;
  if ((normalHits && !mode.IsCharged() && !mode.IsComboed()) || (chargedHits && mode.IsCharged()) ||
      (comboedHits && mode.IsComboed())) {
    result = true;
  }

  return (normalHits && !mode.IsCharged() && !mode.IsComboed()) ||
         (chargedHits && mode.IsCharged()) || (comboedHits && mode.IsComboed());
}

inline EVulnerability direct_to_normal(EVulnerability vuln) {
  if (vuln == kVN_DirectWeak) {
    return kVN_Weak;
  }
  if (vuln == kVN_DirectNormal) {
    return kVN_Normal;
  }
  if (vuln == kVN_DirectImmune) {
    return kVN_Immune;
  }
  return vuln;
}

EVulnerability CDamageVulnerability::GetVulnerability(const CWeaponMode& mode,
                                                      int ignoreDirect) const {
  if (mode.GetType() < kWT_Power || mode.GetType() > kWT_OrangePhazon) {
    return kVN_Deflect;
  }

  if (mode.IsInstantKill()) {
    return kVN_Normal;
  }

  EVulnerability vuln = x0_normal[uint(mode.GetType())];
  EWeaponType type = mode.GetType();
  if (mode.IsCharged()) {
    vuln = type < kWT_Bomb ? x3c_charged[uint(type)] : kVN_Normal;
  }

  if (mode.IsComboed()) {
    vuln = type < kWT_Bomb ? x4c_combo[uint(type)] : kVN_Normal;
  }
  if (ignoreDirect == 1) {
    return vuln;
  }
  return direct_to_normal(vuln);
}

EDeflectionType CDamageVulnerability::GetDeflectionType(const CWeaponMode& mode) const {
  if (mode.IsCharged()) {
    return x60_chargedDeflect;
  }
  if (mode.IsComboed()) {
    return x64_comboDeflect;
  }
  return x5c_deflect;
}
