use crate::{Errors, J1900_OFFSET, MJD_OFFSET, SECONDS_PER_DAY};
const LEAP_SECONDS: [f64; 28] = [
    2_272_060_800.0, 
    2_287_785_600.0, 
    2_303_683_200.0, 
    2_335_219_200.0, 
    2_366_755_200.0, 
    2_398_291_200.0, 
    2_429_913_600.0, 
    2_461_449_600.0, 
    2_492_985_600.0, 
    2_524_521_600.0, 
    2_571_782_400.0, 
    2_603_318_400.0, 
    2_634_854_400.0, 
    2_698_012_800.0, 
    2_776_982_400.0, 
    2_840_140_800.0, 
    2_871_676_800.0, 
    2_918_937_600.0, 
    2_950_473_600.0, 
    2_982_009_600.0, 
    3_029_443_200.0, 
    3_076_704_000.0, 
    3_124_137_600.0, 
    3_345_062_400.0, 
    3_439_756_800.0, 
    3_550_089_600.0, 
    3_644_697_600.0, 
    3_692_217_600.0, 
];
const JANUARY_YEARS: [i32; 17] = [
    1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1988, 1990, 1991, 1996, 1999, 2006, 2009,
    2017,
];
const JULY_YEARS: [i32; 11] = [
    1972, 1981, 1982, 1983, 1985, 1992, 1993, 1994, 1997, 2012, 2015,
];
const USUAL_DAYS_PER_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const USUAL_DAYS_PER_YEAR: f64 = 365.0;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Epoch(f64);
impl Epoch {
    
    pub fn from_tai_seconds(seconds: f64) -> Self {
        Self { 0: seconds }
    }
    
    pub fn from_tai_days(days: f64) -> Self {
        Self {
            0: days * SECONDS_PER_DAY,
        }
    }
    pub fn from_mjd_tai(days: f64) -> Self {
        Self {
            0: (days - J1900_OFFSET) * SECONDS_PER_DAY,
        }
    }
    pub fn from_jde_tai(days: f64) -> Self {
        Self {
            0: (days - J1900_OFFSET - MJD_OFFSET) * SECONDS_PER_DAY,
        }
    }
    
