#![allow(non_snake_case, unused)]

mod common;

use osom_lib_hashes::traits::HashFunction;

use rstest::rstest;

struct Sha2Case {
    pub input: &'static [u8],
    pub output: &'static [u8],
}

const C1: Sha2Case = Sha2Case {
    input: b"",
    output: &[
        0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae,
        0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
    ],
};
const C2: Sha2Case = Sha2Case {
    input: b"ab",
    output: &[
        0xfb, 0x8e, 0x20, 0xfc, 0x2e, 0x4c, 0x3f, 0x24, 0x8c, 0x60, 0xc3, 0x9b, 0xd6, 0x52, 0xf3, 0xc1, 0x34, 0x72,
        0x98, 0xbb, 0x97, 0x7b, 0x8b, 0x4d, 0x59, 0x03, 0xb8, 0x50, 0x55, 0x62, 0x06, 0x03,
    ],
};
const C3: Sha2Case = Sha2Case {
    input: b"aB",
    output: &[
        0x04, 0x2a, 0xc5, 0xb8, 0xda, 0x40, 0x52, 0x54, 0x5d, 0x4b, 0x28, 0xfe, 0x22, 0x39, 0x94, 0x71, 0x14, 0x95,
        0xbe, 0x3c, 0xde, 0xeb, 0x8a, 0x1f, 0x3e, 0x2a, 0x9d, 0x7d, 0x8b, 0xba, 0xe8, 0x7c,
    ],
};
const C4: Sha2Case = Sha2Case {
    input: b"Ab",
    output: &[
        0x02, 0x5b, 0x55, 0x73, 0xcb, 0x68, 0xf3, 0xaf, 0x8b, 0x09, 0xad, 0x8f, 0x67, 0x85, 0x55, 0xec, 0xb1, 0x85,
        0xf7, 0x83, 0x2b, 0x0d, 0x15, 0x68, 0x00, 0xf4, 0x0c, 0x30, 0x06, 0x49, 0xa5, 0x6c,
    ],
};
const C5: Sha2Case = Sha2Case {
    input: b"AB",
    output: &[
        0x38, 0x16, 0x4f, 0xbd, 0x17, 0x60, 0x3d, 0x73, 0xf6, 0x96, 0xb8, 0xb4, 0xd7, 0x26, 0x64, 0xd7, 0x35, 0xbb,
        0x6a, 0x7c, 0x88, 0x57, 0x76, 0x87, 0xfd, 0x2a, 0xe3, 0x3f, 0xd6, 0x96, 0x41, 0x53,
    ],
};
const C6: Sha2Case = Sha2Case {
    input: b"0123456789abcde",
    output: &[
        0x02, 0x76, 0x87, 0xe8, 0x7a, 0xb0, 0x72, 0xc7, 0x78, 0xc8, 0xf2, 0xe6, 0x61, 0x77, 0xfb, 0x78, 0xc6, 0xaa,
        0x19, 0x95, 0x29, 0x87, 0xb9, 0x7b, 0x31, 0x40, 0xb6, 0x8e, 0xbd, 0x5f, 0x90, 0xea,
    ],
};
const C7: Sha2Case = Sha2Case {
    input: b"0123456789abcdef",
    output: &[
        0x9f, 0x9f, 0x51, 0x11, 0xf7, 0xb2, 0x7a, 0x78, 0x1f, 0x1f, 0x1d, 0xdd, 0xe5, 0xeb, 0xc2, 0xdd, 0x2b, 0x79,
        0x6b, 0xfc, 0x73, 0x65, 0xc9, 0xc2, 0x8b, 0x54, 0x8e, 0x56, 0x41, 0x76, 0x92, 0x9f,
    ],
};
const C8: Sha2Case = Sha2Case {
    input: b"0123456789abcdefg",
    output: &[
        0x83, 0x78, 0xb1, 0x9d, 0x69, 0x3e, 0xe8, 0x9e, 0x2c, 0xa5, 0x94, 0xb6, 0xec, 0x0a, 0xbf, 0xaa, 0xdc, 0x4f,
        0xe1, 0x83, 0x1e, 0xac, 0xa2, 0xf7, 0x48, 0xf0, 0xb7, 0x1e, 0xde, 0x38, 0x89, 0x2e,
    ],
};
const C9: Sha2Case = Sha2Case {
    input: b"0123456789abcdefgh",
    output: &[
        0x22, 0x09, 0x89, 0x4a, 0x59, 0xd1, 0xf7, 0x96, 0x3d, 0xae, 0xb1, 0xb8, 0x8c, 0xbc, 0x4f, 0xa1, 0xc7, 0x4f,
        0x90, 0xa3, 0x90, 0xd4, 0x32, 0xaa, 0x57, 0xe5, 0xf1, 0xa4, 0x35, 0x43, 0xb5, 0xdc,
    ],
};

