//! Implementation of string parsing logic for `TimePoint` types.

use core::str::FromStr;

#[cfg(test)]
use crate::Duration;
use crate::{
    FromFineDateTime, HistoricDate, TimePoint, errors::TimePointParsingError, parse::TimeOfDay,
    time_scale::TimeScale,
};

impl<Scale> FromStr for TimePoint<Scale>
where
    Self: FromFineDateTime,
    Scale: TimeScale,
{
    type Err = TimePointParsingError<<Self as FromFineDateTime>::Error>;

    /// Parses a `TimePoint` based on some ISO 8610 date and time of day string. Note that time
    /// shifts are explicitly not supported: those are already included in the choice of `Scale`
    /// for a type. Additionally, we only support the extended calendar date and time of day
    /// formats (see section 5.4.2.1 of ISO 8610). Finally, because the extended format is used
    /// (which explicitly delimits time point components), any number of digits is allowed in the
    /// year component, such that its range can be extended beyond the 0000..=9999 allowed by ISO
    /// 8601.
    fn from_str(string: &str) -> Result<Self, Self::Err> {
        let (historic_date, mut string) = HistoricDate::parse_partial(string)?;

        // Parse the mandatory time designator 'T'
        if string.starts_with("T") {
            string = string.get(1..).unwrap();
        } else {
            return Err(TimePointParsingError::ExpectedTimeDesignator);
        }

        let (time_of_day, mut string) = TimeOfDay::parse_partial(string)?;

        // Finally, the time point must end with a space, followed by the time zone abbreviation.
        if string.starts_with(" ") {
            string = string.get(1..).unwrap();
        } else {
            return Err(TimePointParsingError::ExpectedSpace);
        }

        if string.starts_with(Scale::ABBREVIATION) {
            string = string.get(Scale::ABBREVIATION.len()..).unwrap();
        } else {
            return Err(TimePointParsingError::ExpectedTimeScaleDesignator);
        }

        if !string.is_empty() {
            return Err(TimePointParsingError::UnexpectedRemainder);
        }

        let time_point = Self::from_fine_datetime(
            historic_date.into_date(),
            time_of_day.hour,
            time_of_day.minute,
            time_of_day.second,
            time_of_day.subseconds,
        );

        match time_point {
            Ok(time_point) => Ok(time_point),
            Err(datetime_error) => Err(TimePointParsingError::DateTimeError(datetime_error)),
        }
    }
}

#[cfg(feature = "serde")]
impl<Scale> serde::Serialize for TimePoint<Scale>
where
    Self: ToString,
    Scale: ?Sized,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let string = self.to_string();
        serializer.serialize_str(&string)
    }
}

#[cfg(feature = "serde")]
impl<'de, Scale> serde::Deserialize<'de> for TimePoint<Scale>
where
    Self: FromStr,
    <Self as FromStr>::Err: core::fmt::Display,
    Scale: ?Sized,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let string = String::deserialize(deserializer)?;
        Self::from_str(&string).map_err(serde::de::Error::custom)
    }
}

#[cfg(test)]
#[allow(clippy::too_many_arguments)]
fn check_historic_datetime(
    string: &str,
    year: i32,
    month: crate::Month,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,
    subseconds: Duration,
) {
    use crate::{Date, FromDateTime, IntoFineDateTime, TaiTime};
    let datetime = TaiTime::from_str(string).unwrap();
    let date = Date::from_historic_date(year, month, day).unwrap();
    let expected_datetime =
        TaiTime::from_datetime(date, hour, minute, second).unwrap() + subseconds;
    assert_eq!(datetime, expected_datetime);
    let (date2, hour2, minute2, second2, subseconds2) = datetime.into_fine_datetime();
    assert_eq!(date, date2);
    assert_eq!(hour, hour2);
    assert_eq!(minute, minute2);
    assert_eq!(second, second2);
    assert_eq!(subseconds, subseconds2);
}

#[test]
fn known_timestamps() {
    use num_traits::ConstZero;

    use crate::Month::*;

    check_historic_datetime(
        "1958-01-01T00:00:00.000001 TAI",
        1958,
        January,
        1,
        0,
        0,
        0,
        Duration::microseconds(1),
    );
    check_historic_datetime(
        "1958-01-02T00:00:00 TAI",
        1958,
        January,
        2,
        0,
        0,
        0,
        Duration::ZERO,
    );
    check_historic_datetime(
        "1960-01-01T12:34:56.789123 TAI",
        1960,
        January,
        1,
        12,
        34,
        56,
        Duration::microseconds(789123),
    );
    check_historic_datetime(
        "1961-01-01T00:00:00 TAI",
        1961,
        January,
        1,
        0,
        0,
        0,
        Duration::ZERO,
    );
    check_historic_datetime(
        "1970-01-01T00:00:00 TAI",
        1970,
        January,
        1,
        0,
        0,
        0,
        Duration::ZERO,
    );
    check_historic_datetime(
        "1976-01-01T23:59:59.999 TAI",
        1976,
        January,
        1,
        23,
        59,
        59,
        Duration::microseconds(999000),
    );
    check_historic_datetime(
        "2025-07-16T16:23:24.000000000 TAI",
        2025,
        July,
        16,
        16,
        23,
        24,
        Duration::ZERO,
    );
    check_historic_datetime(
        "2034-12-26T08:02:37.123456000 TAI",
        2034,
        December,
        26,
        8,
        2,
        37,
        Duration::microseconds(123456),
    );
    check_historic_datetime(
        "2760-04-01T21:59:58 TAI",
        2760,
        April,
        1,
        21,
        59,
        58,
        Duration::ZERO,
    );
    check_historic_datetime(
        "1643-01-04T01:01:33.000 TAI",
        1643,
        January,
        4,
        1,
        1,
        33,
        Duration::ZERO,
    );
}
