pub use signature::SignatureEncoding;

use generic_array::GenericArray;

use super::ParameterSet;
use super::utils::typenum::Unsigned;

/// Represents a signature using the specified parameter set.
///
/// # Usage
///
/// ```rust
/// # use mldsa_native_rs::*;
/// # let sk = SigningKey::<P>::new().expect("Keygen failed");
/// use parameter_sets::ML_DSA_44 as P;
///
/// let msg: &[u8] = b"Hello, world!";
///
/// let signature = sk.sign(msg);
///
/// // There are different ways to obtain a byte representation of the
/// // signature.
/// let signature_encoding = signature.to_bytes();
/// let encoded_signature_via_SignatureEncoding: &[u8] = &signature_encoding;
/// let encoded_signature_via_AsBytes = signature.as_bytes();
/// assert_eq!(encoded_signature_via_SignatureEncoding, encoded_signature_via_AsBytes);
///
/// let encoded_signature = encoded_signature_via_AsBytes;
///
/// let recv_sig: Signature<P> = encoded_signature.try_into()
///     .expect("Failed to parse the received signature");
/// assert_eq!(recv_sig, signature);
///
/// // Equivalently, using the FromBytes trait
/// let decoded_signature = Signature::<P>::from_bytes(encoded_signature)
///     .expect("Failed to decode the received signature");
/// assert_eq!(decoded_signature, signature);
/// ```
///
/// If the crate is built without the `rand` feature, then the `sign` method
/// (which takes only the `msg` as an argument) will not be available. In that
/// case, you must generate your own random bytes and use the signing methods
/// provided by the [`SeededSigner`](`super::signing_key::SeededSigner`) trait.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Signature<P: ParameterSet> {
    pub(super) sig: GenericArray<u8, <P as crate::SignatureLen>::LEN>,
}

impl<P: ParameterSet> signature::SignatureEncoding for Signature<P> {
    type Repr = GenericArray<u8, <P as crate::SignatureLen>::LEN>;
}

// Implement TryFrom<&[u8]> for Signature<P>
impl<P: ParameterSet> TryFrom<&[u8]> for Signature<P> {
    type Error = signature::Error;

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        if bytes.len() != <<P as crate::SignatureLen>::LEN>::USIZE {
            return Err(signature::Error::default());
        }
        let arr = GenericArray::from_slice(bytes).clone();
        Ok(Signature { sig: arr })
    }
}

// Implement From<Signature<P>> for GenericArray<u8, <P as crate::SignatureLen>::LEN>
impl<P: ParameterSet> From<Signature<P>> for GenericArray<u8, <P as crate::SignatureLen>::LEN> {
    fn from(sig: Signature<P>) -> Self {
        sig.sig
    }
}

impl<P: ParameterSet> AsRef<[u8]> for Signature<P> {
    fn as_ref(&self) -> &[u8] {
        self.sig.as_ref()
    }
}

/// Prepare the domain separation prefix for pure ML-DSA signing with the
/// context string `context`.
pub(crate) fn prepare_domain_separation_prefix<P: ParameterSet>(
    context: &[u8],
) -> Result<Vec<u8>, signature::Error> {
    use crate::ffi;
    let mut prefix = [0u8; ffi::MLD_DOMAIN_SEPARATION_MAX_BYTES as usize];
    let ret = unsafe {
        P::PREPARE_DOMAIN_SEPARATION_PREFIX_FN(
            prefix.as_mut_ptr(),
            // the ph and phlen arguments are only used for pre-hashed ML-DSA
            std::ptr::null(),
            0,
            context.as_ptr(),
            context.len(),
            ffi::MLD_PREHASH_NONE as ffi::c_int,
        )
    };
    match ret {
        0 => Err(signature::Error::default()),
        len => Ok(Vec::from(&prefix[0..len])),
    }
}
