/*******************************************************************************
 * 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.
 *******************************************************************************/

#include "qpack.h"
#include "hpack.h"

namespace libhttppp::qpack {

// QPACK Static Table (RFC 9204, Appendix A) - 99 entries (0-98)
const Decoder::StaticEntry Decoder::STATIC_TABLE[] = {
    { ":authority",                  ""                                       }, // 0
    { ":path",                       "/"                                      }, // 1
    { "age",                         "0"                                      }, // 2
    { "content-disposition",         ""                                       }, // 3
    { "content-length",              "0"                                      }, // 4
    { "cookie",                      ""                                       }, // 5
    { "date",                        ""                                       }, // 6
    { "etag",                        ""                                       }, // 7
    { "if-modified-since",           ""                                       }, // 8
    { "if-none-match",               ""                                       }, // 9
    { "last-modified",               ""                                       }, // 10
    { "link",                        ""                                       }, // 11
    { "location",                    ""                                       }, // 12
    { "referer",                     ""                                       }, // 13
    { "set-cookie",                  ""                                       }, // 14
    { ":method",                     "CONNECT"                                }, // 15
    { ":method",                     "DELETE"                                 }, // 16
    { ":method",                     "GET"                                    }, // 17
    { ":method",                     "HEAD"                                   }, // 18
    { ":method",                     "OPTIONS"                                }, // 19
    { ":method",                     "POST"                                   }, // 20
    { ":method",                     "PUT"                                    }, // 21
    { ":scheme",                     "http"                                   }, // 22
    { ":scheme",                     "https"                                  }, // 23
    { ":status",                     "103"                                    }, // 24
    { ":status",                     "200"                                    }, // 25
    { ":status",                     "304"                                    }, // 26
    { ":status",                     "404"                                    }, // 27
    { ":status",                     "503"                                    }, // 28
    { "accept",                      "*/*"                                    }, // 29
    { "accept",                      "application/dns-message"                }, // 30
    { "accept-encoding",             "gzip, deflate, br"                      }, // 31
    { "accept-ranges",               "bytes"                                  }, // 32
    { "access-control-allow-headers","cache-control"                          }, // 33
    { "access-control-allow-headers","content-type"                           }, // 34
    { "access-control-allow-origin", "*"                                      }, // 35
    { "cache-control",               "max-age=0"                              }, // 36
    { "cache-control",               "max-age=2592000"                        }, // 37
    { "cache-control",               "max-age=604800"                         }, // 38
    { "cache-control",               "no-cache"                               }, // 39
    { "cache-control",               "no-store"                               }, // 40
    { "cache-control",               "public, max-age=31536000"               }, // 41
    { "content-encoding",            "br"                                     }, // 42
    { "content-encoding",            "gzip"                                   }, // 43
    { "content-type",                "application/dns-message"                }, // 44
    { "content-type",                "application/javascript"                 }, // 45
    { "content-type",                "application/json"                       }, // 46
    { "content-type",                "application/x-www-form-urlencoded"      }, // 47
    { "content-type",                "image/gif"                              }, // 48
    { "content-type",                "image/jpeg"                             }, // 49
    { "content-type",                "image/png"                              }, // 50
    { "content-type",                "text/css"                               }, // 51
    { "content-type",                "text/html; charset=utf-8"               }, // 52
    { "content-type",                "text/plain"                             }, // 53
    { "content-type",                "text/plain;charset=utf-8"               }, // 54
    { "range",                       "bytes=0-"                               }, // 55
    { "strict-transport-security",   "max-age=31536000"                       }, // 56
    { "strict-transport-security",   "max-age=31536000; includesubdomains"    }, // 57
    { "strict-transport-security",   "max-age=31536000; includesubdomains; preload" }, // 58
    { "vary",                        "accept-encoding"                        }, // 59
    { "vary",                        "origin"                                 }, // 60
    { "x-content-type-options",      "nosniff"                                }, // 61
    { "x-xss-protection",           "1; mode=block"                           }, // 62
    { ":status",                     "100"                                    }, // 63
    { ":status",                     "204"                                    }, // 64
    { ":status",                     "206"                                    }, // 65
    { ":status",                     "302"                                    }, // 66
    { ":status",                     "400"                                    }, // 67
    { ":status",                     "403"                                    }, // 68
    { ":status",                     "421"                                    }, // 69
    { ":status",                     "425"                                    }, // 70
    { ":status",                     "500"                                    }, // 71
    { "accept-language",             ""                                       }, // 72
    { "access-control-allow-credentials", "FALSE"                             }, // 73
    { "access-control-allow-credentials", "TRUE"                              }, // 74
    { "access-control-allow-headers","*"                                      }, // 75
    { "access-control-allow-methods","get"                                    }, // 76
    { "access-control-allow-methods","get, post, options"                     }, // 77
    { "access-control-allow-methods","options"                                }, // 78
    { "access-control-expose-headers","content-length"                        }, // 79
    { "access-control-request-headers","content-type"                         }, // 80
    { "access-control-request-method","get"                                   }, // 81
    { "access-control-request-method","post"                                  }, // 82
    { "alt-svc",                     "clear"                                  }, // 83
    { "authorization",               ""                                       }, // 84
    { "content-security-policy",     "script-src 'none'; object-src 'none'; base-uri 'none'" }, // 85
    { "early-data",                  "1"                                      }, // 86
    { "expect-ct",                   ""                                       }, // 87
    { "forwarded",                   ""                                       }, // 88
    { "if-range",                    ""                                       }, // 89
    { "origin",                      ""                                       }, // 90
    { "purpose",                     "prefetch"                               }, // 91
    { "server",                      ""                                       }, // 92
    { "timing-allow-origin",         "*"                                      }, // 93
    { "upgrade-insecure-requests",   "1"                                      }, // 94
    { "user-agent",                  ""                                       }, // 95
    { "x-forwarded-for",            ""                                        }, // 96
    { "x-frame-options",            "deny"                                    }, // 97
    { "x-frame-options",            "sameorigin"                              }, // 98
};
const size_t Decoder::STATIC_TABLE_SIZE = 99;

std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) {
    std::vector<HeaderField> headers;
    if (len < 2) return headers;

    // QPACK section prefix: Required Insert Count (8-bit prefix) + S + Delta Base (7-bit prefix)
    size_t off = 0;
    size_t bytes_read = 0;

    // Required Insert Count
    hpack::Decoder::decodeInt(data + off, len - off, 8, bytes_read);
    off += bytes_read;
    if (off >= len) return headers;

    // S bit + Delta Base
    hpack::Decoder::decodeInt(data + off, len - off, 7, bytes_read);
    off += bytes_read;

    // Decode field lines
    while (off < len) {
        uint8_t byte = data[off];

        if (byte & 0x80) {
            // Indexed Field Line: 1 T index(6)
            bool is_static = (byte & 0x40) != 0;
            uint32_t idx = hpack::Decoder::decodeInt(data + off, len - off, 6, bytes_read);
            off += bytes_read;
            if (is_static && idx < STATIC_TABLE_SIZE) {
                headers.push_back({STATIC_TABLE[idx].name, STATIC_TABLE[idx].value});
            }
        } else if ((byte & 0xC0) == 0x40) {
            // Literal with Name Reference: 01 N T index(4)
            bool is_static = (byte & 0x10) != 0;
            uint32_t idx = hpack::Decoder::decodeInt(data + off, len - off, 4, bytes_read);
            off += bytes_read;
            std::string name;
            if (is_static && idx < STATIC_TABLE_SIZE) {
                name = STATIC_TABLE[idx].name;
            }
            // Read value string
            if (off < len) {
                std::string value = hpack::Decoder::decodeString(data + off, len - off, bytes_read);
                off += bytes_read;
                if (!name.empty()) {
                    headers.push_back({name, value});
                }
            }
        } else if ((byte & 0xE0) == 0x20) {
            // Literal with Literal Name: 001 N H namelen(3) name H valuelen(7) value
            // Skip N bit, read name string
            bool name_huffman = (byte & 0x08) != 0;
            uint32_t name_len = hpack::Decoder::decodeInt(data + off, len - off, 3, bytes_read);
            off += bytes_read;
            std::string name;
            if (off + name_len <= len) {
                if (name_huffman) {
                    name = hpack::Decoder::huffmanDecode(data + off, name_len);
                } else {
                    name.assign(reinterpret_cast<const char*>(data + off), name_len);
                }
                off += name_len;
            }
            // Read value string
            if (off < len) {
                std::string value = hpack::Decoder::decodeString(data + off, len - off, bytes_read);
                off += bytes_read;
                if (!name.empty()) {
                    headers.push_back({name, value});
                }
            }
        } else if ((byte & 0xF0) == 0x10) {
            // Indexed Field Line with Post-Base Index: 0001 index(4)
            hpack::Decoder::decodeInt(data + off, len - off, 4, bytes_read);
            off += bytes_read;
            // Dynamic table not supported, skip
        } else if ((byte & 0xF0) == 0x00) {
            // Literal with Post-Base Name Reference: 0000 N index(3)
            hpack::Decoder::decodeInt(data + off, len - off, 3, bytes_read);
            off += bytes_read;
            // Skip value
            if (off < len) {
                hpack::Decoder::decodeString(data + off, len - off, bytes_read);
                off += bytes_read;
            }
        } else {
            ++off; // Skip unknown
        }
    }
    return headers;
}

