//! Binary data conversion utilities.

use crate::err::*;
use std::{convert::{TryFrom, TryInto}, fmt, num::NonZeroU16};

#[doc(hidden)]
#[macro_export]
macro_rules! rd_impl {
	// worker, creates let statement for 16 bit numbers
	(W $b:expr, $pth:path, $nam:ident, $n:expr) => {
		let $nam = $pth([$b[$n], $b[$n + 1]]);
	};

	// worker, creates let statement for 32 bit numbers
	(D $b:expr, $pth:path, $nam:ident, $n:expr) => {
		let $nam = $pth([$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]);
	};

	// big endian, u16
	(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, u16) => {
		$crate::rd_impl!(W $b, u16::from_be_bytes, $nam, $n + $at);
	};

	// big endian, i16
	(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, i16) => {
		$crate::rd_impl!(W $b, i16::from_be_bytes, $nam, $n + $at);
	};

	// big endian, u32
	(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, u32) => {
		$crate::rd_impl!(D $b, u32::from_be_bytes, $nam, $n + $at);
	};

	// big endian, i32
	(BIG, $b:expr, $at:expr, $n:expr; $nam:ident, i32) => {
		$crate::rd_impl!(D $b, i32::from_be_bytes, $nam, $n + $at);
	};

	// little endian, u16
	(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, u16) => {
		$crate::rd_impl!(W $b, u16::from_le_bytes, $nam, $n + $at);
	};

	// little endian, i16
	(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, i16) => {
		$crate::rd_impl!(W $b, i16::from_le_bytes, $nam, $n + $at);
	};

	// little endian, u32
	(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, u32) => {
		$crate::rd_impl!(D $b, u32::from_le_bytes, $nam, $n + $at);
	};

	// little endian, i32
	(LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, i32) => {
		$crate::rd_impl!(D $b, i32::from_le_bytes, $nam, $n + $at);
	};

	// either endianness, Angle
	($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Angle) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
		let $nam = $crate::fixed::Angle::from_bits($nam);
	};

	// either endianness, Fixed
	($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Fixed) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, u32);
		let $nam = $crate::fixed::Fixed::from_bits($nam);
	};

	// either endianness, Unit
	($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Unit) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
		let $nam = $crate::fixed::Unit::from_bits($nam);
	};

	// either endianness, OptU16
	($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, OptU16) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
		let $nam = $crate::bin::OptU16::from($nam);
	};

	// either endianness, u16 -> usize
	($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, usize, u16) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, u16);
		let $nam = usize::from($nam);
	};

	// either endianness, u32 -> usize
	($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, usize, u32) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, u32);
		let $nam = $crate::bin::usize_from_u32($nam);
	};

	// either endianness, enum type with TryFrom
	($e:ident, $b:expr, $at:expr, $n:expr;
	 $nam:ident, enum, $et:ident$(::$etc:ident)*, $t:ident) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, $t);
		let $nam: $et$(::$etc)* = std::convert::TryFrom::try_from($nam)?;
	};

	// either endianness, bitflag type
	($e:ident, $b:expr, $at:expr, $n:expr;
	 $nam:ident, flag, $ft:ident$(::$ftc:ident)*, $t:ident) => {
		$crate::rd_impl!($e, $b, $at, $n; $nam, $t);
		let $nam = flag_ok!($ft$(::$ftc)*, $nam)?;
	};

	// no endianness, u8
	($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, u8) => {
		let $nam = $b[$n + $at];
	};

	// no endianness, i8
	($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, i8) => {
		let $nam = $b[$n + $at] as i8;
	};

	// no endianness, [u8]
	($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, u8) => {
		let $nam = &$b[$n + $at..$n + $at + $rn];
	};

	// no endianness, Ident
	($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Ident) => {
		$crate::rd_impl!(D $b, Ident, $nam, $n + $at);
	};

	// no endianness, fn([u8]) -> T
	($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr;
	 $nam:ident, no_try, $f:expr) => {
		let $nam = $f(&$b[$n + $at..$n + $at + $rn]);
	};

	// no endianness, fn([u8]) -> Result<T>
	($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, $f:expr) => {
		let $nam = $f(&$b[$n + $at..$n + $at + $rn])?;
	};
}

