#ifndef INCLUDED_BOBCAT_MILTER_
#define INCLUDED_BOBCAT_MILTER_

#include <sys/types.h>
#include <sys/stat.h>
#include <libmilter/mfapi.h>
#include <unordered_map>
#include <string>

#include <bobcat/exception>

namespace FBB
{

class Milter
{
    static std::string s_name;
    static Milter *s_mp;
    static bool s_callClose;

    using iterator =  std::unordered_map<SMFICTX *, Milter *>::iterator;
    static std::unordered_map<SMFICTX *, Milter *> s_map;

    SMFICTX *d_ctx;                 // for local use only

    public:
        virtual ~Milter();          // empty

        using callback_set =  size_t;

        enum CallBack               // using exp(2) to select the callbacks
        {                           // to use in initialize.cc
            CONNECT     = 1 << 0,   
            HELO        = 1 << 1,   
            SENDER      = 1 << 2,   
            RECIPIENT   = 1 << 3,
            HEADER      = 1 << 4,
            EOH         = 1 << 5,
            BODY        = 1 << 6,
            EOM         = 1 << 7,
            ABORT       = 1 << 8,
            CLOSE       = 1 << 9,
            UNKNOWN     = 1 << 10, // version > 2
            DATA        = 1 << 11, // version > 3

            ALL_CALLBACKS = (DATA << 1) - 1
        };

        using flag_set =  unsigned long;
        enum Flags
        {
            NO_FLAGS = 0L,
            ADD_HEADERS     = SMFIF_ADDHDRS,    // filter may add headers
            ADD_RECIPIENTS  = SMFIF_ADDRCPT,    // filter may add recipients
            CHANGE_BODY     = SMFIF_CHGBODY,    // filter may replace body
            CHANGE_HEADERS  = SMFIF_CHGHDRS,    // filter may change/delete
                                                // headers
// NEW:
            CHANGE_ENVELOPE = SMFIF_CHGFROM,    // filter may change 'From '
                                                // (envelope sender)
            DELETE_RECIPIENTS = SMFIF_DELRCPT,  // filter may delete recipients
            QUARANTINE      = SMFIF_QUARANTINE, // filter may quarantine
                                                // envelope
// CHG:
            ALL_FLAGS   =   ADD_HEADERS         | ADD_RECIPIENTS    |
                            CHANGE_BODY         | CHANGE_HEADERS    |
                            CHANGE_ENVELOPE     | DELETE_RECIPIENTS | 
                            QUARANTINE
        };

        enum Status
        {
            ACCEPT  =   SMFIS_ACCEPT,
            CONTINUE =  SMFIS_CONTINUE,
            DISCARD =   SMFIS_DISCARD,
            REJECT =    SMFIS_REJECT,
            TEMPFAIL =  SMFIS_TEMPFAIL,
        };

        enum Return
        {
            FAILURE = MI_FAILURE,
            SUCCESS = MI_SUCCESS,
        };

        static void initialize(std::string const &name, Milter &milter,
                callback_set callbacks = CONNECT, flag_set flags = NO_FLAGS);

        static bool start();                                        // .f
        static void stop();                                         // .f
        static std::string const &name();                           // .f

    protected:
        Milter() = default;
        Milter(Milter const &other) = delete;

        bool addHeader(std::string const &hdrName,                  // .f
                        std::string const &hdrValue);

                                    // from eom() only
                                    // hdr-name: without :
                                    // hdr-value: \n and \t ok.
        bool addRecipient(std::string const &rcptName);             // .f

        bool changeHeader(std::string const &hdrName,               // .f
                        unsigned headerNr, std::string const &hdrValue);

        int changeEnvelope(std::string const &envelope);            // .f

        bool deleteRecipient(std::string const &rcptName);          // .f

        SMFICTX *id() const;                                        // .f

        bool insertHeader(size_t hdrIdx, std::string const &hdrName,    // .f
                          std::string const &hdrValue);

