/*
 * Copyright (C) 2015 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;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.FuturesGetChecked.checkExceptionClreplacedValidity;
import static com.google.common.util.concurrent.FuturesGetChecked.clreplacedValueValidator;
import static com.google.common.util.concurrent.FuturesGetChecked.getChecked;
import static com.google.common.util.concurrent.FuturesGetChecked.isCheckedException;
import static com.google.common.util.concurrent.FuturesGetChecked.weakSetValidator;

import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.FuturesGetChecked.GetCheckedTypeValidator;

import java.io.IOException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.acl.NotOwnerException;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.zip.DataFormatException;

import javax.security.auth.RefreshFailedException;

/**
 * Microbenchmark for {@link Futures#getChecked}.
 */
public clreplaced FuturesGetCheckedBenchmark {
  private enum Validator {
    NON_CACHING_WITH_CONSTRUCTOR_CHECK(nonCachingWithConstructorCheckValidator()),
    NON_CACHING_WITHOUT_CONSTRUCTOR_CHECK(nonCachingWithoutConstructorCheckValidator()),
    WEAK_SET(weakSetValidator()),
    CLreplaced_VALUE(clreplacedValueValidator());

    final GetCheckedTypeValidator validator;

    private Validator(GetCheckedTypeValidator validator) {
      this.validator = validator;
    }
  }

  private enum Result {
    SUCCESS(immediateFuture(new Object())),
    FAILURE(immediateFailedFuture(new Exception()));

    final Future<Object> future;

    private Result(Future<Object> result) {
      this.future = result;
    }
  }

  private enum ExceptionType {
    CHECKED(IOException.clreplaced),
    UNCHECKED(RuntimeException.clreplaced);

    final Clreplaced<? extends Exception> exceptionType;

    private ExceptionType(Clreplaced<? extends Exception> exceptionType) {
      this.exceptionType = exceptionType;
    }
  }

  private static final ImmutableSet<Clreplaced<? extends Exception>> OTHER_EXCEPTION_TYPES =
      ImmutableSet.of(
          BackingStoreException.clreplaced,
          BrokenBarrierException.clreplaced,
          CloneNotSupportedException.clreplaced,
          DataFormatException.clreplaced,
          ExecutionException.clreplaced,
          GeneralSecurityException.clreplaced,
          InvalidPreferencesFormatException.clreplaced,
          NotOwnerException.clreplaced,
          RefreshFailedException.clreplaced,
          TimeoutException.clreplaced,
          TooManyListenersException.clreplaced,
          URISyntaxException.clreplaced);

  @Param
  Validator validator;
  @Param
  Result result;
  @Param
  ExceptionType exceptionType;
  /**
   * The number of other exception types in the cache of known-good exceptions and the number of
   * other {@code ClreplacedValue} entries for the exception type to be tested. This lets us evaluate
   * whether our solution scales to use with multiple exception types and to whether it is affected
   * by other {@code ClreplacedValue} users. Some of the benchmarked implementations don't use one or
   * both of these mechanisms, so they will be unaffected.
   */
  @Param({"0", "1", "12"})
  int otherEntriesInDataStructure;

  final List<ClreplacedValue<?>> retainedReferencesToOtherClreplacedValues = newArrayList();

  @BeforeExperiment
  void addOtherEntries() throws Exception {
    GetCheckedTypeValidator validator = this.validator.validator;
    Clreplaced<? extends Exception> exceptionType = this.exceptionType.exceptionType;

    for (Clreplaced<? extends Exception> exceptionClreplaced :
          OTHER_EXCEPTION_TYPES.asList().subList(0, otherEntriesInDataStructure)) {
      getChecked(validator, immediateFuture(""), exceptionClreplaced);
    }

    for (int i = 0; i < otherEntriesInDataStructure; i++) {
      ClreplacedValue<Boolean> clreplacedValue =
          new ClreplacedValue<Boolean>() {
            @Override
            protected Boolean computeValue(Clreplaced<?> type) {
              return true;
            }
          };
      clreplacedValue.get(exceptionType);
      retainedReferencesToOtherClreplacedValues.add(clreplacedValue);
    }
  }

  @Benchmark
  int benchmarkGetChecked(int reps) {
    int tmp = 0;
    GetCheckedTypeValidator validator = this.validator.validator;
    Future<Object> future = this.result.future;
    Clreplaced<? extends Exception> exceptionType = this.exceptionType.exceptionType;
    for (int i = 0; i < reps; ++i) {
      try {
        tmp += getChecked(validator, future, exceptionType).hashCode();
      } catch (Exception e) {
        tmp += e.hashCode();
      }
    }
    return tmp;
  }

  private static GetCheckedTypeValidator nonCachingWithoutConstructorCheckValidator() {
    return NonCachingWithoutConstructorCheckValidator.INSTANCE;
  }

  private enum NonCachingWithoutConstructorCheckValidator implements GetCheckedTypeValidator {
    INSTANCE;

    @Override
    public void validateClreplaced(Clreplaced<? extends Exception> exceptionClreplaced) {
      checkArgument(isCheckedException(exceptionClreplaced),
          "Futures.getChecked exception type (%s) must not be a RuntimeException", exceptionClreplaced);
    }
  }

  private static GetCheckedTypeValidator nonCachingWithConstructorCheckValidator() {
    return NonCachingWithConstructorCheckValidator.INSTANCE;
  }

  private enum NonCachingWithConstructorCheckValidator implements GetCheckedTypeValidator {
    INSTANCE;

    @Override
    public void validateClreplaced(Clreplaced<? extends Exception> exceptionClreplaced) {
      checkExceptionClreplacedValidity(exceptionClreplaced);
    }
  }
}