/// Reads structured data from a byte slice.
///
/// # Syntax
///
/// First start by specifying the basic information, using the syntax:
/// `endian: ENDIAN, buf: BUFFER, size: SIZE, start: START,` where:
///
/// - `ENDIAN` is `BIG` or `LITTLE` for big- or little-endian respectively.
/// - `BUFFER` is a `u8` slice to read data from. This expression will be
///   evaluated many times, so be careful when specifying it.
/// - `SIZE` is an expression specifying the last index that should be used by
///   this macro in `BUFFER`.
/// - `START` is an expression specifying the index to start at in `BUFFER`. All
///   indices and sizes will have this added to them.
///
/// Following that is a block with the syntax `data { ... }`. All lines within
/// this block have the syntax `let NAME = TYPE[INDEX] OPTS;` where:
///
/// - `NAME` is the binding to put the resulting data in.
/// - `TYPE` is one of:
///   - `u8` or `i8`: one byte will be read at `INDEX`. If `INDEX` is a range,
///     this will be a slice into `BUFFER` instead.
///   - `u16` or `i16`: two bytes will be read at `INDEX` with `from_*_bytes`.
///     If `OPTS` is `usize`, this converts the resulting number to `usize` by
///     using `usize::from`.
///   - `u32` or `i32`: four bytes will be read at `INDEX` with `from_*_bytes`.
///     If `OPTS` is `usize`, this converts the resulting number to `usize` by
///     using `usize_from_u32`.
///   - `Ident`: four bytes will be read at `INDEX` into an array, disregarding
///     endianness, creating an `Ident` object.
///   - `Angle`: same as `u16`, but the result is passed to
///     `fixed::Angle::from_bits`, resulting in a `fixed::Angle` object.
///   - `Fixed`: same as `u32`, but the result is passed to
///     `fixed::Fixed::from_bits`, resulting in a `fixed::Fixed` object.
///   - `Unit`: same as `u16`, but the result is passed to
///     `fixed::Unit::from_bits`, resulting in a `fixed::Unit` object.
///   - `OptU16`: same as `u16`, but the result is passed to `OptU16::from`,
///     resulting in an `OptU16` object.
///   - The name of a function, which is passed the index range as its only
///     argument. The function's result has the `?` operator applied to it,
///     unless `OPTS` is `no_try`.
/// - `OPT`, if not one of the things listed above, may be `enum TYPE` to apply
///   `TryFrom<TYPE>`, or `flag TYPE` to apply a bitfield made by `c_bitfield!`.
/// - `INDEX` is either an integer literal which must be representable as
///   `usize`, or a range with the syntax `INDEX; SIZE` denoting the beginning
///   and size of the range.
///
/// # Panics
///
/// This macro will not panic unless any index expression used exceeds or
/// equals `SIZE + START`, or a function passed to it panics.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate maraiah;
/// # use maraiah::err::*;
/// #
/// # fn main() -> ResultS<()>
/// # {
/// let buffer = &[4, 0, 2, 0, 0, 0, 6];
///
/// read_data! {
/// 	endian: LITTLE, buf: buffer, size: 7, start: 0, data {
/// 		let four = u16[0];
/// 		let two  = u32[2];
/// 		let six  = u8[6];
/// 		let byte = u8[2; 4];
/// 	}
/// }
///
/// assert_eq!(four, 4_u16);
/// assert_eq!(two, 2_u32);
/// assert_eq!(six, 6_u8);
/// assert_eq!(byte, &[2, 0, 0, 0]);
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! read_data {
	(
		endian: $e:ident, buf: $b:expr, size: $sz:expr, start: $at:expr, data {
			$(let $nam:ident = $t:ident$(::$tc:ident)*[$n:expr $(; $rn:expr)?]
			  $($ex:ident$(::$exc:ident)*)*;)*
		}
	) => {
		$crate::bin::check_data($b, $at + $sz)?;
		$($crate::rd_impl!($e, $b, $at, $n;
		                   $($rn;)? $nam, $($ex$(::$exc)*,)* $t$(::$tc)*);)*
	};
}

/// Checks if there is enough data in `b`.
///
/// # Errors
///
/// Returns `Err` if `b.len()` is less than `sz`.
#[inline]
pub fn check_data<T>(b: &[T], sz: usize) -> ResultS<()>
{
	if b.len() < sz {
		Err(err_msg("not enough data"))
	} else {
		Ok(())
	}
}

