use buffered_reader::BufferedReader;

use crate::Result;
use crate::parse::PacketParserResult;
use crate::parse::PacketParser;
use crate::parse::PacketParserEOF;
use crate::parse::PacketParserState;
use crate::parse::PacketParserSettings;
use crate::parse::ParserResult;
use crate::parse::Parse;
use crate::parse::Cookie;
use crate::armor;
use crate::packet;

/// Controls transparent stripping of ASCII armor when parsing.
///
/// When parsing OpenPGP data streams, the [`PacketParser`] will by
/// default automatically detect and remove any ASCII armor encoding
/// (see [Section 6 of RFC 9580]).  This automatism can be disabled
/// and fine-tuned using [`PacketParserBuilder::dearmor`].
///
///   [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6
///   [`PacketParserBuilder::dearmor`]: PacketParserBuilder::dearmor()
#[derive(PartialEq)]
#[non_exhaustive]
pub enum Dearmor {
    /// Unconditionally treat the input as if it were an OpenPGP
    /// message encoded using ASCII armor.
    ///
    /// Parsing a binary encoded OpenPGP message using this mode will
    /// fail.  The [`ReaderMode`] allow further customization of the
    /// ASCII armor parser.
    ///
    ///   [`ReaderMode`]: crate::armor::ReaderMode
    Enabled(armor::ReaderMode),
    /// Unconditionally treat the input as if it were a binary OpenPGP
    /// message.
    ///
    /// Parsing an ASCII armor encoded OpenPGP message using this mode will
    /// fail.
    Disabled,
    /// If input does not appear to be a binary encoded OpenPGP
    /// message, treat it as if it were encoded using ASCII armor.
    ///
    /// This is the default.  The [`ReaderMode`] allow further
    /// customization of the ASCII armor parser.
    ///
    ///   [`ReaderMode`]: crate::armor::ReaderMode
    Auto(armor::ReaderMode),
}
assert_send_and_sync!(Dearmor);

impl Default for Dearmor {
    fn default() -> Self {
        Dearmor::Auto(Default::default())
    }
}

/// This is the level at which we insert the dearmoring filter into
/// the buffered reader stack.
pub(super) const ARMOR_READER_LEVEL: isize = -2;

