#pragma once
#include "common.hpp"
#include "pool.hpp"


class buf_t {
    private:
        char buf[BUF_SIZE];
        size_t off;
        size_t len;

    public:
        buf_t* next;

        INLINE buf_t(): off(0), len(0), next(NULL) {}

        INLINE bool has_space() const {
            return len < sizeof(buf);
        }
        INLINE bool has_data() const {
            return off != len;
        }

        INLINE void get_space(char*& b, size_t& l) {
            assert(has_space());
            b = buf+len;
            l = sizeof(buf)-len;
        }
        INLINE size_t get_data() {
            return len - off;
        }
        INLINE void get_data(char*& b, size_t& l) {
            assert(has_data());
            b = buf+off;
            l = len-off;
        }
        INLINE void get_data(const char*& b, size_t& l) const {
            assert(has_data());
            b = buf+off;
            l = len-off;
        }

        INLINE void del_data(size_t l) {
            off += l;
            assert(off <= len);
        }
        INLINE void add_data(size_t l) {
            len += l;
            assert(len <= sizeof(buf));
        }
        INLINE bool add_nul() {
            if (!has_space()) return false;
            buf[len++] = '\0';
            return true;
        }

        INLINE bool is_done() const {
            return off == sizeof(buf) && len == sizeof(buf);
        }
        INLINE bool is_done(size_t l) const {
            return off+l == sizeof(buf) && len == sizeof(buf);
        }

        INLINE void rewind() { ///< revert del_data()
            off = 0;
        }

        INLINE void reset() {
            off = 0;
            len = 0;
        }

        void debug();
};
extern CPoolPTS<buf_t> buf_pool; ///< threadsafe due to @see AioSink::cb


class chain_t {
    private:
        buf_t* head;
        buf_t* tail;
        size_t bufs;
        bool is_initial_data; ///< whether we still have the very first byte

        buf_t* pop_head();
        tristate_t startswith(const char*, size_t) const;

    public:
        INLINE chain_t(): head(NULL), tail(NULL), bufs(0), is_initial_data(true) {
        }
        INLINE ~chain_t() {
            reset();
        }

        INLINE void reset() {
            unless (bufs) return;
            bufs = 0;
            tail = NULL;
            // nope: is_initial_data = true;
            buf_t* node = head;
            while (node) {
                head = head->next;
                buf_pool.push(node);
                node = head;
            }
        }

        INLINE bool is_empty() const { ///< whether get_data() will succeed
            return !head || !head->has_data();
        }
        INLINE bool is_full() const { ///< whether get_space() or add_data(buf_t*) will succeed
            return bufs >= BUF_MAX && !tail->has_space();
        }

        INLINE bool get_data(char*& b, size_t& l) {
            if (head && head->has_data()) {
                head->get_data(b, l);
                return true;
            } else {
                return false;
            }
        }
        INLINE bool get_data(const char*& b, size_t& l) const {
            if (head && head->has_data()) {
                head->get_data(b, l);
                return true;
            } else {
                return false;
            }
        }
        INLINE bool get_space(char*& b, size_t& l) {
            if (tail && tail->has_space()) {
                tail->get_space(b, l);
                return true;
            } else {
                return false;
            }
        }

        INLINE buf_t* del_data(size_t l) { ///< how much of a previous get_data() has been written. if the whole buf has been written, remove and return it.
            assert(head && head->has_data());
            if (likely(l)) is_initial_data = false;
            head->del_data(l);
            if (head->is_done()) {
                head->rewind();
                return pop_head();
            } else {
                return NULL;
            }
        }
        INLINE buf_t* will_del_data(size_t l) { ///< what del_data() would return, but without actually doing it
            assert(head && head->has_data());
            return head->is_done(l)? head: NULL;
        }

        INLINE void add_data(size_t l) { ///< how much of a previous get_space() has been written
            assert(tail && tail->has_space());
            tail->add_data(l);
        }
        INLINE void add_data(buf_t* b) { ///< push a new buf with initial data, in case get_space() failed. assumes !is_full().
            assert(b->has_data());
            if (!tail) {
                assert(!bufs && !head);
                head = b;
                tail = b;
            } else {
                assert(!tail->has_space()); // as get_space() has failed
                tail->next = b;
                tail = b;
            }
            assert(bufs < BUF_MAX);
            b->next = NULL;
            ++bufs;
        }
        bool add_data(chain_t*); ///< try to clear other chain and take its content. already written/deleted but still available data will be restored. ignores is_full().
        void add_data(const char*, size_t); ///< copy from buffer and append. ignores is_full().

        //void prepend(chain_t*); ///< clears other chain and takes its content before our own. ignores is_full().
        void prepend(buf_t*); ///< inserts buffer at head i.e. as initial data. ignores size checks.

        tristate_t is_http_req() const; ///< true if initial data is available and its actually "GET /"
        tristate_t is_http_resp() const; ///< true if initial data is available and its actually "HTTP/1"

        void debug();
};