use bitflags::bitflags;
use milter_sys as sys;

bitflags! {
    /// Flags representing milter actions.
    ///
    /// # Examples
    ///
    /// ```
    /// # use milter::Actions;
    /// let header_actions = Actions::ADD_HEADER | Actions::REPLACE_HEADER;
    /// ```
    #[derive(Default)]
    pub struct Actions: u64 {
        /// Request macros.
        ///
        /// This flag enables the [`ContextApi::request_macros`] method.
        ///
        /// [`ContextApi::request_macros`]: struct.ContextApi.html#method.request_macros
        const REQUEST_MACROS = sys::SMFIF_SETSYMLIST;

        /// Replace the envelope sender (`MAIL FROM` address) of a message.
        ///
        /// This flag enables the [`ContextApi::replace_sender`] action.
        ///
        /// [`ContextApi::replace_sender`]: struct.ContextApi.html#method.replace_sender
        const REPLACE_SENDER = sys::SMFIF_CHGFROM;
        /// Add an envelope recipient (`RCPT TO` address) for a message.
        ///
        /// Together with [`ADD_RECIPIENT_EXT`], this flag enables the
        /// [`ContextApi::add_recipient`] action.
        ///
        /// [`ADD_RECIPIENT_EXT`]: #associatedconstant.ADD_RECIPIENT_EXT
        /// [`ContextApi::add_recipient`]: struct.ContextApi.html#method.add_recipient
        const ADD_RECIPIENT = sys::SMFIF_ADDRCPT;
        /// Add an envelope recipient (`RCPT TO` address) for a message,
        /// including ESMTP arguments.
        ///
        /// Together with [`ADD_RECIPIENT`], this flag enables the
        /// [`ContextApi::add_recipient`] action.
        ///
        /// [`ADD_RECIPIENT`]: #associatedconstant.ADD_RECIPIENT
        /// [`ContextApi::add_recipient`]: struct.ContextApi.html#method.add_recipient
        const ADD_RECIPIENT_EXT = sys::SMFIF_ADDRCPT_PAR;
        /// Remove an envelope recipient (`RCPT TO` address) from a message.
        ///
        /// This flag enables the [`ContextApi::remove_recipient`] action.
        ///
        /// [`ContextApi::remove_recipient`]: struct.ContextApi.html#method.remove_recipient
        const REMOVE_RECIPIENT = sys::SMFIF_DELRCPT;
        /// Add a header to a message.
        ///
        /// This flag enables the [`ContextApi::add_header`] and
        /// [`ContextApi::insert_header`] actions.
        ///
        /// [`ContextApi::add_header`]: struct.ContextApi.html#method.add_header
        /// [`ContextApi::insert_header`]: struct.ContextApi.html#method.insert_header
        const ADD_HEADER = sys::SMFIF_ADDHDRS;
        /// Replace a header of a message.
        ///
        /// This flag enables the [`ContextApi::replace_header`] action.
        ///
        /// [`ContextApi::replace_header`]: struct.ContextApi.html#method.replace_header
        const REPLACE_HEADER = sys::SMFIF_CHGHDRS;
        /// Replace the body of a message.
        ///
        /// This flag enables the [`ContextApi::append_body_chunk`] action.
        ///
        /// [`ContextApi::append_body_chunk`]: struct.ContextApi.html#method.append_body_chunk
        const REPLACE_BODY = sys::SMFIF_CHGBODY;
        /// Quarantine a message.
        ///
        /// This flag enables the [`ContextApi::quarantine`] action.
        ///
        /// [`ContextApi::quarantine`]: struct.ContextApi.html#method.quarantine
        const QUARANTINE = sys::SMFIF_QUARANTINE;
    }
}

