// Copyright 2022 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

//! Module providing keys, keypairs, and signatures.
//!
//! The easiest way to get a `PublicKey` is to create a random `Keypair` first through one of the
//! `new` functions. A `PublicKey` can't be generated by itself; it must always be derived from a
//! secret key.

use hex_fmt::HexFmt;
use serde::{Deserialize, Serialize};
use std::{fmt, hash::Hash};

/// A signature share, with its index in the combined collection.
#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Debug)]
pub struct SignatureShare {
    /// Index in the combined collection.
    pub index: usize,
    /// Signature over some data.
    pub share: bls::SignatureShare,
}

/// Wrapper for different signature types.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, custom_debug::Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Signature {
    /// Ed25519 signature.
    Ed25519(
        #[debug(with = "Self::fmt_ed25519")]
        #[serde(with = "serde_bytes")]
        ed25519_dalek::Signature,
    ),
    /// BLS signature.
    Bls(bls::Signature),
    /// BLS signature share.
    BlsShare(SignatureShare),
}

impl Signature {
    /// Returns bls::Signature if Self is a BLS variant.
    pub fn into_bls(self) -> Option<bls::Signature> {
        match self {
            Self::Bls(sig) => Some(sig),
            _ => None,
        }
    }

    /// Returns ed25519_dalek::Signature if Self is a Ed25519 variant.
    pub fn into_ed(self) -> Option<ed25519_dalek::Signature> {
        match self {
            Self::Ed25519(sig) => Some(sig),
            _ => None,
        }
    }

    // ed25519_dalek::Signature has overly verbose debug output, so we provide our own
    pub fn fmt_ed25519(sig: &ed25519_dalek::Signature, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Signature({:0.10})", HexFmt(sig))
    }
}

impl From<bls::Signature> for Signature {
    fn from(sig: bls::Signature) -> Self {
        Self::Bls(sig)
    }
}

impl From<ed25519_dalek::Signature> for Signature {
    fn from(sig: ed25519_dalek::Signature) -> Self {
        Self::Ed25519(sig)
    }
}

impl From<SignatureShare> for Signature {
    fn from(sig: SignatureShare) -> Self {
        Self::BlsShare(sig)
    }
}

impl From<(usize, bls::SignatureShare)> for Signature {
    fn from(sig: (usize, bls::SignatureShare)) -> Self {
        let (index, share) = sig;
        Self::BlsShare(SignatureShare { index, share })
    }
}

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

    #[test]
    fn ed25519_rmp_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
        let signature = gen_ed25519_sig();
        let serialized = rmp_serde::to_vec(&signature)?;

        assert_eq!(
            rmp_serde::from_read::<_, Signature>(&serialized[..])?,
            signature
        );

        Ok(())
    }

    /// The new `from_bytes` mechanism for checking a signature, checks the value of the last byte
    /// in the array passed in, and does a bitwise and operation on it. If that operation doesn't
    /// result in 0, an error is returned. The only thing that makes sense to me is to keep trying
    /// until you get a value it accepts. It doesn't seem to take very long.
    fn gen_ed25519_sig() -> Signature {
        let random_bytes: Vec<u8> = (0..ed25519::Signature::BYTE_SIZE)
            .map(|_| rand::random::<u8>())
            .collect();
        let mut result = ed25519::Signature::from_bytes(&random_bytes);
        while result.is_err() {
            let random_bytes: Vec<u8> = (0..ed25519::Signature::BYTE_SIZE)
                .map(|_| rand::random::<u8>())
                .collect();
            result = ed25519::Signature::from_bytes(&random_bytes);
        }
        Signature::Ed25519(result.unwrap())
    }
}