/// Casts a `u32` to a `usize`.
///
/// # Panics
///
/// Will panic if the platform does not have a 32-bit `usize`. In this case,
/// the application is not supported, but will still run and panic at runtime.
///
/// # Examples
///
/// ```
/// use maraiah::bin::usize_from_u32;
///
/// assert_eq!(usize_from_u32(777u32), 777usize);
/// ```
#[inline]
pub fn usize_from_u32(n: u32) -> usize
{
	usize::try_from(n).expect("platform is 16-bit")
}

/// Creates an `Ident` from a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::bin::{ident, Ident};
///
/// assert_eq!(ident(b"POLY"), Ident([b'P', b'O', b'L', b'Y']));
/// ```
#[inline]
pub fn ident(b: &[u8]) -> Ident
{
	Ident(b[0..4].try_into().expect("not enough data"))
}

/// Applies `u32::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::bin::u32b;
///
/// assert_eq!(u32b(&[0x00, 0x0B, 0xDE, 0x31]), 777_777u32);
/// ```
#[inline]
pub fn u32b(b: &[u8]) -> u32
{
	u32::from_be_bytes(b[0..4].try_into().expect("not enough data"))
}

/// Applies `u16::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::bin::u16b;
///
/// assert_eq!(u16b(&[0x1E, 0x61]), 7_777u16);
/// ```
#[inline]
pub fn u16b(b: &[u8]) -> u16
{
	u16::from_be_bytes(b[0..2].try_into().expect("not enough data"))
}

/// Applies `i32::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::bin::i32b;
///
/// assert_eq!(i32b(&[0xFF, 0x89, 0x52, 0x0F]), -7_777_777i32);
/// ```
#[inline]
pub fn i32b(b: &[u8]) -> i32
{
	i32::from_be_bytes(b[0..4].try_into().expect("not enough data"))
}

/// Applies `i16::from_be_bytes` to a slice.
///
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::bin::i16b;
///
/// assert_eq!(i16b(&[0xE1, 0x9F]), -7_777i16);
/// ```
#[inline]
pub fn i16b(b: &[u8]) -> i16
{
	i16::from_be_bytes(b[0..2].try_into().expect("not enough data"))
}

/// Applies a read function over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When there is no
/// data left in `b`, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
///
/// # Examples
///
/// ```
/// use maraiah::{bin::{rd_array, u16b}, err::*};
///
/// fn read_a_u16(b: &[u8]) -> ResultS<(u16, usize)> {Ok((u16b(b), 2))}
///
/// let inp = &[0x1E, 0x61, 0x03, 0x09];
/// assert_eq!(rd_array(inp, read_a_u16).unwrap(), vec![7_777u16, 777u16]);
/// ```
pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
	where T: Sized,
	      F: Fn(&[u8]) -> ResultS<(T, usize)>
{
	let mut v = Vec::new();
	let mut p = 0;

	while p < b.len() {
		let (r, s) = read(&b[p..])?;
		v.push(r);
		p += s;
	}

	Ok(v)
}

/// Applies a read function a number of times over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When `n` elements
/// have been read, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_array_num<T, F>(b: &[u8],
                          n: usize,
                          read: F) -> ResultS<(Vec<T>, usize)>
	where T: Sized,
	      F: Fn(&[u8]) -> ResultS<(T, usize)>
{
	let mut v = Vec::with_capacity(n);
	let mut p = 0;

	for _ in 0..n {
		let (r, s) = read(&b[p..])?;
		v.push(r);
		p += s;
	}

	Ok((v, p))
}

/// Applies a read function over a slice with an offset table.
///
/// Applies `read` over each offset in `b`, of which there are `num` amount of
/// starting at `p`, resulting in a vector of its return values. Each iteration
/// reads a 32-bit big endian offset from `b`, and then passes a slice of `b`
/// to `read` starting at that offset. When all offsets have been read, the
/// function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function panics.
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_ofstable<T, F>(b: &[u8],
                         mut p: usize,
                         num: usize,
                         read: F) -> ResultS<Vec<T>>
	where T: Sized,
	      F: Fn(&[u8]) -> ResultS<T>
{
	let mut v = Vec::with_capacity(num);

	for _ in 0..num {
		let ofs = usize_from_u32(u32b(&b[p..p + 4]));
		check_data(b, ofs)?;
		v.push(read(&b[ofs..])?);
		p += 4;
	}

	Ok(v)
}

