1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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(())
    }
}