/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

package software.amazon.awssdk.crt.http;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * A wrapper class for http header key-value pairs
 */
public class HttpHeader {
    private final static int BUFFER_INT_SIZE = 4;
    private final static Charset UTF8 = StandardCharsets.UTF_8;
    private byte[] name;  /* Not final, Native will manually set name after calling empty Constructor. */
    private byte[] value; /* Not final, Native will manually set value after calling empty Constructor. */

    /** Called by Native to create a new HttpHeader. This is so that Native doesn't have to worry about UTF8
     * encoding/decoding issues. The user thread will deal with them when they call getName() or getValue() **/
    private HttpHeader() {}

    /**
     *
     * @param name header name
     * @param value header value
     */
    public HttpHeader(String name, String value){
        this.name = name.getBytes(UTF8);
        this.value = value.getBytes(UTF8);
    }

    /**
     *
     * @param name header name
     * @param value header value
     */
    public HttpHeader(byte[] name, byte[] value){
        this.name = name;
        this.value = value;
    }

    /**
     *
     * @return the name of the header, converted to a UTF-8 string
     */
    public String getName() {
        if (name == null) {
            return "";
        }
        return new String(name, UTF8);
    }

    /**
     *
     * @return the name of the header, in raw bytes
     */
    public byte[] getNameBytes() {
        return name;
    }

    /**
     *
     * @return the value of the header, converted to a UTF-8 string
     */
    public String getValue() {
        if (value == null) {
            return "";
        }
        return new String(value, UTF8);
    }

    /**
     *
     * @return the value of the header, in raw bytes
     */
    public byte[] getValueBytes() {
        return value;
    }

    @Override
    public String toString() {
        return getName() + ":" + getValue();
    }

    /** Each header is marshalled as
     * [4-bytes BE name length] [variable length name value] [4-bytes BE value length] [variable length value value]
     * @param headersBlob Blob of encoded headers
     * @return array of decoded headers
     */
    public static List<HttpHeader> loadHeadersListFromMarshalledHeadersBlob(ByteBuffer headersBlob) {
        List<HttpHeader> headers = new ArrayList<>(16);

        while(headersBlob.hasRemaining()) {
            int nameLen = headersBlob.getInt();

            // we want to protect against 0 length header names, 0 length values are fine.
            // the marshalling layer will make sure that even if a length is 0, the 0 will
            // still be stored in the byte array.
            if (nameLen > 0) {
                byte[] nameBuf = new byte[nameLen];
                headersBlob.get(nameBuf);
                int valLen = headersBlob.getInt();
                byte[] valueBuf = new byte[valLen];
                headersBlob.get(valueBuf);
                headers.add(new HttpHeader(nameBuf, valueBuf));
            }
        }

        return headers;
    }

    /**
     * Lists of headers are marshalled as follows:
     *
     * each string field is: [4-bytes BE] [variable length bytes specified by the
     * previous field]
     *
     * @param headers List of header name-value pairs
     *
     * @return encoded blob of headers
     */
    public static byte[] marshalHeadersForJni(List<HttpHeader> headers) {
        int size = 0;

        for (HttpHeader header : headers) {
            if (header.getNameBytes().length > 0) {
                size += header.getNameBytes().length + header.getValueBytes().length + (BUFFER_INT_SIZE * 2);
            }
        }

        ByteBuffer buffer = ByteBuffer.allocate(size);
        for (HttpHeader header : headers) {
            if (header.getNameBytes().length > 0) {
                buffer.putInt(header.getNameBytes().length);
                buffer.put(header.getNameBytes());
                buffer.putInt(header.getValueBytes().length);
                buffer.put(header.getValueBytes());
            }
        }

        return buffer.array();
    }


    /**
     * @param headersBlob encoded headers blob
     * @return array of headers
     * @see #loadHeadersListFromMarshalledHeadersBlob
     */
    public static HttpHeader[] loadHeadersFromMarshalledHeadersBlob(ByteBuffer headersBlob) {
        List<HttpHeader> headers = loadHeadersListFromMarshalledHeadersBlob(headersBlob);
        HttpHeader[] headersArray = new HttpHeader[headers.size()];
        return headers.toArray(headersArray);
    }
}

