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

pub mod personnel;

use crate::continent::index::ContinentKey;
use crate::military::Military;
use crate::military::army::personnel::{ArmyPersonnel, ArmyPersonnelIter};
use crate::military::maneuver::ManeuverId;
use crate::military::squad::Squad;
use crate::military::unit::UnitId;
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::ranking::score::Score;
use crate::resources::maintenance::Maintenance;
use crate::ruler::Ruler;
use crate::world::config::WorldConfig;
use bon::Builder;
use derive_more::Display;
use serde::{Deserialize, Serialize};
use std::mem;
use std::ops::{Mul, MulAssign};
use strum::{EnumIs, IntoEnumIterator};
use uuid::Uuid;

#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
#[builder(builder_type(vis = "pub(crate)"))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct Army {
  #[builder(skip)]
  id: ArmyId,

  #[builder(default)]
  personnel: ArmyPersonnel,

  #[builder(into)]
  owner: Ruler,

  #[builder(default, into)]
  state: ArmyState,
}

impl Army {
  #[inline]
  pub fn id(&self) -> ArmyId {
    self.id
  }

  #[inline]
  pub fn personnel(&self) -> &ArmyPersonnel {
    &self.personnel
  }

  pub(crate) fn personnel_mut(&mut self) -> &mut ArmyPersonnel {
    &mut self.personnel
  }

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

  #[inline]
  pub fn squad(&self, id: UnitId) -> &Squad {
    self.personnel.squad(id)
  }

  fn squad_mut(&mut self, id: UnitId) -> &mut Squad {
    self.personnel.squad_mut(id)
  }

  #[inline]
  pub fn owner(&self) -> &Ruler {
    &self.owner
  }

  #[inline]
  pub fn state(&self) -> &ArmyState {
    &self.state
  }

  pub(crate) fn state_mut(&mut self) -> &mut ArmyState {
    &mut self.state
  }

  #[inline]
  pub fn slowest_squad(&self, config: &WorldConfig) -> Option<&Squad> {
    self.personnel.slowest_squad(config)
  }

  #[inline]
  pub fn speed(&self, config: &WorldConfig) -> Speed {
    self.personnel.speed(config)
  }

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

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

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

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

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

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

  #[inline]
  pub fn is_owned_by(&self, ruler: &Ruler) -> bool {
    self.owner.eq(ruler)
  }

  #[inline]
  pub fn is_idle_and_owned_by(&self, ruler: &Ruler) -> bool {
    self.is_idle() && self.is_owned_by(ruler)
  }

  #[inline]
  pub fn is_empty(&self) -> bool {
    self.personnel.is_empty()
  }

  #[inline]
  pub fn is_idle(&self) -> bool {
    self.state.is_idle()
  }

  #[inline]
  pub fn is_maneuvering(&self) -> bool {
    self.state.is_maneuvering()
  }

  #[inline]
  pub fn has_enough_personnel(&self, required: &ArmyPersonnel) -> bool {
    self.personnel.has_enough_personnel(required)
  }

  pub(crate) fn spawn<K>(self, military: &mut Military, key: K)
  where
    K: ContinentKey,
  {
    military.spawn(key, self.owner, self.personnel);
  }
}

macro_rules! impl_army {
  ($($unit:ident),+) => {
    paste::paste! {
      impl Army {
        $(
          #[inline]
          pub fn [<$unit:snake>](&self) -> &Squad {
            &self.personnel.[<$unit:snake>]()
          }
        )+
      }
    }
  };
}

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

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

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

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

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

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

#[must_use]
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct ArmyId(Uuid);

impl ArmyId {
  #[inline]
  pub fn new() -> Self {
    Self(Uuid::new_v4())
  }
}

impl Default for ArmyId {
  fn default() -> Self {
    Self::new()
  }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize, EnumIs)]
#[serde(tag = "kind", rename_all = "kebab-case")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub enum ArmyState {
  #[default]
  Idle,
  Maneuvering {
    maneuver: ManeuverId,
  },
}

impl ArmyState {
  #[inline]
  pub fn with_maneuver(maneuver: ManeuverId) -> Self {
    Self::Maneuvering { maneuver }
  }
}

pub fn collapse_armies(armies: &mut Vec<Army>) {
  for army in mem::take(armies)
    .into_iter()
    .filter(|army| !army.is_empty())
  {
    if army.is_idle()
      && let Some(previous) = find_idle_owned_by(armies, army.owner())
    {
      *previous.personnel_mut() += ArmyPersonnel::from(army);
    } else {
      armies.push(army);
    }
  }
}

fn find_idle_owned_by<'a>(armies: &'a mut [Army], ruler: &Ruler) -> Option<&'a mut Army> {
  armies
    .iter_mut()
    .find(|army| army.is_idle_and_owned_by(ruler))
}
