/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * vim: ts=4 sw=4 expandtab:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.geckoview;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import org.mozilla.gecko.annotation.WrapForJNI;

/** This is an abstract base class for HTTP request and response types. */
@WrapForJNI
@AnyThread
public abstract class WebMessage {

  /** The URI for the request or response. */
  public final @NonNull String uri;

  /** An unmodifiable Map of headers. Defaults to an empty instance. */
  public final @NonNull Map<String, String> headers;

  /**
   * Construct a new WebMessage from the given builder.
   *
   * @param builder Builder instance containing the message parameters
   */
  protected WebMessage(final @NonNull Builder builder) {
    uri = builder.mUri;
    headers = Collections.unmodifiableMap(builder.mHeaders);
  }

  // This is only used via JNI.
  private String[] getHeaderKeys() {
    final String[] keys = new String[headers.size()];
    headers.keySet().toArray(keys);
    return keys;
  }

  // This is only used via JNI.
  private String[] getHeaderValues() {
    final String[] values = new String[headers.size()];
    headers.values().toArray(values);
    return values;
  }

  /** This is a Builder used by subclasses of {@link WebMessage}. */
  @AnyThread
  public abstract static class Builder {
    /* package */ String mUri;
    /* package */ Map<String, String> mHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    /* package */ ByteBuffer mBody;

    /**
     * Construct a Builder instance with the specified URI.
     *
     * @param uri A URI String.
     */
    /* package */ Builder(final @NonNull String uri) {
      uri(uri);
    }

    /**
     * Set the URI
     *
     * @param uri A URI String
     * @return This Builder instance.
     */
    public @NonNull Builder uri(final @NonNull String uri) {
      mUri = uri;
      return this;
    }

    /**
     * Set a HTTP header. This may be called multiple times for additional headers. If an existing
     * header of the same name exists, it will be replaced by this value.
     *
     * <p>Please note that the HTTP header keys are case-insensitive. It means you can retrieve
     * "Content-Type" with map.get("content-type"), and value for "Content-Type" will be overwritten
     * by map.put("cONTENt-TYpe", value); The keys are also sorted in natural order.
     *
     * @param key The key for the HTTP header, e.g. "content-type".
     * @param value The value for the HTTP header, e.g. "application/json".
     * @return This Builder instance.
     */
    public @NonNull Builder header(final @NonNull String key, final @NonNull String value) {
      mHeaders.put(key, value);
      return this;
    }

    /**
     * Add a HTTP header. This may be called multiple times for additional headers. If an existing
     * header of the same name exists, the values will be merged.
     *
     * <p>Please note that the HTTP header keys are case-insensitive. It means you can retrieve
     * "Content-Type" with map.get("content-type"), and value for "Content-Type" will be overwritten
     * by map.put("cONTENt-TYpe", value); The keys are also sorted in natural order.
     *
     * @param key The key for the HTTP header, e.g. "content-type".
     * @param value The value for the HTTP header, e.g. "application/json".
     * @return This Builder instance.
     */
    public @NonNull Builder addHeader(final @NonNull String key, final @NonNull String value) {
      final String existingValue = mHeaders.get(key);
      if (existingValue != null) {
        final StringBuilder builder = new StringBuilder(existingValue);
        builder.append(", ");
        builder.append(value);
        mHeaders.put(key, builder.toString());
      } else {
        mHeaders.put(key, value);
      }

      return this;
    }
  }
}