        static bool openSocket(bool removeIfTrue = true);           // .f

        bool quarantine(std::string const &reason);                 // .f

        bool replaceBody(std::string const &body);                  // .f

        static bool setBacklog(size_t backlog = 5);                 // .f

        static void setConnection(std::string const &socketName);   // .f

                                                                    // .d
        bool setReply(std::string const &rcode, std::string const &xcode = "",
                      std::string const &msg = "");
        // can't do smfi_setmlreply(ctx, ...)
        // since there is no smfi_Vsetmlreply(ctx, ...)
        static void setTimeout(size_t seconds = 7210);              // .f
        char const *symval(std::string const &name) const;          // .f

#if SMFI_VERSION > 2
        virtual sfsistat unknown(char const *ptr);
#endif
        bool wait();                          // from eom() only    // .f

    private:

//        // tmp
//        virtual int chgfrom(SMFICTX *, char *) = 0;

        virtual sfsistat abort();
        virtual sfsistat body(unsigned char *text, size_t length);

        virtual Milter *clone() const = 0;       // cloning required

        virtual sfsistat close();
        virtual sfsistat connect(char *hostname, _SOCK_ADDR *hostaddr);
#if SMFI_VERSION > 3
        virtual sfsistat data();
#endif
        virtual sfsistat eoh();
        virtual sfsistat eom();
        virtual sfsistat header(char *headerf, char *headerv);
        virtual sfsistat helo(char *helohost);

        virtual sfsistat recipient(char **argv);
        virtual sfsistat sender(char **argv);

        static Milter *install(SMFICTX *ctx);

        static sfsistat mConnect(SMFICTX *ctx, char *hostname,
                                             _SOCK_ADDR *hostaddr);
        static sfsistat mAbort(SMFICTX *ctx);
        static sfsistat mBody(SMFICTX *ctx, unsigned char *body, size_t len);
        static sfsistat mSender(SMFICTX *ctx, char **argv);
        static sfsistat mRecipient(SMFICTX *ctx, char **argv);
        static sfsistat mEoh(SMFICTX *ctx);
        static sfsistat mEom(SMFICTX *ctx);
        static sfsistat mHeader(SMFICTX *ctx, char *headerf, char *headerv);
        static sfsistat mHelo(SMFICTX *ctx, char *helohost);
        static sfsistat mClose(SMFICTX *ctx);

#if SMFI_VERSION > 2
        static sfsistat mUnknown(SMFICTX *ctx, char const *ptr);
#endif /* SMFI_VERSION > 2 */

#if SMFI_VERSION > 3
        static sfsistat mData(SMFICTX *ctx);
#endif /* SMFI_VERSION > 3 */
};

inline bool Milter::start()
{
    return smfi_main() == MI_SUCCESS;
}

inline void Milter::stop()
{
    smfi_stop();
}

inline std::string const &Milter::name()
{
    return s_name;
}

// protected:

inline bool Milter::addHeader(std::string const &hdrName,
                std::string const &hdrValue)
{
    return smfi_addheader(d_ctx,
                    const_cast<char *>(hdrName.c_str()),
                    const_cast<char *>(hdrValue.c_str()))
            == SUCCESS;
}
                            // from eom() only
                            // hdr-name: without :
                            // hdr-value: \n and \t ok.
inline bool Milter::addRecipient(std::string const &rcptName)
{
    return smfi_addrcpt(d_ctx,
            const_cast<char *>(rcptName.c_str())) == SUCCESS;
}

inline bool Milter::changeHeader(std::string const &hdrName,
                unsigned headerNr, std::string const &hdrValue)
{
    return smfi_chgheader(d_ctx,
                const_cast<char *>(hdrName.c_str()),
                headerNr,
                hdrValue.length() ?
                    const_cast<char *>(hdrValue.c_str())
                :
                    0
            ) == SUCCESS;
}

