#![cfg(feature = "chrono")]

use super::super::Error;
use super::super::{DeserializeRevisioned, Revisioned, SerializeRevisioned};
use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveTime, Timelike, Utc, offset::TimeZone};

impl SerializeRevisioned for DateTime<Utc> {
	#[inline]
	fn serialize_revisioned<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
		self.timestamp().serialize_revisioned(writer)?;
		self.timestamp_subsec_nanos().serialize_revisioned(writer)?;
		Ok(())
	}
}

impl DeserializeRevisioned for DateTime<Utc> {
	#[inline]
	fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, Error> {
		let secs = <i64 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let nano = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		Utc.timestamp_opt(secs, nano)
			.single()
			.ok_or_else(|| Error::Deserialize("invalid datetime".to_string()))
	}
}

impl Revisioned for DateTime<Utc> {
	#[inline]
	fn revision() -> u16 {
		1
	}
}

impl SerializeRevisioned for NaiveDate {
	#[inline]
	fn serialize_revisioned<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
		self.year().serialize_revisioned(writer)?;
		self.month().serialize_revisioned(writer)?;
		self.day().serialize_revisioned(writer)?;
		Ok(())
	}
}

impl DeserializeRevisioned for NaiveDate {
	#[inline]
	fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, Error> {
		let year = <i32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let month = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let day = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		NaiveDate::from_ymd_opt(year, month, day)
			.ok_or_else(|| Error::Deserialize("invalid date".to_string()))
	}
}

impl Revisioned for NaiveDate {
	#[inline]
	fn revision() -> u16 {
		1
	}
}

impl SerializeRevisioned for NaiveTime {
	#[inline]
	fn serialize_revisioned<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
		self.hour().serialize_revisioned(writer)?;
		self.minute().serialize_revisioned(writer)?;
		self.second().serialize_revisioned(writer)?;
		self.nanosecond().serialize_revisioned(writer)?;
		Ok(())
	}
}

impl DeserializeRevisioned for NaiveTime {
	#[inline]
	fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, Error> {
		let hour = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let minute = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let second = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let nano = <u32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		NaiveTime::from_hms_nano_opt(hour, minute, second, nano)
			.ok_or_else(|| Error::Deserialize("invalid time".to_string()))
	}
}

impl Revisioned for NaiveTime {
	#[inline]
	fn revision() -> u16 {
		1
	}
}

impl SerializeRevisioned for Duration {
	#[inline]
	fn serialize_revisioned<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
		let mut secs = self.num_seconds();
		let mut nano = self.subsec_nanos();

		if nano < 0 {
			secs = secs
				.checked_sub(1)
				.ok_or_else(|| Error::Serialize("invalid duration".to_string()))?;
			nano = nano
				.checked_add(1_000_000_000)
				.ok_or_else(|| Error::Serialize("invalid duration".to_string()))?;
		}

		secs.serialize_revisioned(writer)?;
		nano.serialize_revisioned(writer)?;

		Ok(())
	}
}

impl DeserializeRevisioned for Duration {
	#[inline]
	fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, Error> {
		let secs = <i64 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let nano = <i32 as DeserializeRevisioned>::deserialize_revisioned(reader)?;
		let nano =
			u32::try_from(nano).map_err(|_| Error::Deserialize("invalid duration".to_string()))?;

		Duration::new(secs, nano).ok_or_else(|| Error::Deserialize("invalid duration".to_string()))
	}
}

impl Revisioned for Duration {
	#[inline]
	fn revision() -> u16 {
		1
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_datetime_min() {
		let val = DateTime::<Utc>::MIN_UTC;
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 10);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out =
			<DateTime<Utc> as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
				.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_datetime_max() {
		let val = DateTime::<Utc>::MAX_UTC;
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 14);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out =
			<DateTime<Utc> as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
				.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_naive_date_min() {
		let val = NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 3);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out = <NaiveDate as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_naive_date_max() {
		let val = NaiveDate::from_ymd_opt(9999, 12, 31).unwrap();
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 5);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out = <NaiveDate as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_naive_time_min() {
		let val = NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap();
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 4);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 16);
		let out = <NaiveTime as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_naive_time_max() {
		let val = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap();
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 8);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 16);
		let out = <NaiveTime as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_duration_min() {
		let val = Duration::MIN;
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 14);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out = <Duration as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_duration_zero() {
		let val = Duration::zero();
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 2);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out = <Duration as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}

	#[test]
	fn test_duration_max() {
		let val = Duration::MAX;
		let mut mem: Vec<u8> = vec![];
		val.serialize_revisioned(&mut mem).unwrap();
		#[cfg(not(feature = "fixed-width-encoding"))]
		assert_eq!(mem.len(), 14);
		#[cfg(feature = "fixed-width-encoding")]
		assert_eq!(mem.len(), 12);
		let out = <Duration as DeserializeRevisioned>::deserialize_revisioned(&mut mem.as_slice())
			.unwrap();
		assert_eq!(val, out);
	}
}
