// Copyright (C) Call of Nil contributors
// SPDX-License-Identifier: AGPL-3.0-only

use crate::military::army::Army;
use crate::military::squad::Squad;
use crate::military::squad::size::SquadSize;
use crate::military::unit::stats::haul::Haul;
use crate::military::unit::stats::power::{AttackPower, DefensePower, Power};
use crate::military::unit::stats::speed::Speed;
use crate::military::unit::{UnitId, UnitIdIter};
use crate::ranking::score::Score;
use crate::resources::maintenance::Maintenance;
use crate::world::config::WorldConfig;
use serde::{Deserialize, Serialize};
use std::iter::Sum;
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
use strum::IntoEnumIterator;
use tap::Pipe;

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct ArmyPersonnel {
  archer: Squad,
  axeman: Squad,
  heavy_cavalry: Squad,
  light_cavalry: Squad,
  pikeman: Squad,
  ram: Squad,
  swordsman: Squad,
}

#[bon::bon]
impl ArmyPersonnel {
  #[builder]
  pub fn new(
    #[builder(default, into)] archer: SquadSize,
    #[builder(default, into)] axeman: SquadSize,
    #[builder(default, into)] heavy_cavalry: SquadSize,
    #[builder(default, into)] light_cavalry: SquadSize,
    #[builder(default, into)] pikeman: SquadSize,
    #[builder(default, into)] ram: SquadSize,
    #[builder(default, into)] swordsman: SquadSize,
  ) -> Self {
    use UnitId::*;
    Self {
      archer: Squad::new(Archer, archer),
      axeman: Squad::new(Axeman, axeman),
      heavy_cavalry: Squad::new(HeavyCavalry, heavy_cavalry),
      light_cavalry: Squad::new(LightCavalry, light_cavalry),
      pikeman: Squad::new(Pikeman, pikeman),
      ram: Squad::new(Ram, ram),
      swordsman: Squad::new(Swordsman, swordsman),
    }
  }

  pub fn to_vec(self) -> Vec<Squad> {
    Vec::<Squad>::from(self)
  }

  #[inline]
  pub fn iter(&self) -> ArmyPersonnelIter<'_> {
    ArmyPersonnelIter::new(self)
  }

  pub fn slowest_squad(&self, config: &WorldConfig) -> Option<&Squad> {
    self
      .iter()
      .filter(|squad| squad.size() > 0u32)
      .min_by(|a, b| a.speed(config).total_cmp(&b.speed(config)))
  }

  pub fn speed(&self, config: &WorldConfig) -> Speed {
    self
      .slowest_squad(config)
      .map(|squad| squad.speed(config))
      .unwrap_or_default()
  }

  #[inline]
  pub fn haul(&self) -> Haul {
    self.iter().sum()
  }

  #[inline]
  pub fn score(&self) -> Score {
    self.iter().sum()
  }

  #[inline]
  pub fn maintenance(&self) -> Maintenance {
    self.iter().sum()
  }

  #[inline]
  pub fn power(&self) -> Power {
    self.iter().sum()
  }

  #[inline]
  pub fn attack(&self) -> AttackPower {
    self.iter().sum()
  }

  #[inline]
  pub fn defense(&self) -> DefensePower {
    self.iter().sum()
  }

  #[inline]
  pub fn is_empty(&self) -> bool {
    self.iter().all(Squad::is_empty)
  }
}

macro_rules! impl_army_personnel {
  ($($unit:ident),+) => {
    paste::paste! {
      impl ArmyPersonnel {
        pub fn splat(size: impl Into<SquadSize>) -> Self {
          let size: SquadSize = size.into();
          Self::builder()
            $(.[<$unit:snake>](size))+
            .build()
        }

        pub fn random() -> Self {
          Self::builder()
            $(.[<$unit:snake>](SquadSize::random()))+
            .build()
        }

        pub fn squad(&self, id: UnitId) -> &Squad {
          match id {
            $(UnitId::$unit => &self.[<$unit:snake>],)+
          }
        }

        pub(super) fn squad_mut(&mut self, id: UnitId) -> &mut Squad {
          match id {
            $(UnitId::$unit => &mut self.[<$unit:snake>],)+
          }
        }

        $(
          #[inline]
          pub fn [<$unit:snake>](&self) -> &Squad {
            &self.[<$unit:snake>]
          }
        )+

        pub fn has_enough_personnel(&self, required: &ArmyPersonnel) -> bool {
          $(self.[<$unit:snake>].size() >= required.[<$unit:snake>].size() && )+ true
        }

        pub fn checked_sub(&self, rhs: &Self) -> Option<Self> {
          Self::builder()
            $(
              .[<$unit:snake>](
                self
                  .[<$unit:snake>]
                  .size()
                  .checked_sub(rhs.[<$unit:snake>].size())?
              )
            )+
            .build()
            .pipe(Some)
        }
      }

      impl From<ArmyPersonnel> for Vec<Squad> {
        fn from(personnel: ArmyPersonnel) -> Self {
          vec![$(personnel.[<$unit:snake>],)+]
        }
      }
    }
  };
}

