/*
 * Copyright (C) 2012 The Guava 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 com.google.common.util.concurrent.testing;

import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.AbstractListeningExecutorService;
import com.google.common.util.concurrent.ListenableScheduledFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Factory methods for {@link ExecutorService} for testing.
 *
 * @author Chris Nokleberg
 * @since 14.0
 */
@Beta
public final class TestingExecutors {
  private TestingExecutors() {}

  /**
   * Returns a {@link ScheduledExecutorService} that never executes anything.
   *
   * <p>The {@code shutdownNow} method of the returned executor always returns an empty list despite
   * the fact that everything is still technically awaiting execution.
   * The {@code getDelay} method of any {@link ScheduledFuture} returned by the executor will always
   * return the max long value instead of the time until the user-specified delay.
   */
  public static ListeningScheduledExecutorService noOpScheduledExecutor() {
    return new NoOpScheduledExecutorService();
  }

  /**
   * Creates a scheduled executor service that runs each task in the thread
   * that invokes {@code execute/submit/schedule}, as in
   * {@link CallerRunsPolicy}. This applies both to individually submitted
   * tasks and to collections of tasks submitted via {@code invokeAll},
   * {@code invokeAny}, {@code schedule}, {@code scheduleAtFixedRate}, and
   * {@code scheduleWithFixedDelay}.  In the case of tasks submitted by
   * {@code invokeAll} or {@code invokeAny}, tasks will run serially on the
   * calling thread.  Tasks are run to completion before a {@code Future} is
   * returned to the caller (unless the executor has been shutdown).
   *
   * <p>The returned executor is backed by the executor returned by
   * {@link MoreExecutors#sameThreadExecutor} and subject to the same
   * constraints.
   *
   * <p>Although all tasks are immediately executed in the thread that
   * submitted the task, this {@code ExecutorService} imposes a small
   * locking overhead on each task submission in order to implement shutdown
   * and termination behavior.
   *
   * <p>Because of the nature of single-thread execution, the methods
   * {@code scheduleAtFixedRate} and {@code scheduleWithFixedDelay} are not
   * supported by this class and will throw an UnsupportedOperationException.
   *
   * <p>The implementation deviates from the {@code ExecutorService}
   * specification with regards to the {@code shutdownNow} method.  First,
   * "best-effort" with regards to canceling running tasks is implemented
   * as "no-effort".  No interrupts or other attempts are made to stop
   * threads executing tasks.  Second, the returned list will always be empty,
   * as any submitted task is considered to have started execution.
   * This applies also to tasks given to {@code invokeAll} or {@code invokeAny}
   * which are pending serial execution, even the subset of the tasks that
   * have not yet started execution.  It is unclear from the
   * {@code ExecutorService} specification if these should be included, and
   * it's much easier to implement the interpretation that they not be.
   * Finally, a call to {@code shutdown} or {@code shutdownNow} may result
   * in concurrent calls to {@code invokeAll/invokeAny} throwing
   * RejectedExecutionException, although a subset of the tasks may already
   * have been executed.
   *
   * @since 15.0
   */
  public static SameThreadScheduledExecutorService sameThreadScheduledExecutor() {
    return new SameThreadScheduledExecutorService();
  }

  private static final class NoOpScheduledExecutorService
      extends AbstractListeningExecutorService implements ListeningScheduledExecutorService {

    private volatile boolean shutdown;

    @Override public void shutdown() {
      shutdown = true;
    }

    @Override public List<Runnable> shutdownNow() {
      shutdown();
      return ImmutableList.of();
    }

    @Override public boolean isShutdown() {
      return shutdown;
    }

    @Override public boolean isTerminated() {
      return shutdown;
    }

    @Override public boolean awaitTermination(long timeout, TimeUnit unit) {
      return true;
    }

    @Override public void execute(Runnable runnable) {}

    @Override public <V> ListenableScheduledFuture<V> schedule(
        Callable<V> callable, long delay, TimeUnit unit) {
      return NeverScheduledFuture.create();
    }

    @Override public ListenableScheduledFuture<?> schedule(
        Runnable command, long delay, TimeUnit unit) {
      return NeverScheduledFuture.create();
    }

    @Override public ListenableScheduledFuture<?> scheduleAtFixedRate(
        Runnable command, long initialDelay, long period, TimeUnit unit) {
      return NeverScheduledFuture.create();
    }

    @Override public ListenableScheduledFuture<?> scheduleWithFixedDelay(
        Runnable command, long initialDelay, long delay, TimeUnit unit) {
      return NeverScheduledFuture.create();
    }

    private static class NeverScheduledFuture<V>
        extends AbstractFuture<V> implements ListenableScheduledFuture<V> {

      static <V> NeverScheduledFuture<V> create() {
        return new NeverScheduledFuture<V>();
      }

      @Override public long getDelay(TimeUnit unit) {
        return Long.MAX_VALUE;
      }

      @Override public int compareTo(Delayed other) {
        return Longs.compare(getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS));
      }
    }
  }
}
