/*
 * Copyright 2016, gRPC Authors All rights reserved.
 *
 * 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.cronet;

import static com.google.common.base.Preconditions.checkArgument;
import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
import io.grpc.Attributes;
import io.grpc.ExperimentalApi;
import io.grpc.NameResolver;
import io.grpc.internal.AbstractManagedChannelImplBuilder;
import io.grpc.internal.ClientTransportFactory;
import io.grpc.internal.ConnectionClientTransport;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourceHolder;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.CronetEngine;
import org.chromium.net.ExperimentalCronetEngine;

/** Convenience class for building channels with the cronet transport. */
@ExperimentalApi("There is no plan to make this API stable, given transport API instability")
public final class CronetChannelBuilder extends
    AbstractManagedChannelImplBuilder<CronetChannelBuilder> {

  /** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */
  public static abstract class StreamBuilderFactory {
    public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder(
        String url, BidirectionalStream.Callback callback, Executor executor);
  }

  /** Creates a new builder for the given server host, port and CronetEngine. */
  public static CronetChannelBuilder forAddress(
      String host, int port, final CronetEngine cronetEngine) {
    Preconditions.checkNotNull(cronetEngine, "cronetEngine");
    return new CronetChannelBuilder(
        host,
        port,
        new StreamBuilderFactory() {
          @Override
          public BidirectionalStream.Builder newBidirectionalStreamBuilder(
              String url, BidirectionalStream.Callback callback, Executor executor) {
            return ((ExperimentalCronetEngine) cronetEngine)
                .newBidirectionalStreamBuilder(url, callback, executor);
          }
        });
  }

  /** Creates a new builder for the given server host, port and StreamBuilderFactory. */
  public static CronetChannelBuilder forAddress(
      String host, int port, StreamBuilderFactory streamFactory) {
    return new CronetChannelBuilder(host, port, streamFactory);
  }

  /**
   * Always fails.  Call {@link #forAddress(String, int, CronetEngine)} instead.
   */
  public static CronetChannelBuilder forTarget(String target) {
    throw new UnsupportedOperationException("call forAddress() instead");
  }

  /**
   * Always fails.  Call {@link #forAddress(String, int, CronetEngine)} instead.
   */
  public static CronetChannelBuilder forAddress(String name, int port) {
    throw new UnsupportedOperationException("call forAddress(String, int, CronetEngine) instead");
  }

  private boolean alwaysUsePut = false;

  private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;

  private StreamBuilderFactory streamFactory;

  private CronetChannelBuilder(String host, int port, StreamBuilderFactory streamFactory) {
    super(
        InetSocketAddress.createUnresolved(host, port),
        GrpcUtil.authorityFromHostAndPort(host, port));
    this.streamFactory = Preconditions.checkNotNull(streamFactory, "streamFactory");
  }

  /**
   * Sets the maximum message size allowed to be received on the channel. If not called,
   * defaults to {@link io.grpc.internal.GrpcUtil#DEFAULT_MAX_MESSAGE_SIZE}.
   */
  public final CronetChannelBuilder maxMessageSize(int maxMessageSize) {
    checkArgument(maxMessageSize >= 0, "maxMessageSize must be >= 0");
    this.maxMessageSize = maxMessageSize;
    return this;
  }

  /**
   * Sets the Cronet channel to always use PUT instead of POST. Defaults to false.
   */
  public final CronetChannelBuilder alwaysUsePut(boolean enable) {
    this.alwaysUsePut = enable;
    return this;
  }

  /**
   * Not supported for building cronet channel.
   */
  @Override
  public final CronetChannelBuilder usePlaintext(boolean skipNegotiation) {
    throw new IllegalArgumentException("Plaintext not currently supported");
  }

  @Override
  protected final ClientTransportFactory buildTransportFactory() {
    return new CronetTransportFactory(streamFactory, MoreExecutors.directExecutor(),
        maxMessageSize, alwaysUsePut);
  }

  @Override
  protected Attributes getNameResolverParams() {
    return Attributes.newBuilder()
        .set(NameResolver.Factory.PARAMS_DEFAULT_PORT, GrpcUtil.DEFAULT_PORT_SSL).build();
  }

  @VisibleForTesting
  static class CronetTransportFactory implements ClientTransportFactory {
    private final ScheduledExecutorService timeoutService =
        SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE);
    private final Executor executor;
    private final int maxMessageSize;
    private final boolean alwaysUsePut;
    private final StreamBuilderFactory streamFactory;

    private CronetTransportFactory(
        StreamBuilderFactory streamFactory,
        Executor executor,
        int maxMessageSize,
        boolean alwaysUsePut) {
      this.maxMessageSize = maxMessageSize;
      this.alwaysUsePut = alwaysUsePut;
      this.streamFactory = streamFactory;
      this.executor = Preconditions.checkNotNull(executor, "executor");
    }

    @Override
    public ConnectionClientTransport newClientTransport(SocketAddress addr, String authority,
        @Nullable String userAgent) {
      InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
      return new CronetClientTransport(streamFactory, inetSocketAddr, authority, userAgent,
          executor, maxMessageSize, alwaysUsePut);
    }

    @Override
    public ScheduledExecutorService getScheduledExecutorService() {
      return timeoutService;
    }

    @Override
    public void close() {
      SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeoutService);
    }
  }
}

@@ -1207,16 +1207,57 @@
 pcUtil;%0A
+import io.grpc.internal.ProxyParameters;%0A
 import i
@@ -6271,16 +6271,49 @@
 serAgent
+, @Nullable ProxyParameters proxy
 ) %7B%0A    

add ProxyParam arg to CronetTransportFactory (#3589)