/*
 * Copyright 2013-present Facebook, Inc.
 *
 * 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 com.facebook.buck.event;

import com.facebook.buck.log.CommandThreadFactory;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.timing.Clock;
import com.facebook.buck.util.concurrent.MoreExecutors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.eventbus.AsyncEventBus;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Thin wrapper around guava event bus.
 */
public class BuckEventBus implements Closeable {

  private static final Logger LOG = Logger.get(BuckEventBus.class);

  public static final int DEFAULT_SHUTDOWN_TIMEOUT_MS = 15000;

  private static final Supplier<Long> DEFAULT_THREAD_ID_SUPPLIER = new Supplier<Long>() {
    @Override
    public Long get() {
      return Thread.currentThread().getId();
    }
  };

  private final Clock clock;
  private final ExecutorService executorService;
  private final AsyncEventBus eventBus;
  private final Supplier<Long> threadIdSupplier;
  private final BuildId buildId;
  private final int shutdownTimeoutMillis;

  public BuckEventBus(Clock clock, BuildId buildId) {
    this(clock,
        MoreExecutors.newSingleThreadExecutor(
            new CommandThreadFactory(BuckEventBus.class.getSimpleName())),
        buildId,
        DEFAULT_SHUTDOWN_TIMEOUT_MS);
  }

  @VisibleForTesting
  BuckEventBus(
      Clock clock,
      ExecutorService executorService,
      BuildId buildId,
      int shutdownTimeoutMillis) {
    this.clock = clock;
    this.executorService = executorService;
    this.eventBus = new AsyncEventBus("buck-build-events", executorService);
    this.threadIdSupplier = DEFAULT_THREAD_ID_SUPPLIER;
    this.buildId = buildId;
    this.shutdownTimeoutMillis = shutdownTimeoutMillis;
  }

  public void post(BuckEvent event) {
    timestamp(event);
    eventBus.post(event);
  }

  public void logVerboseAndPost(Logger logger, BuckEvent event) {
    logger.verbose("%s", event);
    post(event);
  }

  public void logDebugAndPost(Logger logger, BuckEvent event) {
    logger.debug("%s", event);
    post(event);
  }

  /**
   * Post event to the EventBus using the timestamp given by atTime.
   */
  public void post(BuckEvent event, BuckEvent atTime) {
    event.configure(atTime.getTimestamp(), atTime.getNanoTime(), threadIdSupplier.get(), buildId);
    eventBus.post(event);
  }

  public void register(Object object) {
    eventBus.register(object);
  }

  public void unregister(Object object) {
    eventBus.unregister(object);
  }

  @VisibleForTesting
  AsyncEventBus getEventBus() {
    return eventBus;
  }

  @VisibleForTesting
  Clock getClock() {
    return clock;
  }

  @VisibleForTesting
  Supplier<Long> getThreadIdSupplier() {
    return threadIdSupplier;
  }

  /**
   * An id that every event posted to this event bus will share. For long-running processes, like
   * the daemon, the build id makes it possible to distinguish when events come from different
   * invocations of Buck.
   * <p>
   * In practice, this should be a short string, because it may be sent over the wire frequently.
   */
  public BuildId getBuildId() {
    return buildId;
  }

  /**
   * {@link ExecutorService#awaitTermination(long, java.util.concurrent.TimeUnit)} is called
   * to wait for events which have been posted, but which have been queued by the
   * {@link AsyncEventBus}, to be delivered. This allows listeners to record or report as much
   * information as possible. This aids debugging when close is called during exception processing.
   */
  @Override
  public void close() throws IOException {
    executorService.shutdown();
    try {
      if (!executorService.awaitTermination(shutdownTimeoutMillis, TimeUnit.MILLISECONDS)) {
        LOG.warn(Joiner.on(System.lineSeparator()).join(
          "The BuckEventBus failed to shut down within the standard timeout.",
          "Your build might have succeeded, but some messages were probably lost.",
          "Here's some debugging information:",
          executorService.toString()));
        executorService.shutdownNow();
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }

  /**
   * Timestamp event. A timestamped event cannot subsequently being posted and is useful only to
   * pass its timestamp on to another posted event.
   */
  public void timestamp(BuckEvent event) {
    event.configure(clock.currentTimeMillis(), clock.nanoTime(), threadIdSupplier.get(), buildId);
  }
}

@@ -1024,21 +1024,16 @@
 ventbus.
-Async
 EventBus
@@ -1034,16 +1034,16 @@
 entBus;%0A
+
 %0Aimport 
@@ -1683,21 +1683,16 @@
 e final 
-Async
 EventBus
@@ -2325,21 +2325,16 @@
 s = new 
-Async
 EventBus
@@ -2357,25 +2357,8 @@
 nts%22
-, executorService
 );%0A 
@@ -2497,24 +2497,239 @@
 illis;%0A  %7D%0A%0A
+  private void dispatch(final BuckEvent event) %7B%0A    executorService.submit(%0A        new Runnable() %7B%0A          @Override%0A          public void run() %7B%0A            eventBus.post(event);%0A          %7D%0A        %7D);%0A  %7D%0A%0A
   public voi
@@ -2776,37 +2776,32 @@
 event);%0A    
-eventBus.post
+dispatch
 (event);%0A  %7D
@@ -3276,37 +3276,32 @@
 ildId);%0A    
-eventBus.post
+dispatch
 (event);%0A  %7D
@@ -3481,21 +3481,16 @@
 sting%0A  
-Async
 EventBus
@@ -4269,16 +4269,16 @@
  by the%0A
+
    * %7B@l
@@ -4285,13 +4285,8 @@
 ink 
-Async
 Even

Use `EventBus` instead of `AsyncEventBus`