use super::utils::typenum;
use super::{
    FFIFunctionPointers, ParameterSet, SeedLen, SignatureLen, SigningKeyLen, VerifyingKeyLen,
};
use crate::ffi;
use ffi::c_int;

/// Length of the signing seed in bytes.
///
/// This is the same for all parameter sets.
pub const SIGNING_SEED_LEN: usize = ffi::MLDSA_SEEDBYTES as usize;

/// [`SIGNING_SEED_LEN`] as a type.
///
/// This is the same for all parameter sets.
pub type SigningSeedLen = typenum::U32;

#[allow(non_camel_case_types)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ML_DSA_44;

impl SigningKeyLen for ML_DSA_44 {
    const SIGNING_KEY_LEN: usize = ffi::MLDSA44_SECRETKEYBYTES as usize;

    type LEN = typenum::U2560;
}

impl VerifyingKeyLen for ML_DSA_44 {
    const VERIFYING_KEY_LEN: usize = ffi::MLDSA44_PUBLICKEYBYTES as usize;

    type LEN = typenum::U1312;
}

impl SignatureLen for ML_DSA_44 {
    const SIGNATURE_LEN: usize = ffi::MLDSA44_BYTES as usize;

    type LEN = typenum::U2420;
}

impl SeedLen for ML_DSA_44 {
    const SEED_LEN: usize = ffi::MLDSA44_SEEDBYTES as usize;

    type LEN = typenum::U32;
}

impl FFIFunctionPointers for ML_DSA_44 {
    const KEYGEN_FROM_SEED_FN: unsafe extern "C" fn(
        pk: *mut u8,
        sk: *mut u8,
        seed: *const u8,
    ) -> ffi::c_int = ffi::mldsa44_keypair_internal;

    const PK_FROM_SK_FN: unsafe extern "C" fn(pk: *mut u8, sk: *const u8) -> c_int =
        ffi::mldsa44_pk_from_sk;

    const SIGN_WITH_SEED_FN: unsafe extern "C" fn(
        sig: *mut u8,
        siglen: *mut usize,
        m: *const u8,
        mlen: usize,
        pre: *const u8,
        prelen: usize,
        rnd: *const u8,
        sk: *const u8,
        externalmu: c_int,
    ) -> c_int = ffi::mldsa44_signature_internal;

    const VERIFY_FN: unsafe extern "C" fn(
        sig: *const u8,
        siglen: usize,
        m: *const u8,
        mlen: usize,
        ctx: *const u8,
        ctxlen: usize,
        pk: *const u8,
    ) -> c_int = ffi::mldsa44_verify;

    const PREPARE_DOMAIN_SEPARATION_PREFIX_FN: unsafe extern "C" fn(
        prefix: *mut u8,
        ph: *const u8,
        phlen: usize,
        ctx: *const u8,
        ctxlen: usize,
        hashalg: c_int,
    ) -> usize = ffi::mldsa44_prepare_domain_separation_prefix;
}

impl ParameterSet for ML_DSA_44 {
    const NAME: &'static str = "ML-DSA-44";
}

#[allow(non_camel_case_types)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ML_DSA_65;

impl SigningKeyLen for ML_DSA_65 {
    const SIGNING_KEY_LEN: usize = ffi::MLDSA65_SECRETKEYBYTES as usize;

    type LEN = typenum::U4032;
}

impl VerifyingKeyLen for ML_DSA_65 {
    const VERIFYING_KEY_LEN: usize = ffi::MLDSA65_PUBLICKEYBYTES as usize;

    type LEN = typenum::U1952;
}

impl SignatureLen for ML_DSA_65 {
    const SIGNATURE_LEN: usize = ffi::MLDSA65_BYTES as usize;

    type LEN = typenum::U3309;
}

impl SeedLen for ML_DSA_65 {
    const SEED_LEN: usize = ffi::MLDSA65_SEEDBYTES as usize;

    type LEN = typenum::U32;
}

impl FFIFunctionPointers for ML_DSA_65 {
    const KEYGEN_FROM_SEED_FN: unsafe extern "C" fn(
        pk: *mut u8,
        sk: *mut u8,
        seed: *const u8,
    ) -> ffi::c_int = ffi::mldsa65_keypair_internal;

    const PK_FROM_SK_FN: unsafe extern "C" fn(pk: *mut u8, sk: *const u8) -> c_int =
        ffi::mldsa65_pk_from_sk;

    const SIGN_WITH_SEED_FN: unsafe extern "C" fn(
        sig: *mut u8,
        siglen: *mut usize,
        m: *const u8,
        mlen: usize,
        pre: *const u8,
        prelen: usize,
        rnd: *const u8,
        sk: *const u8,
        externalmu: c_int,
    ) -> c_int = ffi::mldsa65_signature_internal;

    const VERIFY_FN: unsafe extern "C" fn(
        sig: *const u8,
        siglen: usize,
        m: *const u8,
        mlen: usize,
        ctx: *const u8,
        ctxlen: usize,
        pk: *const u8,
    ) -> c_int = ffi::mldsa65_verify;

    const PREPARE_DOMAIN_SEPARATION_PREFIX_FN: unsafe extern "C" fn(
        prefix: *mut u8,
        ph: *const u8,
        phlen: usize,
        ctx: *const u8,
        ctxlen: usize,
        hashalg: c_int,
    ) -> usize = ffi::mldsa65_prepare_domain_separation_prefix;
}

