//! Time types used by the Bitmessage protocol.

use std::{
    io::{self, Read, Write},
    time::{Duration as StdDuration, SystemTime, UNIX_EPOCH},
};

use crate::io::{LenBm, ReadFrom, WriteTo};

/// Represents a span of time used by the Bitmessage protocol.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Duration(u64);

impl From<u64> for Duration {
    fn from(secs: u64) -> Self {
        Self(secs)
    }
}

impl Duration {
    /// Constructs a duration from a number of seconds.
    pub const fn new(secs: u64) -> Self {
        Self(secs)
    }

    /// Returns the number of seconds as `u64`.
    pub fn as_secs(self) -> u64 {
        self.0
    }
}

impl WriteTo for Duration {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        self.0.write_to(w)
    }
}

impl ReadFrom for Duration {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        Ok(Self(u64::read_from(r)?))
    }
}

#[test]
fn test_duration_write_to() {
    let test: Duration = 0x0123_4567_89ab_cdef.into();
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
    assert_eq!(bytes, expected);
}

#[test]
fn test_duration_read_from() {
    use std::io::Cursor;

    let mut bytes = Cursor::new([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
    let test = Duration::read_from(&mut bytes).unwrap();
    let expected: Duration = 0x0123_4567_89ab_cdef.into();
    assert_eq!(test, expected);
}

/// Represents a point in time used by the Bitmessage protocol.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Time(u64);

impl From<u64> for Time {
    fn from(secs: u64) -> Self {
        Self(secs)
    }
}

impl From<SystemTime> for Time {
    fn from(time: SystemTime) -> Self {
        Self(
            time.duration_since(UNIX_EPOCH)
                .expect("Clock set before epoch")
                .as_secs(),
        )
    }
}

impl From<Time> for SystemTime {
    fn from(time: Time) -> Self {
        UNIX_EPOCH + StdDuration::from_secs(time.0)
    }
}

impl Time {
    /// Constructs a current time.
    pub fn now() -> Self {
        SystemTime::now().into()
    }

    /// Constructs a time from a number of seconds since the UNIX epoch.
    pub const fn new(secs: u64) -> Self {
        Self(secs)
    }

    /// Returns the number of seconds since the UNIX epoch
    /// this object represents as `u64`.
    pub fn as_secs(self) -> u64 {
        self.0
    }

    /// Checked addition.
    /// Computes `self + duration`,
    /// returning `None` if overflow occurred.
    pub fn checked_add(self, duration: Duration) -> Option<Self> {
        match self.0.checked_add(duration.0) {
            Some(value) => Some(Self(value)),
            None => None,
        }
    }
}

impl WriteTo for Time {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        self.0.write_to(w)
    }
}

impl ReadFrom for Time {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        Ok(Self(u64::read_from(r)?))
    }
}

impl LenBm for Time {
    fn len_bm(&self) -> usize {
        self.0.len_bm()
    }
}

#[test]
fn test_time_write_to() {
    let test: Time = 0x0123_4567_89ab_cdef.into();
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
    assert_eq!(bytes, expected);
}

#[test]
fn test_time_read_from() {
    use std::io::Cursor;

    let mut bytes = Cursor::new(vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
    let test = Time::read_from(&mut bytes).unwrap();
    let expected: Time = 0x0123_4567_89ab_cdef.into();
    assert_eq!(test, expected);
}