// QPACK prefix integer encoding (RFC 9204 §4.1.1 / RFC 7541 §5.1)
void Encoder::encodeInt(std::vector<uint8_t> &out, uint8_t prefix_byte,
                        uint8_t prefix_bits, uint64_t value) {
    uint64_t max_val = (1ULL << prefix_bits) - 1;
    if (value < max_val) {
        out.push_back(prefix_byte | static_cast<uint8_t>(value));
    } else {
        out.push_back(prefix_byte | static_cast<uint8_t>(max_val));
        value -= max_val;
        while (value >= 128) {
            out.push_back(static_cast<uint8_t>(0x80 | (value & 0x7F)));
            value >>= 7;
        }
        out.push_back(static_cast<uint8_t>(value));
    }
}

// Encode a QPACK string literal (RFC 9204 §4.1.2), choosing whichever of raw
// vs. Huffman-encoded is shorter -- same table RFC 7541 Appendix B defines,
// which RFC 9204 reuses verbatim, so this shares hpack::Decoder's encoder
// rather than duplicating the bit-packing logic.
void Encoder::encodeString(std::vector<uint8_t> &out, const std::string &s) {
    std::string huff = hpack::Decoder::huffmanEncode(s);
    if (huff.size() < s.size()) {
        encodeInt(out, 0x80, 7, huff.size());
        out.insert(out.end(), huff.begin(), huff.end());
    } else {
        encodeInt(out, 0x00, 7, s.size());
        out.insert(out.end(), s.begin(), s.end());
    }
}

