use std::{
    convert::TryFrom,
    fmt,
    fs::{self, File, OpenOptions},
    path::PathBuf,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_state::DeserializeState;
use necsim_core_bond::NonNegativeF64;
use necsim_impls_std::lineage_file::saver::LineageFileSaver;
use necsim_partitioning_core::partition::PartitionSize;
#[derive(Debug, Serialize)]
pub struct Pause {
    pub before: NonNegativeF64,
    pub config: ResumeConfig,
    pub destiny: SampleDestiny,
    #[serde(default)]
    pub mode: PauseMode,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Serialize, Deserialize)]
pub enum PauseMode {
    Resume,
    FixUp,
    Restart,
}
impl Default for PauseMode {
    fn default() -> Self {
        Self::Resume
    }
}
#[derive(Debug, Serialize, Deserialize)]
pub enum SampleDestiny {
    List,
    Bincode(LineageFileSaver),
}
#[derive(Deserialize)]
#[serde(try_from = "PathBuf")]
pub struct ResumeConfig {
    file: File,
    path: PathBuf,
    temp: bool,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Serialize)]
#[serde(rename = "Pause")]
pub struct FuturePause {
    pub before: NonNegativeF64,
    pub mode: PauseMode,
}
impl<'de> DeserializeState<'de, PartitionSize> for Pause {
    fn deserialize_state<D: Deserializer<'de>>(
        partition_size: &mut PartitionSize,
        deserializer: D,
    ) -> Result<Self, D::Error> {
        let raw = PauseRaw::deserialize(deserializer)?;
        if !partition_size.is_monolithic() {
            return Err(serde::de::Error::custom(
                "Parallel pausing is not yet supported.",
            ));
        }
        if matches!(raw.mode, PauseMode::FixUp) && raw.before == NonNegativeF64::zero() {
            return Err(serde::de::Error::custom(
                "pause mode `FixUp` requires a positive non-zero pause time",
            ));
        }
        Ok(Pause {
            before: raw.before,
            config: raw.config,
            destiny: raw.destiny,
            mode: raw.mode,
        })
    }
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename = "Pause")]
pub struct PauseRaw {
    pub before: NonNegativeF64,
    pub config: ResumeConfig,
    pub destiny: SampleDestiny,
    pub mode: PauseMode,
}
impl Drop for ResumeConfig {
    fn drop(&mut self) {
        if self.temp {
            std::mem::drop(fs::remove_file(self.path.clone()));
        }
    }
}
impl fmt::Debug for ResumeConfig {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        self.path.fmt(fmt)
    }
}
impl Serialize for ResumeConfig {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.path.serialize(serializer)
    }
}
impl TryFrom<PathBuf> for ResumeConfig {
    type Error = anyhow::Error;
    fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
        let file = OpenOptions::new()
            .create_new(true)
            .write(true)
            .open(&path)?;
        Ok(Self {
            file,
            path,
            temp: true,
        })
    }
}
impl ResumeConfig {
    pub fn write(mut self, config: &str) -> anyhow::Result<()> {
        std::io::Write::write_fmt(&mut self.file, format_args!("{config}\n"))?;
        self.temp = false;
        Ok(())
    }
}