/*
 * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.internal.net.http.qpack;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/*
 * A header table with most common header fields.
 * This table was generated by analyzing actual Internet traffic in 2018
 *  and is a part of "QPACK: Header Compression for HTTP/3" RFC.
 */
public final class StaticTable implements HeadersTable {

    /* An immutable list of static header fields */
    public static final List<HeaderField> HTTP3_HEADER_FIELDS = List.of(
            new HeaderField(":authority"),
            new HeaderField(":path", "/"),
            new HeaderField("age", "0"),
            new HeaderField("content-disposition"),
            new HeaderField("content-length", "0"),
            new HeaderField("cookie"),
            new HeaderField("date"),
            new HeaderField("etag"),
            new HeaderField("if-modified-since"),
            new HeaderField("if-none-match"),
            new HeaderField("last-modified"),
            new HeaderField("link"),
            new HeaderField("location"),
            new HeaderField("referer"),
            new HeaderField("set-cookie"),
            new HeaderField(":method", "CONNECT"),
            new HeaderField(":method", "DELETE"),
            new HeaderField(":method", "GET"),
            new HeaderField(":method", "HEAD"),
            new HeaderField(":method", "OPTIONS"),
            new HeaderField(":method", "POST"),
            new HeaderField(":method", "PUT"),
            new HeaderField(":scheme", "http"),
            new HeaderField(":scheme", "https"),
            new HeaderField(":status", "103"),
            new HeaderField(":status", "200"),
            new HeaderField(":status", "304"),
            new HeaderField(":status", "404"),
            new HeaderField(":status", "503"),
            new HeaderField("accept", "*/*"),
            new HeaderField("accept", "application/dns-message"),
            new HeaderField("accept-encoding", "gzip, deflate, br"),
            new HeaderField("accept-ranges", "bytes"),
            new HeaderField("access-control-allow-headers", "cache-control"),
            new HeaderField("access-control-allow-headers", "content-type"),
            new HeaderField("access-control-allow-origin", "*"),
            new HeaderField("cache-control", "max-age=0"),
            new HeaderField("cache-control", "max-age=2592000"),
            new HeaderField("cache-control", "max-age=604800"),
            new HeaderField("cache-control", "no-cache"),
            new HeaderField("cache-control", "no-store"),
            new HeaderField("cache-control", "public, max-age=31536000"),
            new HeaderField("content-encoding", "br"),
            new HeaderField("content-encoding", "gzip"),
            new HeaderField("content-type", "application/dns-message"),
            new HeaderField("content-type", "application/javascript"),
            new HeaderField("content-type", "application/json"),
            new HeaderField("content-type", "application/x-www-form-urlencoded"),
            new HeaderField("content-type", "image/gif"),
            new HeaderField("content-type", "image/jpeg"),
            new HeaderField("content-type", "image/png"),
            new HeaderField("content-type", "text/css"),
            new HeaderField("content-type", "text/html; charset=utf-8"),
            new HeaderField("content-type", "text/plain"),
            new HeaderField("content-type", "text/plain;charset=utf-8"),
            new HeaderField("range", "bytes=0-"),
            new HeaderField("strict-transport-security", "max-age=31536000"),
            new HeaderField("strict-transport-security", "max-age=31536000; includesubdomains"),
            new HeaderField("strict-transport-security", "max-age=31536000; includesubdomains; preload"),
            new HeaderField("vary", "accept-encoding"),
            new HeaderField("vary", "origin"),
            new HeaderField("x-content-type-options", "nosniff"),
            new HeaderField("x-xss-protection", "1; mode=block"),
            new HeaderField(":status", "100"),
            new HeaderField(":status", "204"),
            new HeaderField(":status", "206"),
            new HeaderField(":status", "302"),
            new HeaderField(":status", "400"),
            new HeaderField(":status", "403"),
            new HeaderField(":status", "421"),
            new HeaderField(":status", "425"),
            new HeaderField(":status", "500"),
            new HeaderField("accept-language"),
            new HeaderField("access-control-allow-credentials", "FALSE"),
            new HeaderField("access-control-allow-credentials", "TRUE"),
            new HeaderField("access-control-allow-headers", "*"),
            new HeaderField("access-control-allow-methods", "get"),
            new HeaderField("access-control-allow-methods", "get, post, options"),
            new HeaderField("access-control-allow-methods", "options"),
            new HeaderField("access-control-expose-headers", "content-length"),
            new HeaderField("access-control-request-headers", "content-type"),
            new HeaderField("access-control-request-method", "get"),
            new HeaderField("access-control-request-method", "post"),
            new HeaderField("alt-svc", "clear"),
            new HeaderField("authorization"),
            new HeaderField("content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"),
            new HeaderField("early-data", "1"),
            new HeaderField("expect-ct"),
            new HeaderField("forwarded"),
            new HeaderField("if-range"),
            new HeaderField("origin"),
            new HeaderField("purpose", "prefetch"),
            new HeaderField("server"),
            new HeaderField("timing-allow-origin", "*"),
            new HeaderField("upgrade-insecure-requests", "1"),
            new HeaderField("user-agent"),
            new HeaderField("x-forwarded-for"),
            new HeaderField("x-frame-options", "deny"),
            new HeaderField("x-frame-options", "sameorigin")
    );

    public static final StaticTable HTTP3 = new StaticTable(HTTP3_HEADER_FIELDS);

    private final List<HeaderField> headerFields;
    private final Map<String, Map<String, Integer>> indicesMap;

    private StaticTable(List<HeaderField> headerFields) {
        this.headerFields = headerFields;
        this.indicesMap = buildIndicesMap(headerFields);
    }

    @Override
    public HeaderField get(long index) {
        if (index >= headerFields.size()) {
            throw new IllegalArgumentException("Invalid static table entry index");
        }
        return headerFields.get((int)index);
    }

    @Override
    public long insert(String name, String value) {
        throw new UnsupportedOperationException("Operation not supported by static tables");
    }

    @Override
    public long search(String name, String value) {
        Map<String, Integer> values = indicesMap.get(name);
        // 0 return value if no match is found in the static table
        int searchResult = 0;
        if (values != null) {
            Integer idx = values.get(value);
            if (idx != null) {
                searchResult = idx + 1;
            } else {
                // Only name is found - return first id from indices for the name provided
                searchResult = -values.values().iterator().next() - 1;
            }
        }
        return searchResult;
    }

    private static Map<String, Map<String, Integer>> buildIndicesMap(List<HeaderField> fields) {
        int numEntries = fields.size();
        Map<String, Map<String, Integer>> map = new HashMap<>(numEntries);
        for (int i = 0; i < numEntries; i++) {
            HeaderField f = fields.get(i);
            Map<String, Integer> values = map.computeIfAbsent(f.name(), _ -> new HashMap<>());
            values.put(f.value(), i);
        }
        return map;
    }
}