bitflags! {
    /// Flags representing milter protocol options.
    ///
    /// These flags are used during negotiation, in the [`on_negotiate`]
    /// callback. According with the usage in that callback, `ProtocolOpts`
    /// flags may denote either what the MTA can do (MTA advertises
    /// capabilities), or what the milter application wants to do (milter
    /// requests capabilities).
    ///
    /// [`on_negotiate`]: https://docs.rs/milter-callback/0.2.2/milter_callback/attr.on_negotiate.html
    #[derive(Default)]
    pub struct ProtocolOpts: u64 {
        /// Do not use the `connect` stage callback.
        const NO_CONNECT = sys::SMFIP_NOCONNECT;
        /// Do not use the `helo` stage callback.
        const NO_HELO = sys::SMFIP_NOHELO;
        /// Do not use the `mail` stage callback.
        const NO_MAIL = sys::SMFIP_NOMAIL;
        /// Do not use the `rcpt` stage callback.
        const NO_RCPT = sys::SMFIP_NORCPT;
        /// Do not use the `data` stage callback.
        const NO_DATA = sys::SMFIP_NODATA;
        /// Do not use the `header` stage callback.
        const NO_HEADER = sys::SMFIP_NOHDRS;
        /// Do not use the `eoh` stage callback.
        const NO_EOH = sys::SMFIP_NOEOH;
        /// Do not use the `body` stage callback.
        const NO_BODY = sys::SMFIP_NOBODY;
        /// Do not use the `unknown` stage callback.
        const NO_UNKNOWN = sys::SMFIP_NOUNKNOWN;

        /// Allow skipping further (repeated) calls to the same callback.
        const SKIP = sys::SMFIP_SKIP;

        /// Also send rejected envelope recipients.
        const REJECTED_RCPT = sys::SMFIP_RCPT_REJ;

        /// Respond with [`Noreply`] in the `connect` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_CONNECT = sys::SMFIP_NR_CONN;
        /// Respond with [`Noreply`] in the `helo` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_HELO = sys::SMFIP_NR_HELO;
        /// Respond with [`Noreply`] in the `mail` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_MAIL = sys::SMFIP_NR_MAIL;
        /// Respond with [`Noreply`] in the `rcpt` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_RCPT = sys::SMFIP_NR_RCPT;
        /// Respond with [`Noreply`] in the `data` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_DATA = sys::SMFIP_NR_DATA;
        /// Respond with [`Noreply`] in the `header` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_HEADER = sys::SMFIP_NR_HDR;
        /// Respond with [`Noreply`] in the `eoh` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_EOH = sys::SMFIP_NR_EOH;
        /// Respond with [`Noreply`] in the `body` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_BODY = sys::SMFIP_NR_BODY;
        /// Respond with [`Noreply`] in the `unknown` stage.
        ///
        /// [`Noreply`]: enum.Status.html#variant.Noreply
        const NOREPLY_UNKNOWN = sys::SMFIP_NR_UNKN;

        /// Neither trim nor add leading space in header values.
        ///
        /// Header lines in the canonical form `Name: Value` include leading
        /// whitespace in the header value, which is stripped by default (or
        /// added when [adding headers]). With this option that leading space is
        /// kept exactly as given.
        ///
        /// [adding headers]: struct.ContextApi.html#method.add_header
        const HEADER_LEADING_SPACE = sys::SMFIP_HDR_LEADSPC;
    }
}

/// The milter protocol stage.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Stage {
    /// The `connect` stage.
    Connect = sys::SMFIM_CONNECT as _,
    /// The `helo` stage.
    Helo = sys::SMFIM_HELO as _,
    /// The `mail` stage.
    Mail = sys::SMFIM_ENVFROM as _,
    /// The `rcpt` stage.
    Rcpt = sys::SMFIM_ENVRCPT as _,
    /// The `data` stage.
    Data = sys::SMFIM_DATA as _,
    /// The `eoh` stage.
    Eoh = sys::SMFIM_EOH as _,
    /// The `eom` stage.
    Eom = sys::SMFIM_EOM as _,
}

/// The callback response status.
///
/// A response status is returned from all milter callbacks. It controls whether
/// and how processing of some entity is to proceed; ‘entity’ signifies either
/// connection, message, or recipient, according to which protocol stage the
/// status is returned from.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Status {
    /// Proceed to the next stage. This is the neutral, default response.
    Continue = sys::SMFIS_CONTINUE as _,
    /// Reject the entity being processed.
    Reject = sys::SMFIS_REJECT as _,
    /// Reject the entity being processed with a temporary failure (client may
    /// retry).
    Tempfail = sys::SMFIS_TEMPFAIL as _,
    /// Accept the entity being processed but discard the message.
    Discard = sys::SMFIS_DISCARD as _,
    /// Accept the entity being processed.
    Accept = sys::SMFIS_ACCEPT as _,
    /// Do not send a reply to the MTA. When negotiated for a particular stage,
    /// this status must always be used.
    ///
    /// This status is only available if it has been [negotiated] beforehand.
    ///
    /// [negotiated]: struct.Milter.html#method.on_negotiate
    Noreply = sys::SMFIS_NOREPLY as _,
    /// Skip further (repeated) calls to this callback. This is useful in the
    /// `body` stage, where potentially costly transmission of body content may
    /// be cut short once the milter has received enough data.
    ///
    /// This status is only available if it has been [negotiated] beforehand.
    ///
    /// [negotiated]: struct.Milter.html#method.on_negotiate
    Skip = sys::SMFIS_SKIP as _,

    /// A special status indication used only in [negotiation]: enable all
    /// actions and protocol stages the MTA has to offer.
    ///
    /// [negotiation]: struct.Milter.html#method.on_negotiate
    AllOpts = sys::SMFIS_ALL_OPTS as _,
}

impl Default for Status {
    fn default() -> Self {
        Self::Continue
    }
}
