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

use crate::error::{Error, Result};
use crate::infrastructure::building::level::BuildingLevel;
use crate::infrastructure::building::{Building, MineId};
use crate::world::config::WorldConfig;
use derive_more::Into;
use nil_num::growth::growth;
use nil_util::{ConstDeref, F64Math};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub trait Mine: Building {
  fn mine_id(&self) -> MineId;

  /// Amount of resources generated by the mine at its **current** level.
  fn production(&self, stats: &MineStatsTable) -> Result<MineProduction>;
  /// Amount of resources generated by the mine at its **minimum** level.
  fn min_production(&self) -> MineProduction;
  /// Amount of resources generated by the mine at its **maximum** level.
  fn max_production(&self) -> MineProduction;
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct MineStats {
  pub level: BuildingLevel,
  pub production: MineProduction,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct MineStatsTable {
  id: MineId,
  table: HashMap<BuildingLevel, MineStats>,
}

impl MineStatsTable {
  pub(crate) fn new(config: &WorldConfig, mine: &dyn Mine) -> Self {
    let max_level = *mine.max_level();
    let mut table = HashMap::with_capacity((max_level).into());

    let speed = f64::from(config.speed());
    let mut production = mine.min_production() * speed;

    let production_growth = growth()
      .floor(production)
      .ceil(mine.max_production() * speed)
      .max_level(max_level)
      .call();

    for level in 1..=max_level {
      let level = BuildingLevel::new(level);
      table.insert(
        level,
        MineStats {
          level,
          production: MineProduction::from(production.round()),
        },
      );

      debug_assert!(production.is_normal());

      production += production * production_growth;
    }

    table.shrink_to_fit();

    Self { id: mine.mine_id(), table }
  }

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

  #[inline]
  pub fn get(&self, level: BuildingLevel) -> Result<&MineStats> {
    self
      .table
      .get(&level)
      .ok_or(Error::MineStatsNotFoundForLevel(self.id, level))
  }
}

/// Amount of resources generated by a mine in a single round.
#[derive(Copy, Debug, Into, Deserialize, Serialize, ConstDeref, F64Math)]
#[derive_const(Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct MineProduction(u32);

impl MineProduction {
  #[inline]
  pub const fn new(value: u32) -> Self {
    Self(value)
  }
}

const impl From<f64> for MineProduction {
  fn from(value: f64) -> Self {
    Self::new(value as u32)
  }
}

const impl From<MineProduction> for f64 {
  fn from(value: MineProduction) -> Self {
    f64::from(value.0)
  }
}