fn verify_sha2<TH, TB>(hasher_builder: TB, input: &[u8], expected: &[u8])
where
    TH: HashFunction,
    TB: FnOnce() -> TH,
{
    let mut hash_function = hasher_builder();
    hash_function.update(input);
    let result = hash_function.result();
    assert_eq!(result.as_ref(), expected);
}

const LOREM_IPSUM: &str = concat!(
    "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ",
    "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, ",
    "when an unknown printer took a galley of type and scrambled it to make a type specimen book. ",
    "It has survived not only five centuries, but also the leap into electronic typesetting, ",
    "remaining essentially unchanged. It was popularised in the 1960s with the release of ",
    "Letraset sheets containing Lorem Ipsum passages, and more recently with desktop ",
    "publishing software like Aldus PageMaker including versions of Lorem Ipsum."
);

fn verify_chunked_sha2<TH, TB>(hasher_builder: TB)
where
    TH: HashFunction,
    TB: FnOnce() -> TH,
{
    let mut hash_function = hasher_builder();
    let break_points = [3, 10, 20, 30, 40, 51, 67, 73, 80, 91, 107];
    let mut previous_break_point = 0;
    for break_point in break_points {
        hash_function.update(&LOREM_IPSUM[previous_break_point..break_point]);
        previous_break_point = break_point;
    }
    hash_function.update(&LOREM_IPSUM[previous_break_point..]);
    let result = hash_function.result();
    assert_eq!(
        result.as_ref(),
        &[
            0x73, 0x21, 0x34, 0x8c, 0x88, 0x94, 0x67, 0x84, 0x47, 0xb5, 0x4c, 0x88, 0x8f, 0xdb, 0xc4, 0xe4, 0xb8, 0x25,
            0xbf, 0x4d, 0x1e, 0xb0, 0xcf, 0xb2, 0x78, 0x74, 0x28, 0x6a, 0x23, 0xea, 0x9f, 0xd2,
        ]
    );
}

#[cfg(miri)]
macro_rules! generate_sha2_tests {
    ( $hasher: ident ) => {};
}