/**
     *
     * @param name header name
     * @param value header value
     */
/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

package software.amazon.awssdk.crt.http;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * A wrapper class for http header key-value pairs
 */
public class HttpHeader {
    private final static int BUFFER_INT_SIZE = 4;
    private final static Charset UTF8 = StandardCharsets.UTF_8;
    private byte[] name;  /* Not final, Native will manually set name after calling empty Constructor. */
    private byte[] value; /* Not final, Native will manually set value after calling empty Constructor. */

    /** Called by Native to create a new HttpHeader. This is so that Native doesn't have to worry about UTF8
     * encoding/decoding issues. The user thread will deal with them when they call getName() or getValue() **/
    private HttpHeader() {}

    /**
     *
     * @param name header name
     * @param value header value
     */
    public HttpHeader(String name, String value){
        this.name = name.getBytes(UTF8);
        this.value = value.getBytes(UTF8);
    }

    /**
     *
     * @param name header<SUF>*/
    public HttpHeader(byte[] name, byte[] value){
        this.name = name;
        this.value = value;
    }

    /**
     *
     * @return the name of the header, converted to a UTF-8 string
     */
    public String getName() {
        if (name == null) {
            return "";
        }
        return new String(name, UTF8);
    }

    /**
     *
     * @return the name of the header, in raw bytes
     */
    public byte[] getNameBytes() {
        return name;
    }

    /**
     *
     * @return the value of the header, converted to a UTF-8 string
     */
    public String getValue() {
        if (value == null) {
            return "";
        }
        return new String(value, UTF8);
    }

    /**
     *
     * @return the value of the header, in raw bytes
     */
    public byte[] getValueBytes() {
        return value;
    }

    @Override
    public String toString() {
        return getName() + ":" + getValue();
    }

    /** Each header is marshalled as
     * [4-bytes BE name length] [variable length name value] [4-bytes BE value length] [variable length value value]
     * @param headersBlob Blob of encoded headers
     * @return array of decoded headers
     */
    public static List<HttpHeader> loadHeadersListFromMarshalledHeadersBlob(ByteBuffer headersBlob) {
        List<HttpHeader> headers = new ArrayList<>(16);

        while(headersBlob.hasRemaining()) {
            int nameLen = headersBlob.getInt();

            // we want to protect against 0 length header names, 0 length values are fine.
            // the marshalling layer will make sure that even if a length is 0, the 0 will
            // still be stored in the byte array.
            if (nameLen > 0) {
                byte[] nameBuf = new byte[nameLen];
                headersBlob.get(nameBuf);
                int valLen = headersBlob.getInt();
                byte[] valueBuf = new byte[valLen];
                headersBlob.get(valueBuf);
                headers.add(new HttpHeader(nameBuf, valueBuf));
            }
        }

        return headers;
    }

    /**
     * Lists of headers are marshalled as follows:
     *
     * each string field is: [4-bytes BE] [variable length bytes specified by the
     * previous field]
     *
     * @param headers List of header name-value pairs
     *
     * @return encoded blob of headers
     */
    public static byte[] marshalHeadersForJni(List<HttpHeader> headers) {
        int size = 0;

        for (HttpHeader header : headers) {
            if (header.getNameBytes().length > 0) {
                size += header.getNameBytes().length + header.getValueBytes().length + (BUFFER_INT_SIZE * 2);
            }
        }

        ByteBuffer buffer = ByteBuffer.allocate(size);
        for (HttpHeader header : headers) {
            if (header.getNameBytes().length > 0) {
                buffer.putInt(header.getNameBytes().length);
                buffer.put(header.getNameBytes());
                buffer.putInt(header.getValueBytes().length);
                buffer.put(header.getValueBytes());
            }
        }

        return buffer.array();
    }


    /**
     * @param headersBlob encoded headers blob
     * @return array of headers
     * @see #loadHeadersListFromMarshalledHeadersBlob
     */
    public static HttpHeader[] loadHeadersFromMarshalledHeadersBlob(ByteBuffer headersBlob) {
        List<HttpHeader> headers = loadHeadersListFromMarshalledHeadersBlob(headersBlob);
        HttpHeader[] headersArray = new HttpHeader[headers.size()];
        return headers.toArray(headersArray);
    }
}