impl From<u16> for OptU16
{
	#[inline]
	fn from(n: u16) -> Self
	{
		if n == u16::max_value() {
			Self(None)
		} else {
			Self(NonZeroU16::new(n + 1))
		}
	}
}

impl Into<u16> for OptU16
{
	/// Returns the `u16` representation.
	///
	/// # Examples
	///
	/// ```
	/// use maraiah::bin::OptU16;
	///
	/// let u16_max = u16::max_value();
	///
	/// // These type annotations are necessary.
	///
	/// assert_eq!(<OptU16 as Into<u16>>::into(OptU16::from(500u16)), 500u16);
	/// assert_eq!(<OptU16 as Into<u16>>::into(OptU16::from(u16_max)), u16_max);
	/// assert_eq!(<OptU16 as Into<u16>>::into(OptU16::from(0u16)), 0u16);
	/// ```
	#[inline]
	fn into(self) -> u16
	{
		match self.0 {
			None    => u16::max_value(),
			Some(n) => n.get() - 1,
		}
	}
}

impl OptU16
{
	/// Creates an `OptU16` representing `None`.
	///
	/// # Examples
	///
	/// ```
	/// use maraiah::bin::OptU16;
	///
	/// assert_eq!(OptU16::none(), OptU16::from(u16::max_value()));
	/// ```
	#[inline]
	pub const fn none() -> Self {Self(None)}

	/// Returns the `Option` representation.
	///
	/// # Examples
	///
	/// ```
	/// use maraiah::bin::OptU16;
	///
	/// assert_eq!(OptU16::from(500u16).get(), Some(500u16));
	/// assert_eq!(OptU16::from(u16::max_value()).get(), None);
	/// assert_eq!(OptU16::from(0u16).get(), Some(0u16));
	/// ```
	#[inline]
	pub fn get(self) -> Option<u16>
	{
		match self.0 {
			None    => None,
			Some(n) => Some(n.get() - 1),
		}
	}

	/// Return the memory representation of this integer as a byte array
	/// in big-endian (network) byte order.
	#[inline]
	pub fn to_be_bytes(self) -> [u8; 2]
	{
		<Self as Into<u16>>::into(self).to_be_bytes()
	}

	/// Return the memory representation of this integer as a byte array
	/// in little-endian byte order.
	#[inline]
	pub fn to_le_bytes(self) -> [u8; 2]
	{
		<Self as Into<u16>>::into(self).to_le_bytes()
	}
}

impl fmt::Display for OptU16
{
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
	{
		match self.get() {
			None    => write!(f, "None"),
			Some(n) => write!(f, "Some({})", n),
		}
	}
}

impl fmt::Debug for OptU16
{
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
	{
		match self.get() {
			None    => write!(f, "OptU16::none()"),
			Some(n) => write!(f, "OptU16::from({})", n),
		}
	}
}

impl PartialEq<[u8; 4]> for Ident
{
	#[inline]
	fn eq(&self, o: &[u8; 4]) -> bool {self.0 == *o}
}

impl<'a> PartialEq<[u8; 4]> for &'a Ident
{
	#[inline]
	fn eq(&self, o: &[u8; 4]) -> bool {PartialEq::eq(*self, o)}
}

impl<'a> PartialEq<&'a [u8; 4]> for Ident
{
	#[inline]
	fn eq(&self, o: &&'a [u8; 4]) -> bool {PartialEq::eq(self, *o)}
}

/// A four-character-code identifier.
///
/// # Examples
///
/// ```
/// use maraiah::bin::Ident;
///
/// assert_eq!(Ident(*b"POLY").0, *b"POLY");
/// assert_eq!(Ident(*b"POLY"), *b"POLY");
/// assert_eq!(Ident(*b"POLY"), b"POLY");
/// assert_eq!(&Ident(*b"POLY"), *b"POLY");
/// assert_eq!(&Ident(*b"POLY"), b"POLY");
/// ```
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]);

/// An object identified by a `u16` which may be `u16::max_value()` to
/// represent a nulled value.
#[derive(Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct OptU16(Option<NonZeroU16>);

// EOF
