#pragma once
#include "Frame.h"
#include "HeaderValueParamsParser.h"
#include <cassert>
#include <panda/refcnt.h>
#include <panda/optional.h>
#include <zlib.h>

namespace panda { namespace protocol { namespace websocket {

struct DeflateExt {
    struct Config {
        bool client_no_context_takeover = false;    // sent is only, when it is true; always parsed
        bool server_no_context_takeover = false;    // sent is only, when it is true; always parsed
        std::uint8_t server_max_window_bits = 15;
        std::uint8_t client_max_window_bits = 15;

        // copied from parser config, no direct usage
        size_t max_message_size = 0;
        // non-negotiatiable settings
        int mem_level = 8;
        int compression_level = Z_DEFAULT_COMPRESSION;
        int strategy = Z_DEFAULT_STRATEGY;
        size_t compression_threshold = 1410;  // try to fit into TCP frame
        bool default_compress_binary = false;
    };

    struct EffectiveConfig {
        static const constexpr int HAS_CLIENT_NO_CONTEXT_TAKEOVER = 1 << 0;
        static const constexpr int HAS_SERVER_NO_CONTEXT_TAKEOVER = 1 << 1;
        static const constexpr int HAS_SERVER_MAX_WINDOW_BITS     = 1 << 2;
        static const constexpr int HAS_CLIENT_MAX_WINDOW_BITS     = 1 << 3;
        enum class NegotiationsResult { Success, NotFound, Error };

        EffectiveConfig(const Config& cfg_, NegotiationsResult result_): cfg{cfg_}, result{result_} {}
        EffectiveConfig(NegotiationsResult result_): result{result_} {}

        explicit operator bool() const { return result == NegotiationsResult::Success; }

        Config cfg;
        int flags = 0;
        NegotiationsResult result;
    };

    struct NegotiationsResult {
        enum class Result { Success, NotFound, Error };

        Config cfg;
        int flags = 0;
        Result result = Result::Error;
    };

    enum class Role { CLIENT, SERVER };

    static string bootstrap();

    static EffectiveConfig select(const HeaderValues& values, const Config& cfg, Role role);
    static void request(HeaderValues& ws_extensions, const Config& cfg);
    static DeflateExt* uplift(const EffectiveConfig& cfg, HeaderValues& extensions, Role role);

    ~DeflateExt();

    void reset_tx();
    void reset_rx();

    void reset() {
        reset_rx();
        reset_tx();
    }

    string compress (string_view src, IsFinal final) {
        string ret(src.length() + TRAILER_SIZE);
        _compress(src, ret, Z_SYNC_FLUSH);
        if (final == IsFinal::YES) finalize_message(ret);
        return ret;
    }

    template<typename It>
    string compress (It&& payload_begin, It&& payload_end, IsFinal final) {
        size_t plen = 0;
        for (auto it = payload_begin; it != payload_end; ++it) plen += it->length();

        string ret(plen + TRAILER_SIZE);
        if (plen) {
            for (auto it = payload_begin; it != payload_end; ++it) {
                _compress(*it, ret, (it + 1 == payload_end) ? Z_SYNC_FLUSH : Z_NO_FLUSH);
            }
        }
        else _compress("", ret, Z_SYNC_FLUSH);

        if (final == IsFinal::YES) finalize_message(ret);
        return ret;
    }


    void uncompress (Frame& frame);

    const Config& effective_config() const { return effective_cfg; }

    static constexpr int UNCOMPRESS_PREALLOCATE_RATIO = 10;

private:
    static const constexpr unsigned TRAILER_SIZE = 4; // tail empty frame 0x00 0x00 0xff 0xff

    Config effective_cfg;
    size_t message_size;
    size_t max_message_size;
    z_stream rx_stream;
    z_stream tx_stream;
    bool reset_after_tx;
    bool reset_after_rx;

    DeflateExt(const Config& cfg, Role role);

    void _compress (string_view src, string& dest, int flush);

    void finalize_message (string& str) {
        assert(str.length() >= TRAILER_SIZE);
        str.length(str.length() - TRAILER_SIZE);
        if (reset_after_tx) reset_tx();
    }
};

}}}