#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
        {
            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
            DELETE_RECIPIENTS = SMFIF_DELRCPT,  // filter may delete recipients
            QUARANTINE      = SMFIF_QUARANTINE, // filter may quarantine
                                                // envelope

            ALL_FLAGS   =   ADD_HEADERS         | ADD_RECIPIENTS    |
                            CHANGE_BODY         | CHANGE_HEADERS    |
                            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);

        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:
        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 */
};

#include "milter.f"

}

#endif