// Indexed Field Line referencing static table (RFC 9204 §4.5.2)
//   | 1 | T=1 |    Index (6+)    |
void Encoder::encodeIndexedStatic(std::vector<uint8_t> &out, uint64_t index) {
    encodeInt(out, 0xC0, 6, index);
}

// Literal Field Line With Name Reference to static table (RFC 9204 §4.5.4)
//   | 0 | 1 | N=0 | T=1 | Name Index (4+) |
//   | H |       Value Length (7+)          |
//   |        Value String                 |
void Encoder::encodeLiteralStaticNameRef(std::vector<uint8_t> &out,
                                         uint64_t name_index,
                                         const std::string &value) {
    encodeInt(out, 0x50, 4, name_index);
    encodeString(out, value);
}

// Literal Field Line With Literal Name (RFC 9204 §4.5.6)
//   | 0 | 0 | 1 | N=0 | H=0 | NameLen (3+) |
//   |          Name String                   |
//   | H |       Value Length (7+)            |
//   |          Value String                  |
void Encoder::encodeLiteralName(std::vector<uint8_t> &out,
                                const std::string &name,
                                const std::string &value) {
    encodeInt(out, 0x20, 3, name.size());
    out.insert(out.end(), name.begin(), name.end());
    encodeString(out, value);
}

int Encoder::findStaticNameIndex(const std::string &name) {
    for (size_t i = 0; i < Decoder::STATIC_TABLE_SIZE; ++i) {
        if (name == Decoder::STATIC_TABLE[i].name) return static_cast<int>(i);
    }
    return -1;
}

// Emits a name-referenced literal when the name is in the static table (RFC
// 9204 §4.5.4), otherwise falls back to a fully literal name+value (§4.5.6).
void Encoder::encodeExtraHeader(std::vector<uint8_t> &out, const std::string &name,
                                const std::string &value) {
    int idx = findStaticNameIndex(name);
    if (idx >= 0) {
        encodeLiteralStaticNameRef(out, static_cast<uint64_t>(idx), value);
    } else {
        encodeLiteralName(out, name, value);
    }
}

