/*******************************************************************************
 * Copyright (c) 2014, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#pragma once

#include <cstddef>
#include <cstdint>
#include <deque>
#include <string>
#include <vector>

namespace libhttppp::hpack {

struct HeaderField {
    std::string name;
    std::string value;
    bool incremental_index = false;
};

class Encoder {
public:
    static std::string encodeResponseHeaders(uint16_t status_code,
                                              const std::string &content_type,
                                              size_t content_length,
                                              const std::vector<HeaderField> &extra = {});

    static std::string encodeRequestHeaders(const std::string &method,
                                             const std::string &path,
                                             const std::string &scheme,
                                             const std::string &authority,
                                             const std::vector<HeaderField> &extra = {});

private:
    static void appendLiteral(std::string &out,
                              const std::string &name,
                              const std::string &value,
                              bool incremental_index);
    static void appendLiteralWithNameIndex(std::string &out,
                                           uint8_t name_index,
                                           const std::string &value,
                                           bool incremental_index);
};

class Decoder {
public:
    Decoder() = default;
    ~Decoder() = default;

    // Non-copyable / non-movable — the dynamic table must not be
    // accidentally shared or left in a moved-from state.
    Decoder(const Decoder &) = delete;
    Decoder &operator=(const Decoder &) = delete;
    Decoder(Decoder &&) = delete;
    Decoder &operator=(Decoder &&) = delete;

    void reset() { _dynTable.clear(); _dynTableSize = 0; }

    // Instance method: decode using (and updating) the dynamic table
    std::vector<HeaderField> decode(const uint8_t *data, size_t len);
    std::vector<HeaderField> decode(const std::string &data);

    // Static convenience (stateless, no dynamic table — for one-shot use)
    static std::vector<HeaderField> decodeStateless(const uint8_t *data, size_t len);

    // Shared utilities (also used by QPACK decoder)
    static uint32_t decodeInt(const uint8_t *data, size_t len, uint8_t prefix_bits, size_t &bytes_read);
    static std::string decodeString(const uint8_t *data, size_t len, size_t &bytes_read);
    static std::string huffmanDecode(const uint8_t *data, size_t len);

    // RFC 9204 (QPACK) reuses RFC 7541 Appendix B's Huffman table verbatim,
    // so this is shared with the QPACK encoder too rather than duplicated.
    static std::string huffmanEncode(const std::string &in);

private:
    // Dynamic table (RFC 7541 §2.3.2): newest entry at front
    struct DynEntry { std::string name; std::string value; };
    std::deque<DynEntry> _dynTable;
    size_t _dynTableSize = 0;           // current size in octets
    size_t _dynTableMaxSize = 4096;     // default per RFC 7541 §6.5.2

    // Ceiling a peer's Dynamic Table Size Update (section 6.3) may raise
    // _dynTableMaxSize to. We never send our own SETTINGS_HEADER_TABLE_SIZE,
    // so the RFC 7541 §6.5.2 default of 4096 is what we've implicitly agreed
    // to; without this clamp a peer's update is taken as-is (up to ~4 billion).
    static constexpr uint32_t kMaxDynTableSize = 4096;

    // Look up an index across static + dynamic tables
    bool lookupIndex(uint32_t idx, std::string &name, std::string &value) const;
    bool lookupIndexName(uint32_t idx, std::string &name) const;

    // Add an entry to the dynamic table, evicting as necessary
    void addEntry(const std::string &name, const std::string &value);
    void evict(size_t required);

    struct StaticEntry { const char *name; const char *value; };
    static const StaticEntry STATIC_TABLE[];
    static const size_t STATIC_TABLE_SIZE;
};

} // namespace libhttppp::hpack