#[cfg(not(miri))]
macro_rules! generate_sha2_tests {
    ( $hasher: ident ) => {
        #[rstest]
        #[case::C1(C1)]
        #[case::C2(C2)]
        #[case::C3(C3)]
        #[case::C4(C4)]
        #[case::C5(C5)]
        #[case::C6(C6)]
        #[case::C7(C7)]
        #[case::C8(C8)]
        #[case::C9(C9)]
        fn test_concrete_sha2(#[case] sha2case: Sha2Case) {
            verify_sha2(<$hasher>::new, sha2case.input, sha2case.output);
        }

        #[test]
        fn test_chunked_sha2() {
            verify_chunked_sha2(<$hasher>::new);
        }

        #[rstest]
        #[case(5000, &[0xa6, 0x22, 0x2b, 0x80, 0xb6, 0xc6, 0xdc, 0xc6, 0xb6, 0x9d, 0xb5, 0x17, 0xd8, 0xac, 0x2b, 0xc4, 0xf4, 0x9b, 0xbb, 0xbf, 0xff, 0x30, 0xc5, 0x9d, 0x24, 0x39, 0x29, 0xb1, 0xea, 0x8d, 0x79, 0x08])]
        #[case(5001, &[0x79, 0x4a, 0x30, 0x1d, 0xb7, 0xd3, 0x13, 0xfa, 0x79, 0xc8, 0xe3, 0x61, 0xf6, 0xb5, 0x4b, 0x67, 0x5c, 0x99, 0xa8, 0x76, 0x41, 0xaf, 0x9d, 0xcf, 0xf0, 0xf5, 0xdb, 0xd8, 0x28, 0x0f, 0xf3, 0x61])]
        #[case(5002, &[0x0a, 0x84, 0x1e, 0x02, 0x88, 0x85, 0x74, 0x6f, 0x87, 0x2a, 0xd4, 0x03, 0xd2, 0x84, 0x92, 0xd3, 0xbd, 0xd3, 0x3c, 0x28, 0xce, 0xd9, 0x42, 0x4c, 0x5c, 0xb0, 0xbe, 0xe4, 0x41, 0x20, 0x2b, 0xa3])]
        #[case(5003, &[0x7d, 0x9e, 0x82, 0x00, 0x32, 0xf4, 0x20, 0x04, 0x32, 0xb9, 0xa3, 0x74, 0x71, 0xd6, 0x4a, 0x19, 0xe5, 0xc4, 0x29, 0x7f, 0x24, 0x9d, 0x25, 0xde, 0x52, 0xfa, 0x1c, 0x7a, 0x59, 0x67, 0xa9, 0x48])]
        #[case(5004, &[0xf0, 0x31, 0x0e, 0x18, 0xa6, 0xba, 0xd7, 0x72, 0xce, 0x7c, 0x81, 0x35, 0x88, 0x02, 0xda, 0x41, 0xcc, 0x9c, 0x49, 0x03, 0xb9, 0xb0, 0x33, 0x0e, 0x9e, 0xe4, 0xae, 0x88, 0x54, 0x72, 0xee, 0x29])]
        #[case(5005, &[0x50, 0x26, 0x28, 0xd7, 0xcf, 0xde, 0xe1, 0xd1, 0x18, 0x09, 0x33, 0x57, 0x02, 0xe5, 0xac, 0x30, 0x81, 0x9f, 0xb6, 0xa0, 0x2b, 0x9c, 0x5c, 0xaa, 0x51, 0xff, 0x93, 0x63, 0xde, 0x3a, 0xc6, 0x37])]
        #[case(5006, &[0x48, 0xac, 0x13, 0xf5, 0x72, 0x8e, 0x6a, 0xb4, 0xa3, 0xb9, 0xe5, 0x4c, 0xbe, 0xd2, 0x80, 0xb9, 0xc1, 0x80, 0x2e, 0x16, 0x04, 0xde, 0x4e, 0x0b, 0x09, 0xf6, 0xe0, 0xee, 0x50, 0x23, 0x6e, 0x9b])]
        #[case(5007, &[0x94, 0xbf, 0x2f, 0x7f, 0x04, 0x78, 0x33, 0x84, 0x92, 0xe5, 0x6e, 0x59, 0xf6, 0x91, 0x2d, 0xd2, 0x46, 0x1e, 0x59, 0x1c, 0xba, 0xfb, 0x7c, 0x6a, 0x8b, 0x6b, 0x0c, 0x74, 0x3e, 0xf0, 0x16, 0x0a])]
        #[case(5008, &[0x7a, 0x9d, 0x3b, 0x19, 0xd8, 0xf2, 0x00, 0xee, 0xd5, 0xfa, 0x69, 0xd3, 0xc8, 0x24, 0x01, 0xa9, 0xa9, 0x01, 0x19, 0x03, 0x8c, 0x14, 0x10, 0xcb, 0x16, 0xf4, 0x19, 0xe8, 0x17, 0x5c, 0x12, 0x85])]
        fn test_long_sha2(#[case] size: usize, #[case] expected: &[u8]) {
            use osom_lib_hashes::sha2::sha2_256::portable::SHA2_256_Portable;

            common::test_pseudo_random_hashing(SHA2_256_Portable::new, size, expected);
        }

    };
}

mod portable_tests {
    use super::*;
    use osom_lib_hashes::sha2::sha2_256::portable::SHA2_256_Portable;
    generate_sha2_tests!(SHA2_256_Portable);
}

#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    target_feature = "sha",
    target_feature = "sse4.1",
))]
mod x86_tests {
    use super::*;
    use osom_lib_hashes::sha2::sha2_256::platform::SHA2_256_x86;
    generate_sha2_tests!(SHA2_256_x86);
}

#[cfg(all(target_arch = "aarch64", target_feature = "sha2"))]
mod aarch64_tests {
    use super::*;
    use osom_lib_hashes::sha2::sha2_256::platform::SHA2_256_aarch64;
    generate_sha2_tests!(SHA2_256_aarch64);
}
