use crate::ffi::{ref_to_ptr, ref_to_ptr_mut};

use super::super::sha2::{DIGEST256_EMPTY, Digest256, SHA2_256_BLOCK_SIZE};

#[repr(C)]
#[allow(non_camel_case_types)]
#[allow(unused)]
#[allow(clippy::enum_variant_names)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum SHA2Outcome {
    shaSuccess = 0,
    shaNull,         /* Null pointer parameter */
    shaInputTooLong, /* input data too long */
    shaStateError,   /* called Input after Result */
    shaBadArg,       /* Bad argument was given to a function */
}

#[repr(C)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct Sha2_256Context {
    pub intermediate_hash: Digest256,
    pub length_low: u32,
    pub length_high: u32,
    pub message_block_index: i16,
    pub message_block: [u8; SHA2_256_BLOCK_SIZE],
    pub computed: bool,
    pub corrupted: bool,
}

impl Sha2_256Context {
    pub fn new() -> Self {
        Self {
            intermediate_hash: DIGEST256_EMPTY,
            length_low: 0,
            length_high: 0,
            message_block_index: 0,
            message_block: [0; SHA2_256_BLOCK_SIZE],
            computed: false,
            corrupted: false,
        }
    }
}

impl Default for Sha2_256Context {
    fn default() -> Self {
        Self::new()
    }
}

mod ffi {
    use super::*;
    unsafe extern "C" {
        pub fn sha2_256_oneshot(data: *const u8, len: usize, digest: *mut Digest256)
        -> SHA2Outcome;
        pub fn sha2_256_reset(context: *mut Sha2_256Context) -> SHA2Outcome;
        pub fn sha2_256_input(
            context: *mut Sha2_256Context,
            data: *const u8,
            len: usize,
        ) -> SHA2Outcome;
        pub fn sha2_256_result(
            context: *mut Sha2_256Context,
            digest: *mut Digest256,
        ) -> SHA2Outcome;

        pub fn sha2_256_oneshot_n_times(n: u32, data: *const u8, len: usize);
    }
}

#[inline(always)]
pub fn sha2_256_oneshot(data: &[u8]) -> Result<Digest256, SHA2Outcome> {
    let mut digest: Digest256 = DIGEST256_EMPTY;
    let res =
        unsafe { ffi::sha2_256_oneshot(ref_to_ptr(data), data.len(), ref_to_ptr_mut(&mut digest)) };
    to_res(digest, res)
}

#[inline(always)]
pub fn sha2_256_reset(context: &mut Sha2_256Context) -> Result<(), SHA2Outcome> {
    let res = unsafe { ffi::sha2_256_reset(ref_to_ptr_mut(context)) };
    to_res((), res)
}

#[inline(always)]
pub fn sha2_256_input(context: &mut Sha2_256Context, data: &[u8]) -> Result<(), SHA2Outcome> {
    let res = unsafe { ffi::sha2_256_input(ref_to_ptr_mut(context), ref_to_ptr(data), data.len()) };
    to_res((), res)
}

#[inline(always)]
pub fn sha2_256_result(context: &mut Sha2_256Context) -> Result<Digest256, SHA2Outcome> {
    let mut digest: Digest256 = DIGEST256_EMPTY;
    let res = unsafe { ffi::sha2_256_result(ref_to_ptr_mut(context), ref_to_ptr_mut(&mut digest)) };
    to_res(digest, res)
}

#[inline(always)]
pub fn sha2_256_oneshot_n_times(n: u32, data: &[u8]) {
    unsafe {
        ffi::sha2_256_oneshot_n_times(n, ref_to_ptr(data), data.len());
    };
}

#[inline(always)]
pub(crate) fn to_res<T>(if_ok: T, res: SHA2Outcome) -> Result<T, SHA2Outcome> {
    if res == SHA2Outcome::shaSuccess {
        Ok(if_ok)
    } else {
        Err(res)
    }
}

// NOTE: this SHA2_256 implementation does not suit core::hash::Hasher in Rust, since that expects
// a finish method with &self and that the hasher can be used after finish.

#[cfg(test)]
mod test {
    use crate::hash::Digest256;
    use crate::hash::ffi::sha2_256_oneshot;
    use crate::hash::test::TEST_VALUES;

    #[test]
    fn test_sha2_ffi_check() {
        for (input, expected_output) in TEST_VALUES.iter().copied() {
            assert_eq!(sha2_256_oneshot(input.as_bytes()), Ok(expected_output))
        }
    }
}
