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

use crate::error::{Error, Result};
use crate::resources::Resources;
use crate::resources::influence::Influence;
use crate::ruler::Ruler;
use crate::world::World;
use bon::Builder;
use derive_more::{From, Into};
use serde::{Deserialize, Serialize};
use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct PlayerManager(HashMap<PlayerId, Player>);

impl PlayerManager {
  pub(crate) fn manage(&mut self, player: Player) -> Result<()> {
    if self.0.contains_key(&player.id) {
      return Err(Error::PlayerAlreadySpawned(player.id));
    } else {
      self.0.insert(player.id(), player);
    }

    Ok(())
  }

  pub fn player(&self, id: &PlayerId) -> Result<&Player> {
    self
      .0
      .get(id)
      .ok_or_else(|| Error::PlayerNotFound(id.clone()))
  }

  pub(crate) fn player_mut(&mut self, id: &PlayerId) -> Result<&mut Player> {
    self
      .0
      .get_mut(id)
      .ok_or_else(|| Error::PlayerNotFound(id.clone()))
  }

  pub fn players(&self) -> impl Iterator<Item = &Player> {
    self.0.values()
  }

  pub fn player_ids(&self) -> impl Iterator<Item = &PlayerId> {
    self.0.keys()
  }

  pub fn active_players(&self) -> impl Iterator<Item = &Player> {
    self
      .players()
      .filter(|player| player.is_active())
  }

  #[inline]
  pub fn has(&self, id: &PlayerId) -> bool {
    self.0.contains_key(id)
  }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct Player {
  id: PlayerId,
  status: PlayerStatus,
  resources: Resources,
  influence: Influence,
}

impl Player {
  pub fn new(options: PlayerOptions) -> Self {
    Self {
      id: options.id,
      status: PlayerStatus::Active,
      resources: Resources::PLAYER.clone(),
      influence: Influence::MIN,
    }
  }

  pub fn spawn(self, world: &mut World) -> Result<()> {
    world.spawn_player(self)
  }

  #[inline]
  pub fn id(&self) -> PlayerId {
    self.id.clone()
  }

  #[inline]
  pub fn status(&self) -> PlayerStatus {
    self.status
  }

  pub(crate) fn status_mut(&mut self) -> &mut PlayerStatus {
    &mut self.status
  }

  #[inline]
  pub fn resources(&self) -> &Resources {
    &self.resources
  }

  pub(crate) fn resources_mut(&mut self) -> &mut Resources {
    &mut self.resources
  }

  #[inline]
  pub fn influence(&self) -> Influence {
    self.influence
  }

  #[inline]
  pub fn is_active(&self) -> bool {
    matches!(self.status, PlayerStatus::Active)
  }

  #[inline]
  pub fn is_inactive(&self) -> bool {
    matches!(self.status, PlayerStatus::Inactive)
  }
}

#[derive(
  Debug,
  derive_more::Display,
  From,
  Into,
  PartialEq,
  Eq,
  PartialOrd,
  Ord,
  Hash,
  Deserialize,
  Serialize,
)]
#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PlayerId(Arc<str>);

impl Clone for PlayerId {
  fn clone(&self) -> Self {
    Self(Arc::clone(&self.0))
  }
}

impl AsRef<str> for PlayerId {
  fn as_ref(&self) -> &str {
    self.0.as_str()
  }
}

impl Deref for PlayerId {
  type Target = str;

  fn deref(&self) -> &Self::Target {
    self.0.as_str()
  }
}

impl Borrow<str> for PlayerId {
  fn borrow(&self) -> &str {
    self.0.as_str()
  }
}

impl PartialEq<Ruler> for PlayerId {
  fn eq(&self, other: &Ruler) -> bool {
    if let Ruler::Player { id } = other { self.eq(id) } else { false }
  }
}

#[derive(Clone, Copy, Debug, strum::Display, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub enum PlayerStatus {
  Active,
  Inactive,
}

#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PlayerOptions {
  #[builder(start_fn, into)]
  pub id: PlayerId,
}

impl PlayerOptions {
  #[inline]
  pub fn into_player(self) -> Player {
    Player::new(self)
  }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PublicPlayer {
  id: PlayerId,
  status: PlayerStatus,
}

impl From<&Player> for PublicPlayer {
  fn from(player: &Player) -> Self {
    Self {
      id: player.id.clone(),
      status: player.status,
    }
  }
}
