#include "Runtime/World/CDamageVulnerability.hpp"

namespace urde {

const CDamageVulnerability CDamageVulnerability::sNormalVulnerability(
    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,
    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,
    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,
    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,
    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal,
    EVulnerability::Normal, EVulnerability::Normal, EVulnerability::Normal, EDeflectType::None);

const CDamageVulnerability CDamageVulnerability::sImmuneVulnerability(
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None);

const CDamageVulnerability CDamageVulnerability::sReflectVulnerability(
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect,
    EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::One);

const CDamageVulnerability CDamageVulnerability::sPassThroughVulnerability(
    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,
    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,
    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,
    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,
    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough,
    EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EDeflectType::None);

static inline bool is_not_immune(EVulnerability vuln) {
  return vuln != EVulnerability::Immune && vuln != EVulnerability::DirectImmune;
}

static inline bool is_normal_or_weak(EVulnerability vuln) {
  return vuln == EVulnerability::Normal || vuln == EVulnerability::Weak;
}

void CDamageVulnerability::ConstructNew(CInputStream& in, int propCount) {
  propCount -= 3;
  EVulnerability* vulns = &x0_power;
  for (int i = 0; i < std::min(propCount, 15); ++i)
    vulns[i] = EVulnerability(in.readUint32Big());

  if (propCount < 15) {
    for (int i = propCount; i < 15; ++i)
      vulns[i] = EVulnerability::Deflect;
  }

  for (int i = 15; i < propCount; ++i)
    in.readUint32Big();

  x5c_deflected = EDeflectType(in.readUint32Big());
  EVulnerability* vulns2 = &x3c_chargedPower;
  in.readUint32Big();
  for (int i = 0; i < 4; ++i)
    vulns2[i] = EVulnerability(in.readUint32Big());

  x60_chargedDeflected = EDeflectType(in.readUint32Big());
  EVulnerability* vulns3 = &x4c_superMissile;
  in.readUint32Big();
  for (int i = 0; i < 4; ++i)
    vulns3[i] = EVulnerability(in.readUint32Big());

  x64_comboDeflected = EDeflectType(in.readUint32Big());
}

CDamageVulnerability::CDamageVulnerability(CInputStream& in) {
  u32 propCount = in.readUint32Big();
  if (propCount == 11) {
    EVulnerability* vulns = &x0_power;
    for (int i = 0; i < 15; ++i)
      vulns[i] = EVulnerability(in.readUint32Big());

    if (propCount == 15)
      x5c_deflected = EDeflectType::None;
    else
      x5c_deflected = EDeflectType(in.readUint32Big());

    x3c_chargedPower = x0_power;
    x4c_superMissile = x0_power;
    x40_chargedIce = x4_ice;
    x50_iceSpreader = x4_ice;
    x44_chargedWave = x8_wave;
    x54_wavebuster = x8_wave;
    x48_chargedPlasma = xc_plasma;
    x58_flamethrower = xc_plasma;
  } else
    ConstructNew(in, propCount);
}

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,
                                           EDeflectType deflectType)
: x0_power(power)
, x4_ice(ice)
, x8_wave(wave)
, xc_plasma(plasma)
, x10_bomb(bomb)
, x14_powerbomb(powerBomb)
, x18_missile(missile)
, x1c_boostBall(boostBall)
, x20_phazon(phazon)
, x24_enemyWp1(enemyWp1)
, x28_enemyWp2Poison(enemyWp2)
, x2c_enemyWp3Lava(enemyWp3)
, x30_enemyWp4(enemyWp4)
, x34_unk1(v1)
, x38_unk2(v2)
, x3c_chargedPower(x0_power)
, x40_chargedIce(x4_ice)
, x44_chargedWave(x8_wave)
, x48_chargedPlasma(xc_plasma)
, x4c_superMissile(x0_power)
, x50_iceSpreader(x4_ice)
, x54_wavebuster(x8_wave)
, x58_flamethrower(xc_plasma)
, x5c_deflected(deflectType) {}

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, EVulnerability chargedPower, EVulnerability chargedIce,
    EVulnerability chargedWave, EVulnerability chargedPlasma, EVulnerability superMissile, EVulnerability iceSpreader,
    EVulnerability waveBuster, EVulnerability flameThrower, EDeflectType deflected)
: x0_power(power)
, x4_ice(ice)
, x8_wave(wave)
, xc_plasma(plasma)
, x10_bomb(bomb)
, x14_powerbomb(powerBomb)
, x18_missile(missile)
, x1c_boostBall(boostBall)
, x20_phazon(phazon)
, x24_enemyWp1(enemyWp1)
, x28_enemyWp2Poison(enemyWp2)
, x2c_enemyWp3Lava(enemyWp3)
, x30_enemyWp4(enemyWp4)
, x34_unk1(v1)
, x38_unk2(v2)
, x3c_chargedPower(chargedPower)
, x40_chargedIce(chargedIce)
, x44_chargedWave(chargedWave)
, x48_chargedPlasma(chargedPlasma)
, x4c_superMissile(superMissile)
, x50_iceSpreader(iceSpreader)
, x54_wavebuster(waveBuster)
, x58_flamethrower(flameThrower)
, x5c_deflected(deflected) {}

