#![deny(missing_docs)]
#[allow(missing_docs)]
mod error;
#[cfg(feature = "chrono")]
mod human;
#[cfg(feature = "chrono")]
pub use human::*;
use core::ops::{Add, Sub};
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
pub use crate::error::{TimestampError, TimestampResult};
#[cfg(feature = "chrono")]
pub(crate) use chrono_ext::*;
#[cfg(feature = "chrono")]
mod chrono_ext;
pub const MM: i64 = 1_000_000;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(not(feature = "chrono"), derive(Debug))]
pub struct Timestamp(
    pub i64,
);
impl<D: Into<core::time::Duration>> Add<D> for Timestamp {
    type Output = TimestampResult<Timestamp>;
    fn add(self, rhs: D) -> Self::Output {
        self.checked_add(&rhs.into())
            .ok_or(TimestampError::Overflow)
    }
}
impl<D: Into<core::time::Duration>> Add<D> for &Timestamp {
    type Output = TimestampResult<Timestamp>;
    fn add(self, rhs: D) -> Self::Output {
        self.to_owned() + rhs
    }
}
impl<D: Into<core::time::Duration>> Sub<D> for Timestamp {
    type Output = TimestampResult<Timestamp>;
    fn sub(self, rhs: D) -> Self::Output {
        self.checked_sub(&rhs.into())
            .ok_or(TimestampError::Overflow)
    }
}
impl<D: Into<core::time::Duration>> Sub<D> for &Timestamp {
    type Output = TimestampResult<Timestamp>;
    fn sub(self, rhs: D) -> Self::Output {
        self.to_owned() - rhs
    }
}
impl Timestamp {
    pub const ZERO: Timestamp = Timestamp(0);
    pub const MIN: Timestamp = Timestamp(i64::MIN);
    pub const MAX: Timestamp = Timestamp(i64::MAX);
    pub const HOLOCHAIN_EPOCH: Timestamp = Timestamp(1640995200000000);
    pub fn max() -> Timestamp {
        Timestamp(i64::MAX)
    }
    pub fn from_micros(micros: i64) -> Self {
        Self(micros)
    }
    pub fn as_micros(&self) -> i64 {
        self.0
    }
    pub fn as_millis(&self) -> i64 {
        self.0 / 1000
    }
    pub fn as_seconds_and_nanos(&self) -> (i64, u32) {
        let secs = self.0 / MM;
        let nsecs = (self.0 % 1_000_000) * 1000;
        (secs, nsecs as u32)
    }
    pub fn checked_add(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
        let micros = rhs.as_micros();
        if micros <= i64::MAX as u128 {
            Some(Self(self.0.checked_add(micros as i64)?))
        } else {
            None
        }
    }
    pub fn checked_sub(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
        let micros = rhs.as_micros();
        if micros <= i64::MAX as u128 {
            Some(Self(self.0.checked_sub(micros as i64)?))
        } else {
            None
        }
    }
    pub fn saturating_add(&self, rhs: &core::time::Duration) -> Timestamp {
        self.checked_add(rhs).unwrap_or(Self::MAX)
    }
    pub fn saturating_sub(&self, rhs: &core::time::Duration) -> Timestamp {
        self.checked_sub(rhs).unwrap_or(Self::MIN)
    }
    pub fn saturating_from_dur(duration: &core::time::Duration) -> Self {
        Timestamp(std::cmp::min(duration.as_micros(), i64::MAX as u128) as i64)
    }
}
impl TryFrom<core::time::Duration> for Timestamp {
    type Error = error::TimestampError;
    fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
        Ok(Timestamp(
            value
                .as_micros()
                .try_into()
                .map_err(|_| error::TimestampError::Overflow)?,
        ))
    }
}
#[cfg(feature = "rusqlite")]
impl rusqlite::ToSql for Timestamp {
    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
        Ok(rusqlite::types::ToSqlOutput::Owned(self.0.into()))
    }
}
#[cfg(feature = "rusqlite")]
impl rusqlite::types::FromSql for Timestamp {
    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
        match value {
            rusqlite::types::ValueRef::Integer(i) => Ok(Self::from_micros(i)),
            _ => Err(rusqlite::types::FromSqlError::InvalidType),
        }
    }
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct InclusiveTimestampInterval {
    start: Timestamp,
    end: Timestamp,
}
impl InclusiveTimestampInterval {
    pub fn try_new(start: Timestamp, end: Timestamp) -> TimestampResult<Self> {
        if start > end {
            Err(TimestampError::OutOfOrder)
        } else {
            Ok(Self { start, end })
        }
    }
    pub fn start(&self) -> Timestamp {
        self.start
    }
    pub fn end(&self) -> Timestamp {
        self.end
    }
}
#[cfg(test)]
mod tests {
    use std::convert::TryInto;
    use super::*;
    const TEST_TS: &'static str = "2020-05-05T19:16:04.266431Z";
    #[test]
    fn timestamp_distance() {
        let t1 = Timestamp(i64::MAX); let d1: TimestampResult<chrono::DateTime<chrono::Utc>> = t1.try_into();
        assert_eq!(d1, Err(TimestampError::Overflow));
        let t2 = Timestamp(0) + core::time::Duration::new(0, 1000);
        assert_eq!(t2, Ok(Timestamp(1)));
    }
    #[test]
    fn micros_roundtrip() {
        for t in [Timestamp(1234567890), Timestamp(987654321)] {
            let micros = t.clone().as_micros();
            let r = Timestamp::from_micros(micros);
            assert_eq!(t.0, r.0);
            assert_eq!(t, r);
        }
    }
    #[test]
    fn test_timestamp_serialization() {
        use holochain_serialized_bytes::prelude::*;
        let t: Timestamp = TEST_TS.try_into().unwrap();
        let (secs, nsecs) = t.as_seconds_and_nanos();
        assert_eq!(secs, 1588706164);
        assert_eq!(nsecs, 266431000);
        assert_eq!(TEST_TS, &t.to_string());
        #[derive(Debug, serde::Serialize, serde::Deserialize, SerializedBytes)]
        struct S(Timestamp);
        let s = S(t);
        let sb = SerializedBytes::try_from(s).unwrap();
        let s: S = sb.try_into().unwrap();
        let t = s.0;
        assert_eq!(TEST_TS, &t.to_string());
    }
    #[test]
    fn test_timestamp_alternate_forms() {
        use holochain_serialized_bytes::prelude::*;
        decode::<_, Timestamp>(&encode(&(0u64)).unwrap()).unwrap();
        decode::<_, Timestamp>(&encode(&(i64::MAX as u64)).unwrap()).unwrap();
        assert!(decode::<_, Timestamp>(&encode(&(i64::MAX as u64 + 1)).unwrap()).is_err());
    }
    #[test]
    fn inclusive_timestamp_interval_test_new() {
        for (start, end) in vec![(0, 0), (-1, 0), (0, 1), (i64::MIN, i64::MAX)] {
            InclusiveTimestampInterval::try_new(Timestamp(start), Timestamp(end)).unwrap();
        }
        for (start, end) in vec![(0, -1), (1, 0), (i64::MAX, i64::MIN)] {
            assert!(
                super::InclusiveTimestampInterval::try_new(Timestamp(start), Timestamp(end))
                    .is_err()
            );
        }
    }
}