// indymilter – asynchronous milter library
// Copyright © 2021–2024 David Bürgin <dbuergin@gluet.ch>
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/>.

use bitflags::bitflags;
use std::{ffi::CString, net::SocketAddr};

bitflags! {
    /// Flags that represent `eom` actions.
    #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
    pub struct Actions: u32 {
        /// Enable macro requests during negotiation. Not used.
        const REQUEST_MACROS = 0x100;
        /// Enable action
        /// [`change_sender`][crate::ContextActions::change_sender].
        const CHANGE_SENDER = 0x40;
        /// Enable action
        /// [`add_recipient`][crate::ContextActions::add_recipient].
        const ADD_RCPT = 0x4;
        /// Enable action
        /// [`add_recipient_ext`][crate::ContextActions::add_recipient_ext].
        const ADD_RCPT_EXT = 0x80;
        /// Enable action
        /// [`delete_recipient`][crate::ContextActions::delete_recipient].
        const DELETE_RCPT = 0x8;
        /// Enable actions [`add_header`][crate::ContextActions::add_header] and
        /// [`insert_header`][crate::ContextActions::insert_header].
        const ADD_HEADER = 0x1;
        /// Enable action
        /// [`change_header`][crate::ContextActions::change_header].
        const CHANGE_HEADER = 0x10;
        /// Enable action [`replace_body`][crate::ContextActions::replace_body].
        const REPLACE_BODY = 0x2;
        /// Enable action [`quarantine`][crate::ContextActions::quarantine].
        const QUARANTINE = 0x20;
    }
}

impl Actions {
    pub(crate) fn min_flags() -> Self {
        Self::ADD_RCPT | Self::DELETE_RCPT | Self::ADD_HEADER | Self::REPLACE_BODY
    }
}

bitflags! {
    /// Flags that represent milter protocol options.
    #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
    pub struct ProtoOpts: u32 {
        /// Request that the `connect` stage not be invoked.
        const NO_CONNECT = 0x1;
        /// Request that the `helo` stage not be invoked.
        const NO_HELO = 0x2;
        /// Request that the `mail` stage not be invoked.
        const NO_MAIL = 0x4;
        /// Request that the `rcpt` stage not be invoked.
        const NO_RCPT = 0x8;
        /// Request that the `data` stage not be invoked.
        const NO_DATA = 0x200;
        /// Request that the `header` stage not be invoked.
        const NO_HEADER = 0x20;
        /// Request that the `eoh` stage not be invoked.
        const NO_EOH = 0x40;
        /// Request that the `body` stage not be invoked.
        const NO_BODY = 0x10;
        /// Request that the `unknown` stage not be invoked.
        const NO_UNKNOWN = 0x100;

        /// Declare that the `connect` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_CONNECT = 0x1000;
        /// Declare that the `helo` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_HELO = 0x2000;
        /// Declare that the `mail` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_MAIL = 0x4000;
        /// Declare that the `rcpt` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_RCPT = 0x8000;
        /// Declare that the `data` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_DATA = 0x10000;
        /// Declare that the `header` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_HEADER = 0x80;
        /// Declare that the `eoh` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_EOH = 0x40000;
        /// Declare that the `body` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_BODY = 0x80000;
        /// Declare that the `unknown` stage always returns
        /// [`Status::Noreply`][crate::Status::Noreply].
        const NOREPLY_UNKNOWN = 0x20000;

        /// Request that the MTA also send rejected recipients.
        const REJECTED_RCPT = 0x800;
        /// Keep leading space in header values exactly as given.
        const LEADING_SPACE = 0x100000;
        /// Declare that a callback stage may respond with
        /// [`Status::Skip`][crate::Status::Skip].
        const SKIP = 0x400;
    }
}

impl ProtoOpts {
    pub(crate) fn min_flags() -> Self {
        Self::NO_CONNECT
            | Self::NO_HELO
            | Self::NO_MAIL
            | Self::NO_RCPT
            | Self::NO_HEADER
            | Self::NO_BODY
    }
}

/// Socket information.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum SocketInfo {
    /// An unrecognized socket type or not a socket.
    Unknown,
    /// An internet socket.
    Inet(SocketAddr),
    /// A UNIX domain socket.
    Unix(CString),
}

impl From<SocketAddr> for SocketInfo {
    fn from(addr: SocketAddr) -> Self {
        Self::Inet(addr)
    }
}

impl From<CString> for SocketInfo {
    fn from(path: CString) -> Self {
        Self::Unix(path)
    }
}
