pub mod actual360;
pub mod actual364;
pub mod actual365fixed;
pub mod actual366;
pub mod actualactual;
pub mod business252;
pub mod thirty360;
pub mod thirty365;

use chrono::NaiveDate;
use std::fmt::Debug;

use crate::error::Result;
use crate::time::period::Period;

#[typetag::serde(tag = "type")]
pub trait DayCounters: Debug {
    fn day_count(&self, d1: NaiveDate, d2: NaiveDate) -> Result<i64>;
    fn year_fraction(&self, d1: NaiveDate, d2: NaiveDate) -> Result<f64>;

    fn year_fraction_to_date(&self, reference_date: NaiveDate, t: f64) -> Result<NaiveDate> {
        let guess_date = (reference_date + Period::Days((t * 365.25).round() as i64))?;
        let guess_time = self.year_fraction(reference_date, guess_date)?;
        guess_date + Period::Days(((t - guess_time) * 365.25).round() as i64)
    }
}

#[cfg(test)]
mod tests {
    use crate::error::Result;
    use crate::time::daycounters::DayCounters;
    use crate::time::daycounters::actual365fixed::Actual365Fixed;
    use chrono::NaiveDate;

    #[test]
    fn test_year_fraction_to_date() -> Result<()> {
        let reference_date = NaiveDate::from_ymd_opt(2023, 11, 4).unwrap();
        let target_date = NaiveDate::from_ymd_opt(2024, 11, 3).unwrap();
        assert_eq!(
            Actual365Fixed::default().year_fraction_to_date(reference_date, 365f64 / 365f64)?,
            target_date
        );
        assert_eq!(
            Actual365Fixed::default().year_fraction(reference_date, target_date)?,
            1f64
        );

        Ok(())
    }
}
