/*
 * Copyright 2014 The gRPC Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.grpc.okhttp;

import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
import static io.grpc.internal.GrpcUtil.USER_AGENT_KEY;

import com.google.common.base.Preconditions;
import io.grpc.InternalMetadata;
import io.grpc.Metadata;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.TransportFrameUtil;
import io.grpc.okhttp.internal.framed.Header;
import java.util.ArrayList;
import java.util.List;
import okio.ByteString;

/**
 * Constants for request/response headers.
 */
class Headers {

  public static final Header HTTPS_SCHEME_HEADER = new Header(Header.TARGET_SCHEME, "https");
  public static final Header HTTP_SCHEME_HEADER = new Header(Header.TARGET_SCHEME, "http");
  public static final Header METHOD_HEADER = new Header(Header.TARGET_METHOD, GrpcUtil.HTTP_METHOD);
  public static final Header METHOD_GET_HEADER = new Header(Header.TARGET_METHOD, "GET");
  public static final Header CONTENT_TYPE_HEADER =
      new Header(CONTENT_TYPE_KEY.name(), GrpcUtil.CONTENT_TYPE_GRPC);
  public static final Header TE_HEADER = new Header("te", GrpcUtil.TE_TRAILERS);

  /**
   * Serializes the given headers and creates a list of OkHttp {@link Header}s to be used when
   * creating a stream. Since this serializes the headers, this method should be called in the
   * application thread context.
   */
  public static List<Header> createRequestHeaders(
      Metadata headers,
      String defaultPath,
      String authority,
      String userAgent,
      boolean useGet,
      boolean usePlaintext) {
    Preconditions.checkNotNull(headers, "headers");
    Preconditions.checkNotNull(defaultPath, "defaultPath");
    Preconditions.checkNotNull(authority, "authority");

    // Discard any application supplied duplicates of the reserved headers
    headers.discardAll(GrpcUtil.CONTENT_TYPE_KEY);
    headers.discardAll(GrpcUtil.TE_HEADER);
    headers.discardAll(GrpcUtil.USER_AGENT_KEY);

    // 7 is the number of explicit add calls below.
    List<Header> okhttpHeaders = new ArrayList<>(7 + InternalMetadata.headerCount(headers));

    // Set GRPC-specific headers.
    if (usePlaintext) {
      okhttpHeaders.add(HTTP_SCHEME_HEADER);
    } else {
      okhttpHeaders.add(HTTPS_SCHEME_HEADER);
    }
    if (useGet) {
      okhttpHeaders.add(METHOD_GET_HEADER);
    } else {
      okhttpHeaders.add(METHOD_HEADER);
    }

    okhttpHeaders.add(new Header(Header.TARGET_AUTHORITY, authority));
    String path = defaultPath;
    okhttpHeaders.add(new Header(Header.TARGET_PATH, path));

    okhttpHeaders.add(new Header(GrpcUtil.USER_AGENT_KEY.name(), userAgent));

    // All non-pseudo headers must come after pseudo headers.
    okhttpHeaders.add(CONTENT_TYPE_HEADER);
    okhttpHeaders.add(TE_HEADER);

    // Now add any application-provided headers.
    byte[][] serializedHeaders = TransportFrameUtil.toHttp2Headers(headers);
    for (int i = 0; i < serializedHeaders.length; i += 2) {
      ByteString key = ByteString.of(serializedHeaders[i]);
      String keyString = key.utf8();
      if (isApplicationHeader(keyString)) {
        ByteString value = ByteString.of(serializedHeaders[i + 1]);
        okhttpHeaders.add(new Header(key, value));
      }
    }

    return okhttpHeaders;
  }

  /**
   * Returns {@code true} if the given header is an application-provided header. Otherwise, returns
   * {@code false} if the header is reserved by GRPC.
   */
  private static boolean isApplicationHeader(String key) {
    // Don't allow HTTP/2 pseudo headers or content-type to be added by the application.
    return (!key.startsWith(":")
            && !CONTENT_TYPE_KEY.name().equalsIgnoreCase(key))
            && !USER_AGENT_KEY.name().equalsIgnoreCase(key);
  }
}