std::vector<uint8_t> Encoder::encodeResponseHeaders(uint16_t status_code,
                                                    const std::string &content_type,
                                                    size_t content_length,
                                                    const std::vector<HeaderField> &extra) {
    std::vector<uint8_t> out;
    out.reserve(64 + extra.size() * 48); // rough per-field estimate, avoids reallocation churn

    // Section prefix: Required Insert Count = 0, S=0, Delta Base = 0
    out.push_back(0x00);
    out.push_back(0x00);

    // :status — use indexed static table entries where possible
    // RFC 9204 static table indices:
    //   24=103, 25=200, 26=304, 27=404, 28=503,
    //   63=100, 64=204, 65=206, 66=302, 67=400,
    //   68=403, 69=421, 70=425, 71=500
    switch (status_code) {
    case 103: encodeIndexedStatic(out, 24); break;
    case 200: encodeIndexedStatic(out, 25); break;
    case 304: encodeIndexedStatic(out, 26); break;
    case 404: encodeIndexedStatic(out, 27); break;
    case 503: encodeIndexedStatic(out, 28); break;
    case 100: encodeIndexedStatic(out, 63); break;
    case 204: encodeIndexedStatic(out, 64); break;
    case 206: encodeIndexedStatic(out, 65); break;
    case 302: encodeIndexedStatic(out, 66); break;
    case 400: encodeIndexedStatic(out, 67); break;
    case 403: encodeIndexedStatic(out, 68); break;
    case 421: encodeIndexedStatic(out, 69); break;
    case 425: encodeIndexedStatic(out, 70); break;
    case 500: encodeIndexedStatic(out, 71); break;
    default: {
        // Literal with static name reference; :status name at index 24
        std::string status = std::to_string(status_code);
        encodeLiteralStaticNameRef(out, 24, status);
        break;
    }
    }

    // content-type — name at index 44 in static table
    if (!content_type.empty()) {
        encodeLiteralStaticNameRef(out, 44, content_type);
    }

    // content-length — name at index 4 in static table
    if (content_length > 0) {
        std::string len = std::to_string(content_length);
        encodeLiteralStaticNameRef(out, 4, len);
    }

    for (const auto &h : extra) {
        if (h.name.empty()) {
            continue;
        }
        encodeExtraHeader(out, h.name, h.value);
    }

    return out;
}

std::vector<uint8_t> Encoder::encodeRequestHeaders(const std::string &method,
                                                   const std::string &scheme,
                                                   const std::string &authority,
                                                   const std::string &path,
                                                   const std::vector<HeaderField> &extra,
                                                   bool include_content_length,
                                                   size_t content_length) {
    std::vector<uint8_t> out;
    out.reserve(64 + extra.size() * 48); // rough per-field estimate, avoids reallocation churn

    // Section prefix: Required Insert Count = 0, S=0, Delta Base = 0
    out.push_back(0x00);
    out.push_back(0x00);

    // :method
    if (method == "GET")      encodeIndexedStatic(out, 17);
    else if (method == "POST") encodeIndexedStatic(out, 20);
    else if (method == "PUT")  encodeIndexedStatic(out, 21);
    else if (method == "DELETE") encodeIndexedStatic(out, 16);
    else {
        // :method name reference at index 15
        encodeLiteralStaticNameRef(out, 15, method);
    }

    // :scheme
    if (scheme == "https") encodeIndexedStatic(out, 23);
    else encodeIndexedStatic(out, 22);

    // :path (name index 1)
    if (path == "/") {
        encodeIndexedStatic(out, 1);
    } else {
        encodeLiteralStaticNameRef(out, 1, path);
    }

    // :authority (name index 0)
    if (!authority.empty()) {
        encodeLiteralStaticNameRef(out, 0, authority);
    }

    if (include_content_length) {
        encodeLiteralStaticNameRef(out, 4, std::to_string(content_length));
    }

    for (const auto &h : extra) {
        if (h.name.empty()) {
            continue;
        }
        encodeExtraHeader(out, h.name, h.value);
    }

    return out;
}

} // namespace libhttppp::qpack
