//! Job Identifiers and Conversion between them.
//!
//! A Job Identifier is a unique identifier for each job registered with the system.
//! It is used to identify a job and to route job calls to the appropriate job handler.
//!
//! A job ID can be anything that implements `Into<JobId>`, and it is implemented for various primitive types.

use alloc::string::ToString as _;
use core::fmt::Write as _;

/// Job Identifier.
#[derive(Clone, PartialEq, Eq, Hash, Copy)]
#[repr(C)]
pub struct JobId(pub [u64; 4]);

// Ensure that the size of `JobId` is the same as `[u64; 4]` always.
const _: () = {
    assert!(
        core::mem::size_of::<JobId>() == core::mem::size_of::<[u64; 4]>(),
        "JobId size changed, update transmutes"
    );
};

impl core::fmt::Debug for JobId {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_tuple("JobId").field(&self.to_string()).finish()
    }
}

impl core::fmt::Display for JobId {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for limb in &self.0 {
            write!(f, "{limb:016x}")?;
        }
        Ok(())
    }
}

impl Default for JobId {
    #[inline]
    fn default() -> Self {
        Self::ZERO
    }
}

impl JobId {
    /// The value zero.
    pub const ZERO: Self = Self([0; 4]);

    /// The smallest value that can be represented by [`JobId`].
    /// Alias for [`Self::ZERO`].
    pub const MIN: Self = Self::ZERO;

    /// The largest value that can be represented by [`JobId`].
    pub const MAX: Self = {
        let mut limbs = [u64::MAX; 4];
        limbs[4 - 1] &= u64::MAX;
        Self(limbs)
    };
}

macro_rules! impl_from_numbers {
    (
        $($ty:ty),*
    ) => {
        $(
            impl From<$ty> for JobId {
                #[inline]
                fn from(value: $ty) -> Self {
                    let mut id = [0; 4];
                    id[3] = value as u64;
                    Self(id)
                }
            }

            impl From<&$ty> for JobId {
                #[inline]
                fn from(value: &$ty) -> Self {
                    Self::from(*value)
                }
            }

            impl From<JobId> for $ty {
                #[inline]
                fn from(value: JobId) -> Self {
                    value.0[3] as $ty
                }
            }
        )*
	};

    ($(wide $ty:ty),*) => {
        $(
            impl From<$ty> for JobId {
                #[inline]
                fn from(value: $ty) -> Self {
                    let mut id = [0; 4];
                    id[3] = value as u64;
                    id[2] = (value >> 64) as u64;
                    Self(id)
                }
            }

            impl From<&$ty> for JobId {
                #[inline]
                fn from(value: &$ty) -> Self {
                    Self::from(*value)
                }
            }

            impl From<JobId> for $ty {
                #[inline]
                fn from(value: JobId) -> Self {
                    (value.0[2] as $ty) << 64 | value.0[3] as $ty
                }
            }
        )*
    };
}

impl_from_numbers!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);
impl_from_numbers!(wide u128, wide i128);

impl From<[u8; 32]> for JobId {
    #[inline]
    fn from(value: [u8; 32]) -> Self {
        Self([
            u64::from_le_bytes([
                value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
            ]),
            u64::from_le_bytes([
                value[8], value[9], value[10], value[11], value[12], value[13], value[14],
                value[15],
            ]),
            u64::from_le_bytes([
                value[16], value[17], value[18], value[19], value[20], value[21], value[22],
                value[23],
            ]),
            u64::from_le_bytes([
                value[24], value[25], value[26], value[27], value[28], value[29], value[30],
                value[31],
            ]),
        ])
    }
}

impl From<JobId> for [u8; 32] {
    #[inline]
    fn from(value: JobId) -> Self {
        let a = value.0[0].to_le_bytes();
        let b = value.0[1].to_le_bytes();
        let c = value.0[2].to_le_bytes();
        let d = value.0[3].to_le_bytes();
        [
            a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], b[0], b[1], b[2], b[3], b[4], b[5],
            b[6], b[7], c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], d[0], d[1], d[2], d[3],
            d[4], d[5], d[6], d[7],
        ]
    }
}

impl From<&[u8]> for JobId {
    #[inline]
    fn from(value: &[u8]) -> Self {
        use tiny_keccak::Hasher;
        let mut hasher = tiny_keccak::Keccak::v256();
        hasher.update(value);
        let mut out = [0u8; 32];
        hasher.finalize(&mut out);
        Self::from(out)
    }
}

impl From<&str> for JobId {
    #[inline]
    fn from(value: &str) -> Self {
        Self::from(value.as_bytes())
    }
}

impl From<alloc::string::String> for JobId {
    #[inline]
    fn from(value: alloc::string::String) -> Self {
        Self::from(value.as_str())
    }
}

impl From<JobId> for alloc::string::String {
    #[inline]
    fn from(value: JobId) -> Self {
        let hash: [u8; 32] = value.into();
        let mut result = alloc::string::String::with_capacity(64);
        for byte in &hash {
            let _ = write!(result, "{byte:02x}");
        }
        result
    }
}

impl From<&alloc::string::String> for JobId {
    #[inline]
    fn from(value: &alloc::string::String) -> Self {
        Self::from(value.as_str())
    }
}

impl From<alloc::vec::Vec<u8>> for JobId {
    #[inline]
    fn from(value: alloc::vec::Vec<u8>) -> Self {
        Self::from(&value)
    }
}

impl From<JobId> for alloc::vec::Vec<u8> {
    #[inline]
    fn from(value: JobId) -> Self {
        let hash: [u8; 32] = value.into();
        hash.to_vec()
    }
}

impl From<&alloc::vec::Vec<u8>> for JobId {
    #[inline]
    fn from(value: &alloc::vec::Vec<u8>) -> Self {
        Self::from(value.as_slice())
    }
}

impl From<()> for JobId {
    #[inline]
    fn from((): ()) -> Self {
        Self::ZERO
    }
}

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

    macro_rules! test_from_into_numbers {
        ($($ty:ty),*) => {
            $(
                let num: $ty = 42;
                let job_id = JobId::from(num);
                assert_eq!(job_id, JobId::from(num));
                assert_eq!(num, <$ty>::from(job_id));

                let num: $ty = <$ty>::MAX;
                let job_id = JobId::from(num);
                assert_eq!(job_id, JobId::from(num));
                assert_eq!(num, <$ty>::from(job_id));

                let num: $ty = <$ty>::MIN;
                let job_id = JobId::from(num);
                assert_eq!(job_id, JobId::from(num));
                assert_eq!(num, <$ty>::from(job_id));
            )*
        };
    }

    #[test]
    fn job_id_works() {
        let id = JobId::from("test");
        assert_eq!(id, JobId::from("test"));
        assert_ne!(id, JobId::from("test2"));
    }

    #[test]
    fn it_works_with_numbers() {
        test_from_into_numbers!(
            u8, i8, u16, i16, u32, i32, u64, i64, usize, isize, u128, i128
        );
    }
}