impl_army_personnel!(
  Archer,
  Axeman,
  HeavyCavalry,
  LightCavalry,
  Pikeman,
  Ram,
  Swordsman
);

impl Default for ArmyPersonnel {
  fn default() -> Self {
    Self::builder().build()
  }
}

impl From<Army> for ArmyPersonnel {
  fn from(army: Army) -> Self {
    army.personnel
  }
}

impl From<SquadSize> for ArmyPersonnel {
  fn from(size: SquadSize) -> Self {
    ArmyPersonnel::splat(size)
  }
}

impl FromIterator<Squad> for ArmyPersonnel {
  fn from_iter<T>(iter: T) -> Self
  where
    T: IntoIterator<Item = Squad>,
  {
    iter
      .into_iter()
      .fold(Self::default(), |mut personnel, squad| {
        personnel += squad;
        personnel
      })
  }
}

impl<'a> IntoIterator for &'a ArmyPersonnel {
  type Item = &'a Squad;
  type IntoIter = ArmyPersonnelIter<'a>;

  fn into_iter(self) -> Self::IntoIter {
    self.iter()
  }
}

impl<'a> Sum<&'a ArmyPersonnel> for ArmyPersonnel {
  fn sum<I>(iter: I) -> Self
  where
    I: Iterator<Item = &'a ArmyPersonnel>,
  {
    iter.fold(ArmyPersonnel::default(), |mut acc, personnel| {
      acc += personnel;
      acc
    })
  }
}

impl<'a> Sum<&'a Army> for ArmyPersonnel {
  fn sum<I>(iter: I) -> Self
  where
    I: Iterator<Item = &'a Army>,
  {
    iter.map(Army::personnel).sum()
  }
}

impl Add for ArmyPersonnel {
  type Output = ArmyPersonnel;

  fn add(mut self, rhs: Self) -> Self::Output {
    self += &rhs;
    self
  }
}

impl Add<Squad> for ArmyPersonnel {
  type Output = ArmyPersonnel;

  fn add(mut self, rhs: Squad) -> Self::Output {
    self += rhs;
    self
  }
}

impl AddAssign for ArmyPersonnel {
  fn add_assign(&mut self, rhs: Self) {
    *self += &rhs;
  }
}

impl AddAssign<&ArmyPersonnel> for ArmyPersonnel {
  fn add_assign(&mut self, rhs: &ArmyPersonnel) {
    for squad in rhs {
      *self.squad_mut(squad.id()) += squad.size();
    }
  }
}

impl AddAssign<Squad> for ArmyPersonnel {
  fn add_assign(&mut self, rhs: Squad) {
    *self.squad_mut(rhs.id()) += rhs;
  }
}

impl Sub for ArmyPersonnel {
  type Output = ArmyPersonnel;

  fn sub(mut self, rhs: Self) -> Self::Output {
    self -= rhs;
    self
  }
}

impl SubAssign for ArmyPersonnel {
  fn sub_assign(&mut self, rhs: Self) {
    for squad in &rhs {
      *self.squad_mut(squad.id()) -= squad.size();
    }
  }
}

impl SubAssign<Squad> for ArmyPersonnel {
  fn sub_assign(&mut self, rhs: Squad) {
    *self.squad_mut(rhs.id()) -= rhs;
  }
}

impl Mul<f64> for ArmyPersonnel {
  type Output = ArmyPersonnel;

  fn mul(mut self, rhs: f64) -> Self::Output {
    self *= rhs;
    self
  }
}

impl MulAssign<f64> for ArmyPersonnel {
  fn mul_assign(&mut self, rhs: f64) {
    for id in UnitId::iter() {
      *self.squad_mut(id) *= rhs;
    }
  }
}

pub struct ArmyPersonnelIter<'a> {
  personnel: &'a ArmyPersonnel,
  units: UnitIdIter,
}

impl<'a> ArmyPersonnelIter<'a> {
  pub fn new(personnel: &'a ArmyPersonnel) -> Self {
    Self { personnel, units: UnitId::iter() }
  }
}

impl<'a> Iterator for ArmyPersonnelIter<'a> {
  type Item = &'a Squad;

  fn next(&mut self) -> Option<Self::Item> {
    let id = self.units.next()?;
    Some(self.personnel.squad(id))
  }
}
