//! Provides Bitmessage message content types.

use std::io::{self, Read, Write};

use crate::{
    address::{Address, AddressVersion, FromPublicKeysError, StreamNumber},
    crypto::{self, PublicKey, Signature, VerifyError},
    encoding::Encoding,
    error::TooLongError,
    feature::Features,
    io::{ReadFrom, SizedReadFrom, WriteTo},
    message,
    pow::{NonceTrialsPerByte, PayloadLengthExtraBytes},
    var_type::VarInt,
};

/// A content of a broadcast message.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Broadcast {
    address_version: AddressVersion,
    stream_number: StreamNumber,
    behavior_bitfield: Features,
    public_signing_key: PublicKey,
    public_encryption_key: PublicKey,
    nonce_trials_per_byte: NonceTrialsPerByte,
    extra_bytes: PayloadLengthExtraBytes,
    encoding: Encoding,
    message: Vec<u8>,
    signature: Signature,
}

impl Broadcast {
    /// Returns the stream number.
    pub fn stream_number(&self) -> StreamNumber {
        self.stream_number
    }

    /// Returns the encoding.
    pub fn encoding(&self) -> Encoding {
        self.encoding
    }

    /// Returns the message.
    pub fn message(&self) -> &[u8] {
        &self.message
    }

    /// Verify the signature.
    pub fn verify(&self, signed_header: impl AsRef<[u8]>) -> Result<(), VerifyError> {
        let mut data = signed_header.as_ref().to_vec();
        self.address_version.write_to(&mut data).unwrap();
        self.stream_number.write_to(&mut data).unwrap();
        self.behavior_bitfield.write_to(&mut data).unwrap();
        self.public_signing_key.write_to(&mut data).unwrap();
        self.public_encryption_key.write_to(&mut data).unwrap();
        if self.address_version.as_u64() >= 3 {
            self.nonce_trials_per_byte.write_to(&mut data).unwrap();
            self.extra_bytes.write_to(&mut data).unwrap();
        }
        self.encoding.write_to(&mut data).unwrap();
        let len = VarInt::from(self.message.len());
        len.write_to(&mut data).unwrap();
        self.message.write_to(&mut data).unwrap();
        crypto::verify(&data, &self.signature, &self.public_signing_key)
    }

    /// Returns the Bitmessage address.
    pub fn address(&self) -> Result<Address, FromPublicKeysError> {
        Ok(Address::from_public_keys(
            self.address_version,
            self.stream_number,
            &self.public_signing_key,
            &self.public_encryption_key,
        )?)
    }
}

impl WriteTo for Broadcast {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        self.address_version.write_to(w)?;
        self.stream_number.write_to(w)?;
        self.behavior_bitfield.write_to(w)?;
        self.public_signing_key.write_to(w)?;
        self.public_encryption_key.write_to(w)?;
        if self.address_version.as_u64() >= 3 {
            self.nonce_trials_per_byte.write_to(w)?;
            self.extra_bytes.write_to(w)?;
        }
        self.encoding.write_to(w)?;
        let len = VarInt::from(self.message.len());
        len.write_to(w)?;
        self.message.write_to(w)?;
        self.signature.write_to(w)?;
        Ok(())
    }
}

#[allow(clippy::useless_let_if_seq)]
impl ReadFrom for Broadcast {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        let address_version = AddressVersion::read_from(r)?;
        let stream_number = StreamNumber::read_from(r)?;
        let behavior_bitfield = Features::read_from(r)?;
        let public_signing_key = PublicKey::read_from(r)?;
        let public_encryption_key = PublicKey::read_from(r)?;
        let nonce_trials_per_byte;
        let extra_bytes;
        if address_version.as_u64() >= 3 {
            nonce_trials_per_byte = NonceTrialsPerByte::read_from(r)?;
            extra_bytes = PayloadLengthExtraBytes::read_from(r)?;
        } else {
            nonce_trials_per_byte = 1000.into();
            extra_bytes = 1000.into();
        }
        let encoding = Encoding::read_from(r)?;
        let len = VarInt::read_from(r)?;
        if len.as_u64() > message::Object::MAX_OBJECT_PAYLOAD_LENGTH as u64 {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                TooLongError::new(
                    message::Object::MAX_OBJECT_PAYLOAD_LENGTH,
                    len.as_u64() as usize,
                ),
            ));
        }
        let message = Vec::<u8>::sized_read_from(r, len.as_u64() as usize)?;
        let signature = Signature::read_from(r)?;
        Ok(Self {
            address_version,
            stream_number,
            behavior_bitfield,
            public_signing_key,
            public_encryption_key,
            nonce_trials_per_byte,
            extra_bytes,
            encoding,
            message,
            signature,
        })
    }
}
