#include "Runtime/World/CDamageVulnerability.hpp"

namespace metaforce {

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 constexpr bool is_deflect_direct(EVulnerability vuln) {
  return vuln == EVulnerability::Deflect || vuln == EVulnerability::DirectWeak ||
         vuln == EVulnerability::DirectNormal || vuln == EVulnerability::DirectImmune;
}

static constexpr bool is_normal_or_weak(EVulnerability vuln) {
  return vuln == EVulnerability::Weak || vuln == EVulnerability::Normal;
}
static constexpr bool is_not_deflect(EVulnerability vuln) {
  return vuln != EVulnerability::Deflect;
}

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

  for (int i = 0; i < std::min(propCount, 15); ++i) {
    x0_normal[i] = EVulnerability(in.readUint32Big());
  }

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

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

  x5c_deflected = EDeflectType(in.readUint32Big());
  in.readUint32Big();
  for (int i = 0; i < 4; ++i) {
    x3c_charged[i] = EVulnerability(in.readUint32Big());
  }

  x60_chargedDeflected = EDeflectType(in.readUint32Big());
  in.readUint32Big();
  for (int i = 0; i < 4; ++i) {
    x4c_combo[i] = EVulnerability(in.readUint32Big());
  }

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

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

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

    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 {
    ConstructNew(in, propCount);
  }
}

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;
  }

  bool normalHurts = true;
  const auto vuln = x0_normal[u32(mode.GetType())];
  if (!ignoreDirect) {
    bool directHurts = true;
    if (!is_normal_or_weak(vuln) && vuln != EVulnerability::DirectWeak) {
      directHurts = false;
    }
    if (!directHurts && vuln != EVulnerability::DirectNormal) {
      normalHurts = false;
    }
  } else if (!is_normal_or_weak(vuln)) {
    normalHurts = false;
  }

  bool chargedHurts = true;
  if (mode.GetType() < EWeaponType::Bomb) {
    const auto chargedVuln = x3c_charged[u32(mode.GetType())];
    if (!ignoreDirect) {
      bool directHurts = true;
      if (!is_normal_or_weak(chargedVuln) && chargedVuln != EVulnerability::DirectWeak) {
        directHurts = false;
      }

      if (!directHurts && chargedVuln != EVulnerability::DirectNormal) {
        chargedHurts = false;
      }
    } else if (!is_normal_or_weak(chargedVuln)) {
      chargedHurts = false;
    }
  }

  bool comboedHurts = true;
  if (mode.GetType() < EWeaponType::Bomb) {
    const auto comboedVuln = x4c_combo[u32(mode.GetType())];
    if (!ignoreDirect) {
      bool directHurts = true;
      if (!is_normal_or_weak(comboedVuln) && comboedVuln != EVulnerability::DirectWeak) {
        directHurts = false;
      }

      if (!directHurts && comboedVuln != EVulnerability::DirectNormal) {
        comboedHurts = false;
      }
    } else if (!is_normal_or_weak(comboedVuln)) {
      comboedHurts = false;
    }
  }

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

    if (!mode.IsComboed()) {
      return false;
    }
  }
  return true;
}

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;
  }
  const auto normalVuln = x0_normal[u32(mode.GetType())];
  bool normalHits = true;
  if (!checkDirect) {
    normalHits = is_not_deflect(normalVuln);
  } else if (is_deflect_direct(normalVuln)) {
    normalHits = false;
  }

  bool chargedHits = true;
  if (mode.GetType() < EWeaponType::Bomb) {
    const auto chargedVuln = x3c_charged[u32(mode.GetType())];
    if (!checkDirect) {
      chargedHits = is_not_deflect(chargedVuln);
    } else if (is_deflect_direct(normalVuln)) {
      chargedHits = false;
    } else {
      chargedHits = true;
    }
  }

  bool comboedHits = true;
  if (mode.GetType() < EWeaponType::Bomb) {
    const auto chargedVuln = x3c_charged[u32(mode.GetType())];
    if (!checkDirect) {
      comboedHits = is_not_deflect(chargedVuln);
    } else if (is_deflect_direct(normalVuln)) {
      comboedHits = false;
    } else {
      comboedHits = true;
    }
  }

  if (normalHits && !mode.IsCharged() && !mode.IsComboed()) {
    return true;
  }

  if (!chargedHits || !mode.IsCharged()) {
    if (comboedHits) {
      return false;
    }
    if (!mode.IsComboed()) {
      return false;
    }
  }
  return true;
}

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

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

  auto vuln = x0_normal[u32(type)];
  if (mode.IsCharged()) {
    if (type < EWeaponType::Bomb) {
      vuln = x3c_charged[u32(type)];
    } else {
      vuln = EVulnerability::Normal;
    }
  }

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

  if (ignoreDirect) {
    return vuln;
  }

  if (vuln == EVulnerability::DirectWeak) {
    return EVulnerability::Weak;
  }

  if (vuln == EVulnerability::DirectNormal) {
    return EVulnerability::Normal;
  }

  if (vuln == EVulnerability::DirectImmune) {
    return EVulnerability::Immune;
  }

  return vuln;
}
} // namespace metaforce