    // NEW: 
    // envelope is the plain envelope address like   who@domain
    // smfi_chgfrom's 3rd arg not used (ESMTP arguments)
    // this function must be called from 
inline int Milter::changeEnvelope(std::string const &envelope)
{
    return smfi_chgfrom(d_ctx, const_cast<char *>(envelope.c_str()), 0);
//    return chgfrom(d_ctx, const_cast<char *>(envelope.c_str()))
//           == SUCCESS;
}

inline bool Milter::deleteRecipient(std::string const &rcptName)
{
    return smfi_delrcpt(d_ctx,
            const_cast<char *>(rcptName.c_str())) == SUCCESS;
}

inline SMFICTX *Milter::id() const
{
    return d_ctx;
}

inline bool Milter::insertHeader(size_t hdrIdx, std::string const &hdrName,
                  std::string const &hdrValue)
{
    return smfi_insheader(d_ctx, hdrIdx,
            const_cast<char *>(hdrName.c_str()),
            const_cast<char *>(hdrValue.c_str()));
}
inline bool Milter::openSocket(bool removeIfTrue)
{
    return smfi_opensocket(removeIfTrue) == SUCCESS;
}

inline bool Milter::quarantine(std::string const &reason)
{
    return  smfi_quarantine(d_ctx,
            const_cast<char *>(reason.c_str())) == SUCCESS;
}

inline bool Milter::replaceBody(std::string const &body)
{
    return smfi_replacebody(d_ctx,
                reinterpret_cast<unsigned char *>
                (
                    const_cast<char *>(body.c_str())
                ),
                body.length()) == SUCCESS;
}

inline bool Milter::setBacklog(size_t backlog)
{
    return smfi_setbacklog(backlog) == SUCCESS;
}

inline void Milter::setConnection(std::string const &socketName)
{
    smfi_setconn(const_cast<char *>(socketName.c_str()));
}

inline bool Milter::setReply(std::string const &rcode, 
                std::string const &xcode, std::string const &msg)
{
    return smfi_setreply(d_ctx,
                const_cast<char *>(rcode.c_str()),
                xcode.length() ?
                    const_cast<char *>(xcode.c_str())
                :
                    0,
                const_cast<char *>(msg.c_str()));

        // rcode: The three-digit (RFC 821/2821) SMTP reply code, as a
        // null-terminated string. rcode cannot be NULL, and must be a
        // valid 4XX or 5XX reply code.

        // xxcode The extended (RFC 1893/2034) reply code. If xcode is
        // NULL, no extended code is used. Otherwise, xcode must
        // conform to RFC 1893/2034.



        // * Values passed to smfi_setreply are not checked for
        // standards compliance.

        // * The message parameter should contain only printable
        // characters, other characters may lead to undefined
        // behavior. For example, CR or LF will cause the call to
        // fail, single '%' characters will cause the text to be
        // ignored (if there really should be a '%' in the string, use
        // '%%' just like for printf(3)).

        // * If the reply code (rcode) given is a '4XX' code but
        // SMFI_REJECT is used for the message, the custom reply is
        // not used.

        // * Similarly, if the reply code (rcode) given is a '5XX'
        // code but SMFI_TEMPFAIL is used for the message, the custom
        // reply is not used.

        // Note: in neither of the last two cases an error is returned
        // to the milter, libmilter silently ignores the reply code.

        // * If the milter returns SMFI_TEMPFAIL and sets the reply
        // code to '421', then the SMTP server will terminate the SMTP
        // session with a 421 error code.
}
// can't do smfi_setmlreply(ctx, ...)
// since there is no smfi_Vsetmlreply(ctx, ...)
inline void Milter::setTimeout(size_t seconds)
{
    smfi_settimeout(seconds);
}

inline char const *Milter::symval(std::string const &name) const
{
    return smfi_getsymval(d_ctx,
            const_cast<char *>(name.c_str()));
}

inline bool Milter::wait()                           // from eom() only
{
    return smfi_progress(d_ctx) == SUCCESS;
}

}

#endif