impl ParameterSet for ML_DSA_65 {
    const NAME: &'static str = "ML-DSA-65";
}

#[allow(non_camel_case_types)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ML_DSA_87;

impl SigningKeyLen for ML_DSA_87 {
    const SIGNING_KEY_LEN: usize = ffi::MLDSA87_SECRETKEYBYTES as usize;

    type LEN = typenum::U4896;
}

impl VerifyingKeyLen for ML_DSA_87 {
    const VERIFYING_KEY_LEN: usize = ffi::MLDSA87_PUBLICKEYBYTES as usize;

    type LEN = typenum::U2592;
}

impl SignatureLen for ML_DSA_87 {
    const SIGNATURE_LEN: usize = ffi::MLDSA87_BYTES as usize;

    type LEN = typenum::U4627;
}

impl SeedLen for ML_DSA_87 {
    const SEED_LEN: usize = ffi::MLDSA87_SEEDBYTES as usize;

    type LEN = typenum::U32;
}

impl FFIFunctionPointers for ML_DSA_87 {
    const KEYGEN_FROM_SEED_FN: unsafe extern "C" fn(
        pk: *mut u8,
        sk: *mut u8,
        seed: *const u8,
    ) -> ffi::c_int = ffi::mldsa87_keypair_internal;

    const PK_FROM_SK_FN: unsafe extern "C" fn(pk: *mut u8, sk: *const u8) -> c_int =
        ffi::mldsa87_pk_from_sk;

    const SIGN_WITH_SEED_FN: unsafe extern "C" fn(
        sig: *mut u8,
        siglen: *mut usize,
        m: *const u8,
        mlen: usize,
        pre: *const u8,
        prelen: usize,
        rnd: *const u8,
        sk: *const u8,
        externalmu: c_int,
    ) -> c_int = ffi::mldsa87_signature_internal;

    const VERIFY_FN: unsafe extern "C" fn(
        sig: *const u8,
        siglen: usize,
        m: *const u8,
        mlen: usize,
        ctx: *const u8,
        ctxlen: usize,
        pk: *const u8,
    ) -> c_int = ffi::mldsa87_verify;

    const PREPARE_DOMAIN_SEPARATION_PREFIX_FN: unsafe extern "C" fn(
        prefix: *mut u8,
        ph: *const u8,
        phlen: usize,
        ctx: *const u8,
        ctxlen: usize,
        hashalg: c_int,
    ) -> usize = ffi::mldsa87_prepare_domain_separation_prefix;
}

impl ParameterSet for ML_DSA_87 {
    const NAME: &'static str = "ML-DSA-87";
}

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

    fn test_type_level_constants_generic<P: ParameterSet>() {
        assert_eq!(P::SIGNATURE_LEN, <P as SignatureLen>::LEN::USIZE);

        assert_eq!(P::SIGNING_KEY_LEN, <P as SigningKeyLen>::LEN::USIZE);
        assert_eq!(P::SIGNING_KEY_LEN, P::SECRET_KEY_LEN);

        assert_eq!(P::VERIFYING_KEY_LEN, <P as VerifyingKeyLen>::LEN::USIZE);
        assert_eq!(P::VERIFYING_KEY_LEN, P::PUBLIC_KEY_LEN);

        assert_eq!(P::SEED_LEN, <P as SeedLen>::LEN::USIZE);
    }

    #[test]
    fn test_type_level_constants() {
        test_type_level_constants_generic::<ML_DSA_44>();
        test_type_level_constants_generic::<ML_DSA_65>();
        test_type_level_constants_generic::<ML_DSA_87>();
    }

    #[test]
    #[rustfmt::skip]
    fn test_constants_match_ffi_values() {
        assert_eq!(ML_DSA_44::SIGNATURE_LEN, ffi::MLDSA44_BYTES as usize);
        assert_eq!(ML_DSA_44::SECRET_KEY_LEN, ffi::MLDSA44_SECRETKEYBYTES as usize);
        assert_eq!(ML_DSA_44::PUBLIC_KEY_LEN, ffi::MLDSA44_PUBLICKEYBYTES as usize);
        assert_eq!(ML_DSA_44::SEED_LEN, ffi::MLDSA44_SEEDBYTES as usize);

        assert_eq!(ML_DSA_65::SIGNATURE_LEN, ffi::MLDSA65_BYTES as usize);
        assert_eq!(ML_DSA_65::SECRET_KEY_LEN, ffi::MLDSA65_SECRETKEYBYTES as usize);
        assert_eq!(ML_DSA_65::PUBLIC_KEY_LEN, ffi::MLDSA65_PUBLICKEYBYTES as usize);
        assert_eq!(ML_DSA_65::SEED_LEN, ffi::MLDSA65_SEEDBYTES as usize);

        assert_eq!(ML_DSA_87::SIGNATURE_LEN, ffi::MLDSA87_BYTES as usize);
        assert_eq!(ML_DSA_87::SECRET_KEY_LEN, ffi::MLDSA87_SECRETKEYBYTES as usize);
        assert_eq!(ML_DSA_87::PUBLIC_KEY_LEN, ffi::MLDSA87_PUBLICKEYBYTES as usize);
        assert_eq!(ML_DSA_87::SEED_LEN, ffi::MLDSA87_SEEDBYTES as usize);
    }
}