/// A builder for configuring a `PacketParser`.
///
/// Since the default settings are usually appropriate, this mechanism
/// will only be needed in exceptional circumstances.  Instead use,
/// for instance, `PacketParser::from_file` or
/// `PacketParser::from_reader` to start parsing an OpenPGP message.
///
/// # Examples
///
/// ```rust
/// # fn main() -> sequoia_openpgp::Result<()> {
/// use sequoia_openpgp as openpgp;
/// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder};
///
/// // Parse a message.
/// let message_data: &[u8] = // ...
/// #    include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp");
/// let mut ppr = PacketParserBuilder::from_bytes(message_data)?
///     // Customize the `PacketParserBuilder` here.
///     .build()?;
/// while let PacketParserResult::Some(mut pp) = ppr {
///     // ...
///
///     // Start parsing the next packet, recursing.
///     ppr = pp.recurse()?.1;
/// }
/// # Ok(()) }
/// ```
pub struct PacketParserBuilder<'a> {
    bio: Box<dyn BufferedReader<Cookie> + 'a>,
    dearmor: Dearmor,
    settings: PacketParserSettings,
    csf_transformation: bool,
}
assert_send_and_sync!(PacketParserBuilder<'_>);

impl<'a> Parse<'a, PacketParserBuilder<'a>> for PacketParserBuilder<'a> {
    /// Starts parsing an OpenPGP object stored in a `BufferedReader` object.
    ///
    /// This function returns a `PacketParser` for the first packet in
    /// the stream.
    fn from_buffered_reader<R>(reader: R) -> Result<PacketParserBuilder<'a>>
    where
        R: BufferedReader<Cookie> + 'a,
    {
        PacketParserBuilder::from_cookie_reader(reader.into_boxed())
    }
}

impl<'a> crate::seal::Sealed for PacketParserBuilder<'a> {}

impl<'a> PacketParserBuilder<'a> {
    // Creates a `PacketParserBuilder` for an OpenPGP message stored
    // in a `BufferedReader` object.
    //
    // Note: this clears the `level` field of the
    // `Cookie` cookie.
    pub(crate) fn from_cookie_reader(mut bio: Box<dyn BufferedReader<Cookie> + 'a>)
            -> Result<Self> {
        bio.cookie_mut().level = None;
        Ok(PacketParserBuilder {
            bio,
            dearmor: Default::default(),
            settings: PacketParserSettings::default(),
            csf_transformation: false,
        })
    }

    /// Sets the maximum recursion depth.
    ///
    /// Setting this to 0 means that the `PacketParser` will never
    /// recurse; it will only parse the top-level packets.
    ///
    /// This is a u8, because recursing more than 255 times makes no
    /// sense.  The default is [`DEFAULT_MAX_RECURSION_DEPTH`].
    /// (GnuPG defaults to a maximum recursion depth of 32.)
    ///
    /// [`DEFAULT_MAX_RECURSION_DEPTH`]: crate::parse::DEFAULT_MAX_RECURSION_DEPTH
    ///
    /// # Examples
    ///
    /// ```rust
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::Packet;
    /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder};
    ///
    /// // Parse a compressed message.
    /// let message_data: &[u8] = // ...
    /// #    include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp");
    /// let mut ppr = PacketParserBuilder::from_bytes(message_data)?
    ///     .max_recursion_depth(0)
    ///     .build()?;
    /// while let PacketParserResult::Some(mut pp) = ppr {
    ///     assert_eq!(pp.recursion_depth(), 0);
    ///
    ///     // Start parsing the next packet, recursing.
    ///     ppr = pp.recurse()?.1;
    /// }
    /// # Ok(()) }
    /// ```
    pub fn max_recursion_depth(mut self, value: u8) -> Self {
        self.settings.max_recursion_depth = value;
        self
    }

    /// Sets the maximum size in bytes of non-container packets.
    ///
    /// Packets that exceed this limit will be returned as
    /// `Packet::Unknown`, with the error set to
    /// `Error::PacketTooLarge`.
    ///
    /// This limit applies to any packet type that is *not* a
    /// container packet, i.e. any packet that is not a literal data
    /// packet, a compressed data packet, a symmetrically encrypted
    /// data packet, or an AEAD encrypted data packet.
    ///
    /// The default is [`DEFAULT_MAX_PACKET_SIZE`].
    ///
    /// [`DEFAULT_MAX_PACKET_SIZE`]: crate::parse::DEFAULT_MAX_PACKET_SIZE
    ///
    /// # Examples
    ///
    /// ```rust
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::{Error, Packet};
    /// use openpgp::packet::Tag;
    /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder};
    /// use openpgp::serialize::MarshalInto;
    ///
    /// // Parse a signed message.
    /// let message_data: &[u8] = // ...
    /// #    include_bytes!("../../tests/data/messages/signed-1.pgp");
    /// let mut ppr = PacketParserBuilder::from_bytes(message_data)?
    ///     .max_packet_size(256)    // Only parse 256 bytes of headers.
    ///     .buffer_unread_content() // Used below.
    ///     .build()?;
    /// while let PacketParserResult::Some(mut pp) = ppr {
    ///     match &pp.packet {
    ///         Packet::OnePassSig(p) =>
    ///             // The OnePassSig packet was small enough.
    ///             assert!(p.serialized_len() < 256),
    ///         Packet::Literal(p) =>
    ///             // Likewise the `Literal` packet, excluding the body.
    ///             assert!(p.serialized_len() - p.body().len() < 256),
    ///         Packet::Unknown(p) =>
    ///             // The signature packet was too big.
    ///             assert_eq!(
    ///                 &Error::PacketTooLarge(Tag::Signature, 307, 256),
    ///                 p.error().downcast_ref().unwrap()),
    ///         _ => unreachable!(),
    ///     }
    ///
    ///     // Start parsing the next packet, recursing.
    ///     ppr = pp.recurse()?.1;
    /// }
    /// # Ok(()) }
    /// ```
    pub fn max_packet_size(mut self, value: u32) -> Self {
        self.settings.max_packet_size = value;
        self
    }

    /// Causes `PacketParser::build()` to buffer any unread content.
    ///
    /// The unread content can be accessed using [`Literal::body`],
    /// [`Unknown::body`], or [`Container::body`].
    ///
    ///   [`Literal::body`]: crate::packet::Literal::body()
    ///   [`Unknown::body`]: crate::packet::Unknown::body()
    ///   [`Container::body`]: crate::packet::Container::body()
    ///
    /// # Examples
    ///
    /// ```rust
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::Packet;
    /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder};
    ///
    /// // Parse a simple message.
    /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world.";
    /// let mut ppr = PacketParserBuilder::from_bytes(message_data)?
    ///     .buffer_unread_content()
    ///     .build()?;
    /// while let PacketParserResult::Some(mut pp) = ppr {
    ///     // Start parsing the next packet, recursing.
    ///     let (packet, tmp) = pp.recurse()?;
    ///     ppr = tmp;
    ///
    ///     match packet {
    ///         Packet::Literal(l) => assert_eq!(l.body(), b"Hello world."),
    ///         _ => unreachable!(),
    ///     }
    /// }
    /// # Ok(()) }
    /// ```
    pub fn buffer_unread_content(mut self) -> Self {
        self.settings.buffer_unread_content = true;
        self
    }

    /// Causes `PacketParser::finish()` to drop any unread content.
    ///
    /// This is the default.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::Packet;
    /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder};
    ///
    /// // Parse a simple message.
    /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world.";
    /// let mut ppr = PacketParserBuilder::from_bytes(message_data)?
    ///     .drop_unread_content()
    ///     .build()?;
    /// while let PacketParserResult::Some(mut pp) = ppr {
    ///     // Start parsing the next packet, recursing.
    ///     let (packet, tmp) = pp.recurse()?;
    ///     ppr = tmp;
    ///
    ///     match packet {
    ///         Packet::Literal(l) => assert_eq!(l.body(), b""),
    ///         _ => unreachable!(),
    ///     }
    /// }
    /// # Ok(()) }
    /// ```
    pub fn drop_unread_content(mut self) -> Self {
        self.settings.buffer_unread_content = false;
        self
    }

    /// Controls mapping.
    ///
    /// Note that enabling mapping buffers all the data.
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::parse::{Parse, PacketParserBuilder};
    ///
    /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world.";
    /// let pp = PacketParserBuilder::from_bytes(message_data)?
    ///     .map(true) // Enable mapping.
    ///     .build()?
    ///     .expect("One packet, not EOF");
    /// let map = pp.map().expect("Mapping is enabled");
    ///
    /// assert_eq!(map.iter().nth(0).unwrap().name(), "CTB");
    /// assert_eq!(map.iter().nth(0).unwrap().offset(), 0);
    /// assert_eq!(map.iter().nth(0).unwrap().as_bytes(), &[0xcb]);
    /// # Ok(()) }
    /// ```
    pub fn map(mut self, enable: bool) -> Self {
        self.settings.map = enable;
        self
    }

    /// Controls dearmoring.
    ///
    /// By default, if the input does not appear to be plain binary
    /// OpenPGP data, we assume that it is ASCII-armored.  This method
    /// can be used to tweak the behavior.  See [`Dearmor`] for
    /// details.
    ///
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::parse::{Parse, PacketParserBuilder, Dearmor};
    ///
    /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world.";
    /// let pp = PacketParserBuilder::from_bytes(message_data)?
    ///     .dearmor(Dearmor::Disabled) // Disable dearmoring.
    ///     .build()?
    ///     .expect("One packet, not EOF");
    /// # Ok(()) }
    /// ```
    pub fn dearmor(mut self, mode: Dearmor) -> Self {
        self.dearmor = mode;
        self
    }

    /// Controls automatic hashing.
    ///
    /// When encountering a [`OnePassSig`] packet, the packet parser
    /// will, by default, start hashing later packets using the hash
    /// algorithm specified in the packet.  In some cases, this is not
    /// needed, and hashing will incur a non-trivial overhead.
    ///
    /// If automatic hashing is disabled, then hashing may be
    /// explicitly enabled using [`PacketParser::start_hashing`] while
    /// parsing each [`OnePassSig`] packet.
    ///
    ///   [`OnePassSig`]: crate::packet::OnePassSig
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// # use sequoia_openpgp as openpgp;
    /// # use openpgp::parse::{Parse, PacketParserBuilder};
    /// #
    /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world.";
    /// let pp = PacketParserBuilder::from_bytes(message_data)?
    ///     .automatic_hashing(false) // Disable automatic hashing.
    ///     .build()?
    ///     .expect("One packet, not EOF");
    /// # Ok(()) }
    /// ```
    pub fn automatic_hashing(mut self, enable: bool) -> Self {
        self.settings.automatic_hashing = enable;
        self
    }

    /// Controls transparent transformation of messages using the
    /// cleartext signature framework into signed messages.
    ///
    /// XXX: This could be controlled by `Dearmor`, but we cannot add
    /// values to that now.
    pub(crate) fn csf_transformation(mut self, enable: bool) -> Self {
        self.csf_transformation = enable;
        self
    }

    /// Builds the `PacketParser`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # fn main() -> sequoia_openpgp::Result<()> {
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder};
    ///
    /// // Parse a message.
    /// let message_data: &[u8] = // ...
    /// #    include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp");
    /// let mut ppr = PacketParserBuilder::from_bytes(message_data)?
    ///     // Customize the `PacketParserBuilder` here.
    ///     .build()?;
    /// while let PacketParserResult::Some(mut pp) = ppr {
    ///     // ...
    ///
    ///     // Start parsing the next packet, recursing.
    ///     ppr = pp.recurse()?.1;
    /// }
    /// # Ok(()) }
    #[allow(clippy::redundant_pattern_matching)]
    pub fn build(mut self)
        -> Result<PacketParserResult<'a>>
        where Self: 'a
    {
        let state = PacketParserState::new(self.settings);

        let dearmor_mode = match self.dearmor {
            Dearmor::Enabled(mode) => Some(mode),
            Dearmor::Disabled => None,
            Dearmor::Auto(mode) => {
                if self.bio.eof() {
                    None
                } else {
                    let mut reader = buffered_reader::Dup::with_cookie(
                        self.bio, Cookie::default());
                    let header = packet::Header::parse(&mut reader);
                    self.bio = Box::new(reader).into_inner().unwrap();
                    if let Ok(header) = header {
                        if let Err(_) = header.valid(false) {
                            // Invalid header: better try an ASCII armor
                            // decoder.
                            Some(mode)
                        } else {
                            None
                        }
                    } else {
                        // Failed to parse the header: better try an ASCII
                        // armor decoder.
                        Some(mode)
                    }
                }
            }
        };

        if let Some(mode) = dearmor_mode {
            // Add a top-level filter so that it is peeled off when
            // the packet parser is finished.  We use level -2 for that.
            self.bio =
                armor::Reader::from_cookie_reader_csft(self.bio, Some(mode),
                    Cookie::new(ARMOR_READER_LEVEL), self.csf_transformation)
                .into_boxed();
        }

        // Parse the first packet.
        match PacketParser::parse(Box::new(self.bio), state, vec![ 0 ])? {
            ParserResult::Success(mut pp) => {
                // We successfully parsed the first packet's header.
                pp.state.message_validator.push(
                    pp.packet.tag(), pp.packet.version(), &[0]);
                pp.state.keyring_validator.push(pp.packet.tag());
                pp.state.cert_validator.push(pp.packet.tag());
                Ok(PacketParserResult::Some(pp))
            },
            ParserResult::EOF((reader, state, _path)) => {
                // `bio` is empty.  We're done.
                Ok(PacketParserResult::EOF(PacketParserEOF::new(state, reader)))
            }
        }
    }
}

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

    #[test]
    fn armor() {
        // Not ASCII armor encoded data.
        let msg = crate::tests::message("sig.pgp");

        // Make sure we can read the first packet.
        let ppr = PacketParserBuilder::from_bytes(msg).unwrap()
            .dearmor(Dearmor::Disabled)
            .build();
        assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr);

        let ppr = PacketParserBuilder::from_bytes(msg).unwrap()
            .dearmor(Dearmor::Auto(Default::default()))
            .build();
        assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr);

        let ppr = PacketParserBuilder::from_bytes(msg).unwrap()
            .dearmor(Dearmor::Enabled(Default::default()))
            .build();
        assert_match!(Err(_) = ppr);

        // ASCII armor encoded data.
        let msg = crate::tests::message("a-cypherpunks-manifesto.txt.ed25519.sig");

        // Make sure we can read the first packet.
        let ppr = PacketParserBuilder::from_bytes(msg).unwrap()
            .dearmor(Dearmor::Disabled)
            .build();
        assert_match!(Err(_) = ppr);

        let ppr = PacketParserBuilder::from_bytes(msg).unwrap()
            .dearmor(Dearmor::Auto(Default::default()))
            .build();
        assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr);

        let ppr = PacketParserBuilder::from_bytes(msg).unwrap()
            .dearmor(Dearmor::Enabled(Default::default()))
            .build();
        assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr);
    }
}
