// Copyright 2017 The Bazel 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 build.buildfarm.proxy.http;

import static java.util.concurrent.TimeUnit.SECONDS;

import build.buildfarm.common.LoggingMain;
import com.google.auth.Credentials;
import com.google.devtools.common.options.OptionsParser;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.util.TransmitStatusRuntimeExceptionInterceptor;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;

public class HttpProxy extends LoggingMain {
  // We need to keep references to the root and netty loggers to prevent them from being garbage
  // collected, which would cause us to loose their configuration.
  private static final Logger nettyLogger = Logger.getLogger("io.grpc.netty");
  public static final Logger logger = Logger.getLogger(HttpProxy.class.getName());

  private final HttpProxyOptions options;
  private final Server server;

  public HttpProxy(HttpProxyOptions options, @Nullable Credentials creds)
      throws URISyntaxException, SSLException {
    this(ServerBuilder.forPort(options.port), creds, options);
  }

  public HttpProxy(
      ServerBuilder<?> serverBuilder, @Nullable Credentials creds, HttpProxyOptions options)
      throws URISyntaxException, SSLException {
    super("HttpProxy");
    this.options = options;
    SimpleBlobStore simpleBlobStore =
        HttpBlobStore.create(
            URI.create(options.httpCache),
            /* remoteMaxConnections=*/ 0,
            (int) SECONDS.toMillis(options.timeout),
            creds);
    server =
        serverBuilder
            .addService(new ActionCacheService(simpleBlobStore))
            .addService(
                new ContentAddressableStorageService(
                    simpleBlobStore, options.treeDefaultPageSize, options.treeMaxPageSize))
            .addService(new ByteStreamService(simpleBlobStore))
            .intercept(TransmitStatusRuntimeExceptionInterceptor.instance())
            .build();
  }

  public void start() throws IOException {
    server.start();
  }

  @Override
  protected void onShutdown() {
    System.err.println("*** shutting down gRPC server since JVM is shutting down");
    stop();
    System.err.println("*** server shut down");
  }

  public void stop() {
    if (server != null) {
      server.shutdown();
    }
  }

  private void blockUntilShutdown() throws InterruptedException {
    if (server != null) {
      server.awaitTermination();
    }
  }

  private static void printUsage(OptionsParser parser) {
    System.out.println("Usage: [OPTIONS]");
    System.out.println(
        parser.describeOptions(Collections.emptyMap(), OptionsParser.HelpVerbosity.LONG));
  }

  public static void main(String[] args) throws Exception {
    // Only log severe log messages from Netty. Otherwise it logs warnings that look like this:
    //
    // 170714 08:16:28.552:WT 18 [io.grpc.netty.NettyServerHandler.onStreamError] Stream Error
    // io.netty.handler.codec.http2.Http2Exception$StreamException: Received DATA frame for an
    // unknown stream 11369
    nettyLogger.setLevel(Level.SEVERE);

    OptionsParser parser =
        OptionsParser.newOptionsParser(HttpProxyOptions.class, AuthAndTLSOptions.class);
    parser.parseAndExitUponError(args);
    List<String> residue = parser.getResidue();
    if (!residue.isEmpty()) {
      printUsage(parser);
      throw new IllegalArgumentException("Unrecognized arguments: " + residue);
    }
    HttpProxyOptions options = parser.getOptions(HttpProxyOptions.class);
    if (options.port < 0) {
      printUsage(parser);
      throw new IllegalArgumentException("invalid port: " + options.port);
    }
    AuthAndTLSOptions authAndTlsOptions = parser.getOptions(AuthAndTLSOptions.class);
    HttpProxy server = new HttpProxy(options, GoogleAuthUtils.newCredentials(authAndTlsOptions));
    server.start();
    server.blockUntilShutdown();
  }
}