/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.proxy;

import alluxio.ClientContext;
import alluxio.Constants;
import alluxio.conf.Configuration;
import alluxio.conf.PropertyKey;
import alluxio.grpc.NetAddress;
import alluxio.heartbeat.FixedIntervalSupplier;
import alluxio.heartbeat.HeartbeatContext;
import alluxio.heartbeat.HeartbeatThread;
import alluxio.master.MasterClientContext;
import alluxio.util.CommonUtils;
import alluxio.util.ThreadFactoryUtils;
import alluxio.util.WaitForOptions;
import alluxio.util.network.NetworkAddressUtils;
import alluxio.util.network.NetworkAddressUtils.ServiceType;
import alluxio.web.ProxyWebServer;
import alluxio.web.WebServer;
import alluxio.wire.Address;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * This class encapsulates the different worker services that are configured to run.
 */
@NotThreadSafe
public final class AlluxioProxyProcess implements ProxyProcess {
  private static final Logger LOG = LoggerFactory.getLogger(AlluxioProxyProcess.class);

  /** The web server. */
  private WebServer mWebServer = null;

  /** Worker start time in milliseconds. */
  private final long mStartTimeMs;

  private final CountDownLatch mLatch;
  private ProxyMasterSync mMasterSync;

  private ExecutorService mPool = Executors.newFixedThreadPool(1,
      ThreadFactoryUtils.build("proxy-routine-%d", true));

  /**
   * Creates an instance of {@link AlluxioProxy}.
   */
  AlluxioProxyProcess() {
    mStartTimeMs = System.currentTimeMillis();
    mLatch = new CountDownLatch(1);
  }

  @Override
  public int getWebLocalPort() {
    return mWebServer.getLocalPort();
  }

  @Override
  public long getStartTimeMs() {
    return mStartTimeMs;
  }

  @Override
  public long getUptimeMs() {
    return System.currentTimeMillis() - mStartTimeMs;
  }

  @Override
  public void start() throws Exception {
    mWebServer = new ProxyWebServer(ServiceType.PROXY_WEB.getServiceName(),
        NetworkAddressUtils.getBindAddress(ServiceType.PROXY_WEB, Configuration.global()),
        this);
    // reset proxy web port
    Configuration.set(PropertyKey.PROXY_WEB_PORT,
        mWebServer.getLocalPort());
    NetAddress proxyAddress = NetAddress.newBuilder()
        .setHost(NetworkAddressUtils.getConnectHost(ServiceType.PROXY_WEB,
                Configuration.global()))
        .setRpcPort(mWebServer.getLocalPort()).build();
    mWebServer.start();
    MasterClientContext context = MasterClientContext.newBuilder(ClientContext.create()).build();
    mMasterSync = new ProxyMasterSync(
        Address.fromProto(proxyAddress), context, mStartTimeMs);
    mPool.submit(new HeartbeatThread(HeartbeatContext.PROXY_META_MASTER_SYNC, mMasterSync,
        () -> new FixedIntervalSupplier(
            Configuration.getMs(PropertyKey.PROXY_MASTER_HEARTBEAT_INTERVAL)),
        Configuration.global(), context.getUserState()));

    mLatch.await();
  }

  @Override
  public void stop() throws Exception {
    if (mWebServer != null) {
      mWebServer.stop();
      mWebServer = null;
    }
    if (mMasterSync != null) {
      mMasterSync.close();
    }
    if (mPool != null) {
      mPool.shutdownNow();
      mPool = null;
    }
    mLatch.countDown();
  }

  @Override
  public boolean waitForReady(int timeoutMs) {
    try {
      CommonUtils.waitFor(this + " to start", () -> {
        if (mWebServer == null || !mWebServer.getServer().isRunning()) {
          return false;
        }
        HttpClient client = HttpClientBuilder.create().build();
        HttpPost method = new HttpPost(String
            .format("http://%s:%d%s/%s/%s/%s", mWebServer.getBindHost(), mWebServer.getLocalPort(),
                Constants.REST_API_PREFIX, PathsRestServiceHandler.SERVICE_PREFIX, "%2f",
                PathsRestServiceHandler.EXISTS));
        try {
          HttpResponse response = client.execute(method);
          if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            return true;
          }
          LOG.debug(IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
          return false;
        } catch (IOException e) {
          LOG.debug("Exception: ", e);
          return false;
        }
      }, WaitForOptions.defaults().setTimeoutMs(timeoutMs));
      return true;
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      return false;
    } catch (TimeoutException e) {
      return false;
    }
  }

  @Override
  public String toString() {
    return "Alluxio Proxy";
  }
}