EDeflectType CDamageVulnerability::GetDeflectionType(const CWeaponMode& mode) const {
  if (mode.IsCharged())
    return x60_chargedDeflected;
  if (mode.IsComboed())
    return x64_comboDeflected;
  return x5c_deflected;
}

bool CDamageVulnerability::WeaponHurts(const CWeaponMode& mode, bool ignoreDirect) const {
  if (mode.GetType() == EWeaponType::None || mode.GetType() > EWeaponType::OrangePhazon)
    return false;
  if (mode.IsInstantKill())
    return true;

  EVulnerability normalVuln = (&x0_power)[u32(mode.GetType())];
  bool normalHurts = true;
  if (ignoreDirect) {
    normalHurts = is_normal_or_weak(normalVuln);
  } else {
    if (!is_normal_or_weak(normalVuln) &&
        (normalVuln != EVulnerability::DirectWeak && normalVuln != EVulnerability::DirectNormal))
      normalHurts = false;
  }

  bool chargedHurts = true;
  bool comboedHurts = true;
  if (mode.GetType() < EWeaponType::Bomb) {
    EVulnerability chargedVuln = (&x3c_chargedPower)[u32(mode.GetType())];
    EVulnerability comboedVuln = (&x4c_superMissile)[u32(mode.GetType())];
    if (ignoreDirect) {
      chargedHurts = is_normal_or_weak(chargedVuln);
      comboedHurts = is_normal_or_weak(comboedVuln);
    } else {
      if (!is_normal_or_weak(chargedVuln) &&
          (chargedVuln != EVulnerability::DirectWeak && chargedVuln != EVulnerability::DirectNormal))
        chargedHurts = false;
      if (!is_normal_or_weak(comboedVuln) &&
          (comboedVuln != EVulnerability::DirectWeak && comboedVuln != EVulnerability::DirectNormal))
        comboedHurts = false;
    }
  }

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

bool CDamageVulnerability::WeaponHits(const CWeaponMode& mode, bool checkDirect) const {
  if (mode.GetType() == EWeaponType::None || mode.GetType() > EWeaponType::OrangePhazon)
    return false;
  if (mode.IsInstantKill())
    return true;

  bool normalVuln;
  if (!checkDirect)
    normalVuln = (&x0_power)[u32(mode.GetType())] != EVulnerability::Immune;
  else
    normalVuln = is_not_immune((&x0_power)[u32(mode.GetType())]);

  bool chargedVuln = true;
  bool comboedVuln = true;

  if (mode.GetType() < EWeaponType::Bomb) {
    if (!checkDirect) {
      chargedVuln = (&x3c_chargedPower)[u32(mode.GetType())] != EVulnerability::Immune;
      comboedVuln = (&x4c_superMissile)[u32(mode.GetType())] != EVulnerability::Immune;
    } else {
      chargedVuln = is_not_immune((&x3c_chargedPower)[u32(mode.GetType())]);
      comboedVuln = is_not_immune((&x4c_superMissile)[u32(mode.GetType())]);
    }
  }

  if (normalVuln && !mode.IsCharged() && !mode.IsComboed())
    return true;
  if (chargedVuln && mode.IsCharged())
    return true;
  return comboedVuln && mode.IsComboed();
}

EVulnerability CDamageVulnerability::GetVulnerability(const CWeaponMode& mode, bool ignoreDirect) const {
  if (mode.GetType() == EWeaponType::None || mode.GetType() > EWeaponType::OrangePhazon)
    return EVulnerability::Deflect;

  if (mode.IsInstantKill())
    return EVulnerability::Normal;

  EVulnerability vuln = (&x0_power)[u32(mode.GetType())];
  if (mode.IsCharged()) {
    if (mode.GetType() < EWeaponType::Bomb)
      vuln = (&x3c_chargedPower)[u32(mode.GetType())];
    else
      vuln = EVulnerability::Normal;
  }

  if (mode.IsComboed()) {
    if (mode.GetType() < EWeaponType::Bomb)
      vuln = (&x3c_chargedPower)[u32(mode.GetType())];
    else
      vuln = EVulnerability::Normal;
  }

  if (ignoreDirect)
    return vuln;

  if (vuln == EVulnerability::DirectWeak)
    return EVulnerability::Weak;
  else if (vuln == EVulnerability::DirectNormal)
    return EVulnerability::Normal;
  else if (vuln == EVulnerability::DirectImmune)
    return EVulnerability::Immune;

  return vuln;
}
} // namespace urde
