#pragma once
#include "common.hpp"
#include "config.hpp"
#include "dns.hpp"
#include "hooks.hpp"
#include "sock.hpp"
#include "stats.hpp"
#include "sni.hpp"
#include "proxy.hpp"
#include "bump.hpp"
#include "io.hpp"
#include "ac.hpp"


/**\verbatim
 * AioFile                       IoSource client_in --> chain_t upstream   --> IoSink server_out  --> chain_t upstream_splice
 *    ^                                 v                                             v                  v
 * AioSink client_splice         IoWrapper client                              IoWrapper server       AioSink server_splice
 *    ^                                 ^                                             ^                  v
 * chain_t downstream_splice <-- IoSink client_out  <-- chain_t downstream <-- IoSource server_in     AioFile
 \endverbatim*/
class Ctx {
    private:
        typedef enum {
            STATE_INIT,
            STATE_PROXY,
            STATE_DNS,
            STATE_SNI,
            STATE_BUMP_OP,
            STATE_AC,
            STATE_BUMP,
            STATE_HTTP_REQ,
            STATE_DENY,
            STATE_CONNECT,
            STATE_CERT_CN,
            STATE_CN_AC,
            STATE_IO
        } state_t;
        state_t state, max_state;

        typedef struct {
            bool internal; ///< not possible outside handler
            bool short_timeout; ///< want to be notified after the short timeout elapsed?
            event_t client_ign_ev; ///< if != EVENT_NONE, don't listen for server events (only CLOSE, if any), don't care about server status for client events, and don't listen for given client events, even if they would be possible chain-wise (might be combined w/ #server_ign_ev)
            event_t server_ign_ev; ///< @see #client_ign_ev
        } state_info_t; ///< state metadata for sanity checks and update_events()
        static const state_info_t state_info[STATE_IO+1];

        const sockaddr_t src;
        sockaddr_t dst;

        IoWrapper client, server;
        IoSource client_in, server_in;
        IoSink client_out, server_out;
        chain_t upstream, downstream;
        #if (SPLICE)
        AioSink client_splice, server_splice;
        chain_t upstream_splice, downstream_splice;
        #endif

        char* host; ///< the SNI
        buf_t* host_peek_buf; ///< pre-read client data peeked for getting the SNI
        DNSResult* resolver; ///< in case we have to resolve a HTTP CONNECT tunnel destination
        AccessControlResult* ac; ///< holds the ac result, if any
        bump_op_t bump_op;
        Bump* bump; ///< holds the ssl stuff for the client side, if any
        tristate_t is_http; ///< whether we already have found a client-side HTTP request (if bumped)

        time_t last_io; ///< for long downloads etc. one side might legitimately timeout. so we use the idle time to timeout only if we did not see anything from either side.
        unsigned last_pollid; ///< remember when we're called the last time
        static unsigned last_ctx_no;
        const unsigned ctx_no;
        logctx_t log_ctx;
        const msec_t start_msec;

        void do_io();
        void s_handler(event_t);
        void c_handler(event_t);
        void access_log();

    public:
        Ctx(int, sockaddr_t, sockaddr_t); ///< on freshly accepted connection for #client
        ~Ctx();

        static unsigned ctx_num; ///< how many instances exist right now

        bool update_events(poll_handler_t); ///< returns whether both sides are finished/closed
        bool handler(int, event_t, unsigned); ///< returns false upon noop
};


class CtxAcceptor {
    private:
        static void handler(int, event_t, unsigned, void*);

    public:
        static void accept_handler(int, event_t, unsigned, void*); ///< creates new instance for every accepted connection
};