use serde::{Deserialize, Serialize};

use crate::{
    message::ProtocolVersion,
    packet::PayloadLength,
    pow::{NonceTrialsPerByte, PayloadLengthExtraBytes},
};

/// A set of configurations for the Koibumi Bitmessage core.
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Config {
    #[serde(default = "default_protocol_version")]
    protocol_version: ProtocolVersion,

    #[serde(default = "default_max_payload_length")]
    max_payload_length: PayloadLength,

    #[serde(default = "default_nonce_trials_per_byte")]
    nonce_trials_per_byte: NonceTrialsPerByte,
    #[serde(default = "default_payload_length_extra_bytes")]
    payload_length_extra_bytes: PayloadLengthExtraBytes,
}

fn default_protocol_version() -> ProtocolVersion {
    ProtocolVersion::new(3)
}

fn default_max_payload_length() -> PayloadLength {
    PayloadLength::new(1_600_100)
}

fn default_nonce_trials_per_byte() -> NonceTrialsPerByte {
    NonceTrialsPerByte::new(1000)
}

fn default_payload_length_extra_bytes() -> PayloadLengthExtraBytes {
    PayloadLengthExtraBytes::new(1000)
}

impl Default for Config {
    fn default() -> Self {
        Self {
            protocol_version: default_protocol_version(),

            max_payload_length: default_max_payload_length(),

            nonce_trials_per_byte: default_nonce_trials_per_byte(),
            payload_length_extra_bytes: default_payload_length_extra_bytes(),
        }
    }
}

impl Config {
    /// Constructs a builder for building a configuration set.
    pub fn builder() -> Builder {
        Builder::new()
    }

    /// Constructs a default configuration set.
    pub fn new() -> Self {
        Self::default()
    }

    /// Returns the protocol version.
    /// The default is `3`.
    pub fn protocol_version(&self) -> ProtocolVersion {
        self.protocol_version
    }

    /// Returns the maximum payload length which can be stored
    /// in a Bitmessage packet.
    /// The default is `1_600_100`.
    pub fn max_payload_length(&self) -> PayloadLength {
        self.max_payload_length
    }

    /// Returns the nonce trials per byte,
    /// which is one of the parameters of the proof of work protocol
    /// Bitmessage uses.
    /// The default is `1000`.
    pub fn nonce_trials_per_byte(&self) -> NonceTrialsPerByte {
        self.nonce_trials_per_byte
    }

    /// Returns the payload length extra bytes,
    /// which is one of the parameters of the proof of work protocol
    /// Bitmessage uses.
    /// The default is `1000`.
    pub fn payload_length_extra_bytes(&self) -> PayloadLengthExtraBytes {
        self.payload_length_extra_bytes
    }
}

/// A builder for building a configuration set
/// for the Koibumi Bitmessage core.
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct Builder {
    config: Config,
}

impl Builder {
    fn new() -> Self {
        Self::default()
    }

    /// Sets the protocol version.
    /// The default is `3`.
    pub fn protocol_version(&mut self, v: ProtocolVersion) -> &mut Self {
        self.config.protocol_version = v;
        self
    }

    /// Sets the maximum payload length which can be stored
    /// in a Bitmessage packet.
    /// The default is `1_600_100`.
    pub fn max_payload_length(&mut self, v: PayloadLength) -> &mut Self {
        self.config.max_payload_length = v;
        self
    }

    /// Sets the nonce trials per byte,
    /// which is one of the parameters of the proof of work protocol
    /// Bitmessage uses.
    /// The default is `1000`.
    pub fn nonce_trials_per_byte(&mut self, v: NonceTrialsPerByte) -> &mut Self {
        self.config.nonce_trials_per_byte = v;
        self
    }

    /// Sets the payload length extra bytes,
    /// which is one of the parameters of the proof of work protocol
    /// Bitmessage uses.
    /// The default is `1000`.
    pub fn payload_length_extra_bytes(&mut self, v: PayloadLengthExtraBytes) -> &mut Self {
        self.config.payload_length_extra_bytes = v;
        self
    }

    /// Returns the configuration set this builder represents.
    pub fn build(&self) -> Config {
        self.config.clone()
    }
}
