1use crate::json::{DeserializedObj, JSON};
2use crate::scheduling::calendars::Calendar;
3use crate::scheduling::frequency::{Frequency, RollDay, Scheduling};
4
5use chrono::prelude::*;
6use pyo3::exceptions::PyValueError;
7use pyo3::prelude::*;
8use pyo3::types::PyTuple;
9
10enum FrequencyNewArgs {
11    CalDays(i32),
12    BusDays(i32, Calendar),
13    Months(i32, Option<RollDay>),
14    Zero(),
15}
16
17impl<'py> IntoPyObject<'py> for FrequencyNewArgs {
18    type Target = PyTuple;
19    type Output = Bound<'py, Self::Target>;
20    type Error = std::convert::Infallible;
21
22    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
23        match self {
24            FrequencyNewArgs::CalDays(x) => Ok((x,).into_pyobject(py).unwrap()),
25            FrequencyNewArgs::BusDays(x, y) => Ok((x, y).into_pyobject(py).unwrap()),
26            FrequencyNewArgs::Months(x, y) => Ok((x, y).into_pyobject(py).unwrap()),
27            FrequencyNewArgs::Zero() => Ok(PyTuple::empty(py)),
28        }
29    }
30}
31
32impl<'py> FromPyObject<'py> for FrequencyNewArgs {
33    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
34        let ext: PyResult<(i32,)> = ob.extract();
35        if ext.is_ok() {
36            let (x,) = ext.unwrap();
37            return Ok(Self::CalDays(x));
38        }
39        let ext: PyResult<(i32, Calendar)> = ob.extract();
40        if ext.is_ok() {
41            let (x, y) = ext.unwrap();
42            return Ok(Self::BusDays(x, y));
43        }
44        let ext: PyResult<(i32, Option<RollDay>)> = ob.extract();
45        if ext.is_ok() {
46            let (x, y) = ext.unwrap();
47            Ok(Self::Months(x, y))
48        } else {
49            Ok(Self::Zero())
51        }
52    }
53}
54
55#[pymethods]
56impl Frequency {
57    #[pyo3(name = "next")]
68    fn next_py(&self, date: NaiveDateTime) -> NaiveDateTime {
69        self.next(&date)
70    }
71
72    #[pyo3(name = "periods_per_annum")]
78    fn periods_per_annum_py(&self) -> f64 {
79        self.periods_per_annum()
80    }
81
82    #[pyo3(name = "unext")]
94    fn unext_py(&self, udate: NaiveDateTime) -> PyResult<NaiveDateTime> {
95        self.try_unext(&udate)
96    }
97
98    #[pyo3(name = "previous")]
109    fn previous_py(&self, date: NaiveDateTime) -> NaiveDateTime {
110        self.previous(&date)
111    }
112
113    #[pyo3(name = "uprevious")]
125    fn uprevious_py(&self, udate: NaiveDateTime) -> PyResult<NaiveDateTime> {
126        self.try_uprevious(&udate)
127    }
128
129    #[pyo3(name = "uregular")]
144    fn uregular_py(
145        &self,
146        ueffective: NaiveDateTime,
147        utermination: NaiveDateTime,
148    ) -> PyResult<Vec<NaiveDateTime>> {
149        self.try_uregular(&ueffective, &utermination)
150    }
151
152    #[pyo3(name = "infer_ustub")]
175    fn infer_ustub_py(
176        &self,
177        ueffective: NaiveDateTime,
178        utermination: NaiveDateTime,
179        short: bool,
180        front: bool,
181    ) -> PyResult<Option<NaiveDateTime>> {
182        if front {
183            self.try_infer_ufront_stub(&ueffective, &utermination, short)
184        } else {
185            self.try_infer_uback_stub(&ueffective, &utermination, short)
186        }
187    }
188
189    #[pyo3(name = "is_stub")]
204    fn is_stub_py(&self, ustart: NaiveDateTime, uend: NaiveDateTime, front: bool) -> bool {
205        if front {
206            self.is_front_stub(&ustart, &uend)
207        } else {
208            self.is_back_stub(&ustart, &uend)
209        }
210    }
211
212    #[pyo3(name = "string")]
218    fn string_py(&self) -> PyResult<String> {
219        match self {
220            Frequency::Zero {} => Ok("Z".to_string()),
221            Frequency::CalDays { number: n } => Ok(format!("{n}D")),
222            Frequency::BusDays {
223                number: n,
224                calendar: _,
225            } => Ok(format!("{n}B")),
226            Frequency::Months { number: 1, roll: _ } => Ok(format!("M")),
227            Frequency::Months { number: 2, roll: _ } => Ok(format!("B")),
228            Frequency::Months { number: 3, roll: _ } => Ok(format!("Q")),
229            Frequency::Months { number: 4, roll: _ } => Ok(format!("T")),
230            Frequency::Months { number: 6, roll: _ } => Ok(format!("S")),
231            Frequency::Months {
232                number: 12,
233                roll: _,
234            } => Ok(format!("A")),
235            _ => Err(PyValueError::new_err(
236                "No recognisable string representation for Frequency.",
237            )),
238        }
239    }
240
241    fn __str__(&self) -> String {
242        match self {
243            Frequency::Zero {} => "Z".to_string(),
244            Frequency::CalDays { number: n } => format!("{n}D"),
245            Frequency::BusDays {
246                number: n,
247                calendar: _,
248            } => format!("{n}B"),
249            Frequency::Months { number: n, roll: r } => {
250                let x = match r {
251                    Some(v) => v.__str__(),
252                    None => "none".to_string(),
253                };
254                format!("{n}M (roll: {x})")
255            }
256        }
257    }
258
259    fn __getnewargs__(&self) -> FrequencyNewArgs {
260        match self {
261            Frequency::BusDays {
262                number: n,
263                calendar: c,
264            } => FrequencyNewArgs::BusDays(*n, c.clone()),
265            Frequency::CalDays { number: n } => FrequencyNewArgs::CalDays(*n),
266            Frequency::Months { number: n, roll: r } => FrequencyNewArgs::Months(*n, *r),
267            Frequency::Zero {} => FrequencyNewArgs::Zero(),
268        }
269    }
270
271    #[new]
272    fn new_py(args: FrequencyNewArgs) -> Frequency {
273        match args {
274            FrequencyNewArgs::BusDays(n, c) => Frequency::BusDays {
275                number: n,
276                calendar: c,
277            },
278            FrequencyNewArgs::CalDays(n) => Frequency::CalDays { number: n },
279            FrequencyNewArgs::Months(n, r) => Frequency::Months { number: n, roll: r },
280            FrequencyNewArgs::Zero() => Frequency::Zero {},
281        }
282    }
283
284    fn __repr__(&self) -> String {
285        match self {
286            Frequency::Zero {} => format!("<rl.Frequency.Zero at {:p}>", self),
287            Frequency::CalDays { number: n } => {
288                format!("<rl.Frequency.CalDays({}) at {:p}>", n, self)
289            }
290            Frequency::BusDays {
291                number: n,
292                calendar: _,
293            } => format!("<rl.Frequency.BusDays({}, ...) at {:p}>", n, self),
294            Frequency::Months { number: n, roll: r } => match r {
295                Some(val) => format!("<rl.Frequency.Months({}, {:?}) at {:p}>", n, val, self),
296                None => format!("<rl.Frequency.Months({}, None) at {:p}>", n, self),
297            },
298        }
299    }
300
301    #[pyo3(name = "to_json")]
307    fn to_json_py(&self) -> PyResult<String> {
308        match DeserializedObj::Frequency(self.clone()).to_json() {
309            Ok(v) => Ok(v),
310            Err(_) => Err(PyValueError::new_err(
311                "Failed to serialize `Frequency` to JSON.",
312            )),
313        }
314    }
315}