#pragma once
#include "chain.hpp"
#include "sock.hpp"
#include "stats.hpp"
#include "ssl.hpp"
#include "aio.hpp"
#include <map>


/** simple fd or ssl i/o operation wrapper that handles fd, ssl, and poll cleanup **/
class IoWrapper {
    private:
        int fd;
        ssl_t* ssl;
        bool polled;
        bool got_close;

    public:
        INLINE IoWrapper(): fd(-1), ssl(NULL), polled(false), got_close(false), reset_ptr(NULL) {}
        INLINE IoWrapper(int _fd): fd(_fd), ssl(NULL), polled(false), got_close(false), reset_ptr(NULL) {}
        INLINE IoWrapper(ssl_t* _ssl): fd(_ssl->fd), ssl(_ssl), polled(false), got_close(false), reset_ptr(NULL) {}

        INLINE ~IoWrapper() { reset(); }
        void reset(); ///< closes fd, frees ssl if any, removes from poll, resets #reset_ptr
        void** reset_ptr; ///< upon (real) reset, set this to NULL, if any

        void set(int); ///< resets and switches to this fd
        void set(ssl_t*); ///< resets and switches to this ssl, but allows upgrading from fd to its own ssl

        //INLINE const ssl_t* get() const { return ssl; }
        INLINE operator int() const { return fd; }
        INLINE operator const int*() const { return &fd; }

        INLINE bool is_valid() const { return (fd != -1); }
        INLINE bool is_ssl() const { return (ssl != NULL); }
        INLINE int info() const { return ssl? -fd: fd; } ///< for logging purposes only
        bool poll(poll_handler_t, event_t, bool short_timeout=false, void* =NULL); ///< poll add/mod/del wrapper, removes upon EVENT_NONE

        INLINE bool closed() const { return got_close; }
        INLINE void closed(bool c) { assert(got_close == c || c); got_close = c; }

        INLINE ssize_t do_read(char* b, size_t l) { return ssl? ssl_read(ssl, b, l): ((fd != -1)? read(fd, b, l): (errno = EBADF, -1)); }
        INLINE ssize_t do_write(const char* b, size_t l) { return ssl? ssl_write(ssl, b, l): ((fd != -1)? write(fd, b, l): (errno = EBADF, -1)); }

        bool send_alert(); ///< tries to send a tls alert if it's no ssl connection yet or if it's ssl and no data has been sent yet
};


class IoSource {
    private:
        IoWrapper* handler;
        chain_t* chain;
        size_t total; // TODO: need uint64_t?
        bool read_wants_write;

    public:
        stat_t* rx_stats; ///< whether incoming traffic should be accounted for
        int err; ///< last encoutered read errno
        msec_t stime; ///< first read timestamp
        static void (*data_in_handler)(const chain_t*);

        INLINE IoSource(IoWrapper* _handler, chain_t* _out_chain): handler(_handler), chain(_out_chain), total(0), read_wants_write(false), rx_stats(NULL), err(0), stime(0) {
            assert(handler && chain);
        }

        event_t events(const IoWrapper*) const;
        INLINE event_t events() const {
            assert(handler->is_valid());
            return read_wants_write? EVENT_OUT: (chain->is_full()? EVENT_NONE: EVENT_IN);
        }

        INLINE size_t get_total() const { return total; }

        bool do_io(); ///< reads and appends to chain if possible, resets #handler upon err, returns true if new data has actually been read.
};


class IoSink {
    private:
        IoWrapper* handler;
        chain_t* in_chain;
        chain_t* out_chain; ///< can be NULL
        size_t total; // TODO: need uint64_t?
        bool write_wants_read;
        std::map<size_t, buf_t*> inject_bufs; ///< inject/write some data at the given total offset. TODO: this wastes lots of space, might use the slab pool

    public:
        int err; ///< last encoutered write errno
        msec_t stime; ///< first write timestamp

        INLINE IoSink(IoWrapper* _handler, chain_t* _in_chain): handler(_handler), in_chain(_in_chain), out_chain(NULL), total(0), write_wants_read(false), err(0), stime(0) {
            assert(handler && in_chain);
        }
        ~IoSink();

        event_t events(const IoWrapper*) const;
        INLINE event_t events() const {
            assert(handler->is_valid() && !handler->closed());
            return write_wants_read? EVENT_IN: (in_chain->is_empty()? EVENT_NONE: EVENT_OUT); // XXX: don't care about #out_chain
        }

        INLINE size_t get_total() const { return total; }

        bool splice_to(chain_t*); ///< tries to set or remove the chain to splice to
        bool do_io(); ///< pops from #in_chain and writes, eventually tries to push to #out_chain, returns true if something has actually been written.
        bool set_http_req(const sockaddr_t& cip, const sockaddr_t& sip, const char* sni, size_t sni_len); ///< change http request to a proxy request with x-forwarded-* headers. ATTENTION: This is only done for the first of possibly more keepalive-requests, so the upstream proxy must stick to the initial headers.
};


class AioSink {
    private:
        AioFile* aio;
        chain_t* chain;

        static void cb(intptr_t); ///< free's the given buf_t, if any. @see AioFile::aio_handler_t

    public:
        INLINE AioSink(): aio(NULL), chain(NULL) {}
        INLINE ~AioSink() {
            if (aio) aio->finish(); // TODO: try do_io() one last time?
        }

        bool set(const char*, chain_t*); ///< sets or unsets file to write to from chain
        void do_io();
};