    pub fn maybe_from_gregorian_tai(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanos: u32,
    ) -> Result<Self, Errors> {
        if !is_gregorian_valid(year, month, day, hour, minute, second, nanos) {
            return Err(Errors::Carry);
        }
        let mut seconds_wrt_1900: f64 =
            f64::from((year - 1900).abs()) * SECONDS_PER_DAY * USUAL_DAYS_PER_YEAR;
        
        for year in 1900..year {
            if is_leap_year(year) {
                seconds_wrt_1900 += SECONDS_PER_DAY;
            }
        }
        
        for month in 0..month - 1 {
            seconds_wrt_1900 += SECONDS_PER_DAY * f64::from(USUAL_DAYS_PER_MONTH[(month) as usize]);
        }
        if is_leap_year(year) && month > 2 {
            
            
            seconds_wrt_1900 += SECONDS_PER_DAY;
        }
        seconds_wrt_1900 += f64::from(day - 1) * SECONDS_PER_DAY
            + f64::from(hour) * 3600.0
            + f64::from(minute) * 60.0
            + f64::from(second);
        if second == 60 {
            
            
            seconds_wrt_1900 -= 1.0;
        }
        Ok(Self {
            0: seconds_wrt_1900,
        })
    }
    
    
    pub fn from_gregorian_tai(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanos: u32,
    ) -> Self {
        Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos)
            .expect("invalid Gregorian date")
    }
    pub fn from_gregorian_tai_at_midnight(year: i32, month: u8, day: u8) -> Self {
        Self::maybe_from_gregorian_tai(year, month, day, 0, 0, 0, 0)
            .expect("invalid Gregorian date")
    }
    pub fn from_gregorian_tai_at_noon(year: i32, month: u8, day: u8) -> Self {
        Self::maybe_from_gregorian_tai(year, month, day, 12, 0, 0, 0)
            .expect("invalid Gregorian date")
    }
    pub fn from_gregorian_tai_hms(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
    ) -> Self {
        Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, 0)
            .expect("invalid Gregorian date")
    }
    
    pub fn maybe_from_gregorian_utc(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanos: u32,
    ) -> Result<Self, Errors> {
        let mut if_tai =
            Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos)?;
        
        let mut cnt = 0;
        for tai_ts in LEAP_SECONDS.iter() {
            if &if_tai.0 >= tai_ts {
                if cnt == 0 {
                    cnt = 10;
                } else {
                    cnt += 1;
                }
            }
        }
        if_tai.0 -= f64::from(cnt);
        Ok(if_tai)
    }
    
    
    pub fn from_gregorian_utc(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanos: u32,
    ) -> Self {
        Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, nanos)
            .expect("invalid Gregorian date")
    }
    pub fn from_gregorian_utc_at_midnight(year: i32, month: u8, day: u8) -> Self {
        Self::maybe_from_gregorian_utc(year, month, day, 0, 0, 0, 0)
            .expect("invalid Gregorian date")
    }
    pub fn from_gregorian_utc_at_noon(year: i32, month: u8, day: u8) -> Self {
        Self::maybe_from_gregorian_utc(year, month, day, 12, 0, 0, 0)
            .expect("invalid Gregorian date")
    }
    pub fn from_gregorian_utc_hms(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
    ) -> Self {
        Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, 0)
            .expect("invalid Gregorian date")
    }
    pub fn as_tai_seconds(self) -> f64 {
        self.0
    }
    pub fn as_tai_days(self) -> f64 {
        self.0 / SECONDS_PER_DAY
    }
    
    pub fn as_utc_seconds(self) -> f64 {
        let mut cnt = 0;
        for tai_ts in LEAP_SECONDS.iter() {
            if &self.0 >= tai_ts {
                if cnt == 0 {
                    cnt = 10;
                } else {
                    cnt += 1;
                }
            }
        }
        self.0 + f64::from(cnt)
    }
    pub fn as_utc_days(self) -> f64 {
        self.as_utc_seconds() / SECONDS_PER_DAY
    }
    
    
    pub fn as_mjd_tai_days(self) -> f64 {
        self.as_tai_days() + J1900_OFFSET
    }
    
    pub fn as_mjd_utc_days(self) -> f64 {
        self.as_utc_days() + J1900_OFFSET
    }
    
    pub fn as_mjd_tai_seconds(self) -> f64 {
        self.as_mjd_tai_days() * SECONDS_PER_DAY
    }
    
    pub fn as_mjd_utc_seconds(self) -> f64 {
        self.as_mjd_utc_days() * SECONDS_PER_DAY
    }
    
    
    
    pub fn as_jde_tai_days(self) -> f64 {
        self.as_mjd_tai_days() + MJD_OFFSET
    }
    
    pub fn as_jde_tai_seconds(self) -> f64 {
        self.as_jde_tai_days() * SECONDS_PER_DAY
    }
    
    pub fn as_jde_utc_days(self) -> f64 {
        self.as_mjd_utc_days() + MJD_OFFSET
    }
    
    pub fn as_jde_utc_seconds(self) -> f64 {
        self.as_jde_utc_days() * SECONDS_PER_DAY
    }
    
    pub fn mut_add_days(&mut self, days: f64) {
        self.0 += days * SECONDS_PER_DAY
    }
    
    pub fn mut_add_secs(&mut self, seconds: f64) {
        self.0 += seconds
    }
    
    pub fn mut_sub_days(&mut self, days: f64) {
        self.0 -= days * SECONDS_PER_DAY
    }
    
    pub fn mut_sub_secs(&mut self, seconds: f64) {
        self.0 -= seconds
    }
}
pub fn is_gregorian_valid(
    year: i32,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,
    nanos: u32,
) -> bool {
    let max_seconds = if (month == 12 || month == 6)
        && day == USUAL_DAYS_PER_MONTH[month as usize - 1]
        && hour == 23
        && minute == 59
        && ((month == 6 && JULY_YEARS.contains(&year))
            || (month == 12 && JANUARY_YEARS.contains(&(year + 1))))
    {
        60
    } else {
        59
    };
    
    if month == 0
        || month > 12
        || day == 0
        || day > 31
        || hour > 24
        || minute > 59
        || second > max_seconds
        || f64::from(nanos) > 1e9
    {
        return false;
    }
    if day > USUAL_DAYS_PER_MONTH[month as usize - 1] && (month != 2 || !is_leap_year(year)) {
        
        return false;
    }
    true
}
fn is_leap_year(year: i32) -> bool {
    (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
#[test]
fn utc_epochs() {
    use std::f64::EPSILON;
    assert!(Epoch::from_mjd_tai(J1900_OFFSET).as_tai_seconds() < EPSILON);
    assert!((Epoch::from_mjd_tai(J1900_OFFSET).as_mjd_tai_days() - J1900_OFFSET).abs() < EPSILON);
    
    
    
    
    let this_epoch = Epoch::from_tai_seconds(1_199_333_568.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 1, 3, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_204_156_800.0);
    let epoch_utc =
        Epoch::maybe_from_gregorian_utc(1938, 2, 28, 00, 00, 00, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_204_243_199.0);
    let epoch_utc =
        Epoch::maybe_from_gregorian_utc(1938, 2, 28, 23, 59, 59, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_204_243_200.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 3, 1, 00, 00, 00, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_206_850_368.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 3, 31, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_214_194_368.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 6, 24, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_220_069_568.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 8, 31, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_230_610_368.0);
    let epoch_utc =
        Epoch::maybe_from_gregorian_utc(1938, 12, 31, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_230_696_768.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 1, 1, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_235_794_368.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 3, 1, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_267_416_768.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 3, 1, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_233_375_168.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 2, 1, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_264_911_168.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 1, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_267_243_968.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 28, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(1_267_330_368.0);
    let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 29, 4, 12, 48, 0).expect("init epoch");
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(2_272_060_790.0);
    let epoch_utc = Epoch::from_gregorian_utc_at_midnight(1972, 1, 1);
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    let this_epoch = Epoch::from_tai_seconds(2_272_060_800.0);
    let epoch_utc = Epoch::from_gregorian_utc_hms(1972, 1, 1, 0, 0, 10);
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    
    let this_epoch = Epoch::from_tai_seconds(3_692_217_599.0);
    let epoch_utc = Epoch::from_gregorian_utc_hms(2017, 1, 1, 0, 0, 36);
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    let mut this_epoch = Epoch::from_tai_seconds(3_692_217_600.0);
    let epoch_utc = Epoch::from_gregorian_utc_hms(2017, 1, 1, 0, 0, 37);
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
    this_epoch.mut_add_secs(3600.0);
    assert_eq!(
        this_epoch,
        Epoch::from_gregorian_utc_hms(2017, 1, 1, 1, 0, 37),
        "Incorrect epoch after add"
    );
    this_epoch.mut_sub_secs(3600.0);
    assert_eq!(epoch_utc, this_epoch, "Incorrect epoch after sub");
}
#[test]
fn julian_epoch() {
    
    
    let nist_j1900 = Epoch::from_tai_days(0.0);
    assert!((nist_j1900.as_mjd_tai_days() - 15_020.0).abs() < std::f64::EPSILON);
    assert!((nist_j1900.as_jde_tai_days() - 2_415_020.5).abs() < std::f64::EPSILON);
    let mjd = Epoch::from_gregorian_utc_at_midnight(1900, 1, 1);
    assert!((mjd.as_mjd_tai_days() - 15_020.0).abs() < std::f64::EPSILON);
    
    let j1900 = Epoch::from_tai_days(0.5);
    assert!((j1900.as_mjd_tai_days() - 15_020.5).abs() < std::f64::EPSILON);
    assert!((j1900.as_jde_tai_days() - 2_415_021.0).abs() < std::f64::EPSILON);
    let mjd = Epoch::from_gregorian_utc_at_noon(1900, 1, 1);
    assert!((mjd.as_mjd_tai_days() - 15_020.5).abs() < std::f64::EPSILON);
    
    let mjd = Epoch::from_gregorian_utc_at_midnight(1900, 1, 8);
    assert!((mjd.as_mjd_tai_days() - 15_027.0).abs() < std::f64::EPSILON);
    assert!((mjd.as_jde_tai_days() - 2_415_027.5).abs() < std::f64::EPSILON);
    
    let gps_std_epoch = Epoch::from_gregorian_tai_at_midnight(1980, 1, 6);
    assert!((gps_std_epoch.as_mjd_tai_days() - 44_244.0).abs() < std::f64::EPSILON);
    assert!((gps_std_epoch.as_jde_tai_days() - 2_444_244.5).abs() < std::f64::EPSILON);
    
    let j2000 = Epoch::from_gregorian_tai_at_midnight(2000, 1, 1);
    assert!((j2000.as_mjd_tai_days() - 51_544.0).abs() < std::f64::EPSILON);
    assert!((j2000.as_jde_tai_days() - 2_451_544.5).abs() < std::f64::EPSILON);
    let j2000 = Epoch::from_gregorian_utc_at_midnight(2000, 1, 1);
    assert!((j2000.as_mjd_utc_days() - 51_544.0).abs() < std::f64::EPSILON);
    assert!((j2000.as_jde_utc_days() - 2_451_544.5).abs() < std::f64::EPSILON);
    
    let jd020207 = Epoch::from_gregorian_tai_at_midnight(2002, 2, 7);
    assert!((jd020207.as_mjd_tai_days() - 52_312.0).abs() < std::f64::EPSILON);
    assert!((jd020207.as_jde_tai_days() - 2_452_312.5).abs() < std::f64::EPSILON);
    
    
    
    
    
    assert!(
        (Epoch::from_gregorian_tai_hms(2015, 6, 30, 23, 59, 59).as_mjd_tai_days()
            - 57_203.999_988_425_92)
            .abs()
            < std::f64::EPSILON,
        "Incorrect July 2015 leap second MJD computed"
    );
    
    assert!(
        (Epoch::from_gregorian_tai_hms(2015, 6, 30, 23, 59, 60).as_mjd_tai_days()
            - 57_203.999_988_425_92)
            .abs()
            < std::f64::EPSILON,
        "Incorrect July 2015 leap second MJD computed"
    );
    
    assert!(
        (Epoch::from_gregorian_tai_at_midnight(2015, 7, 1).as_mjd_tai_days() - 57_204.0).abs()
            < std::f64::EPSILON,
        "Incorrect Post July 2015 leap second MJD computed"
    );
}
#[test]
fn leap_year() {
    assert!(!is_leap_year(2019));
    assert!(!is_leap_year(2001));
    assert!(!is_leap_year(1000));
    
    let leap_years: [i32; 146] = [
        1804, 1808, 1812, 1816, 1820, 1824, 1828, 1832, 1836, 1840, 1844, 1848, 1852, 1856, 1860,
        1864, 1868, 1872, 1876, 1880, 1884, 1888, 1892, 1896, 1904, 1908, 1912, 1916, 1920, 1924,
        1928, 1932, 1936, 1940, 1944, 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984,
        1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2044,
        2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076, 2080, 2084, 2088, 2092, 2096, 2104, 2108,
        2112, 2116, 2120, 2124, 2128, 2132, 2136, 2140, 2144, 2148, 2152, 2156, 2160, 2164, 2168,
        2172, 2176, 2180, 2184, 2188, 2192, 2196, 2204, 2208, 2212, 2216, 2220, 2224, 2228, 2232,
        2236, 2240, 2244, 2248, 2252, 2256, 2260, 2264, 2268, 2272, 2276, 2280, 2284, 2288, 2292,
        2296, 2304, 2308, 2312, 2316, 2320, 2324, 2328, 2332, 2336, 2340, 2344, 2348, 2352, 2356,
        2360, 2364, 2368, 2372, 2376, 2380, 2384, 2388, 2392, 2396, 2400,
    ];
    for year in leap_years.iter() {
        assert!(is_leap_year(*year));
    }
}
#[test]
fn datetime_invalid_dates() {
    assert!(!is_gregorian_valid(2001, 2, 29, 22, 8, 47, 0));
    assert!(!is_gregorian_valid(2016, 12, 31, 23, 59, 61, 0));
    assert!(!is_gregorian_valid(2015, 6, 30, 23, 59, 61, 0));
}