/*
 * Copyright (c) 2011-2016 The original author or authors
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *      The Eclipse Public License is available at
 *      http://www.eclipse.org/legal/epl-v10.html
 *
 *      The Apache License v2.0 is available at
 *      http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.circuitbreaker;

import io.vertx.circuitbreaker.impl.CircuitBreakerImpl;
import io.vertx.codegen.annotations.CacheReturn;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;

import java.util.function.Function;

/**
 * An implementation of the circuit breaker pattern for Vert.x
 *
 * @author <a href="http://escoffier.me">Clement Escoffier</a>
 */
@VertxGen
public interface CircuitBreaker {

  /**
   * Creates a new instance of {@link CircuitBreaker}.
   *
   * @param name    the name
   * @param vertx   the Vert.x instance
   * @param options the configuration option
   * @return the created instance
   */
  static CircuitBreaker create(String name, Vertx vertx, CircuitBreakerOptions options) {
    return new CircuitBreakerImpl(name, vertx, options == null ? new CircuitBreakerOptions() : options);
  }

  /**
   * Creates a new instance of {@link CircuitBreaker}, with default options.
   *
   * @param name  the name
   * @param vertx the Vert.x instance
   * @return the created instance
   */
  static CircuitBreaker create(String name, Vertx vertx) {
    return new CircuitBreakerImpl(name, vertx, new CircuitBreakerOptions());
  }

  /**
   * Closes the circuit breaker. It stops sending events on its state on the event bus.
   * This method is not related to the {@code close} state of the circuit breaker. To set the circuit breaker in the
   * {@code close} state, use {@link #reset()}.
   */
  @Fluent
  CircuitBreaker close();

  /**
   * Sets a {@link Handler} invoked when the circuit breaker state switches to open.
   *
   * @param handler the handler, must not be {@code null}
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  CircuitBreaker openHandler(Handler<Void> handler);

  /**
   * Sets a {@link Handler} invoked when the circuit breaker state switches to half-open.
   *
   * @param handler the handler, must not be {@code null}
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  CircuitBreaker halfOpenHandler(Handler<Void> handler);

  /**
   * Sets a {@link Handler} invoked when the circuit breaker state switches to close.
   *
   * @param handler the handler, must not be {@code null}
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  CircuitBreaker closeHandler(Handler<Void> handler);

  /**
   * Executes the given operation with the circuit breaker control. The operation is generally calling an
   * <em>external</em> system. The operation receives a {@link Promise} object as parameter and <strong>must</strong>
   * call {@link Promise#complete(Object)} when the operation has terminated successfully. The operation must also
   * call {@link Promise#fail(Throwable)} in case of failure.
   * <p>
   * The operation is not invoked if the circuit breaker is open, and the given fallback is called immediately. The
   * circuit breaker also monitor the completion of the operation before a configure timeout. The operation is
   * considered as failed if it does not terminate in time.
   * <p>
   * This method returns a {@link Future} object to retrieve the status and result of the operation, with the status
   * being a success or a failure. If the fallback is called, the returned future is successfully completed with the
   * value returned from the fallback. If the fallback throws an exception, the returned future is marked as failed.
   *
   * @param command  the operation
   * @param fallback the fallback function. It gets an exception as parameter and returns the <em>fallback</em> result
   * @param <T>      the type of result
   * @return a future object completed when the operation or its fallback completes
   */
  <T> Future<T> executeWithFallback(Handler<Promise<T>> command, Function<Throwable, T> fallback);

  /**
   * Same as {@link #executeWithFallback(Handler, Function)} but using a callback.
   *
   * @param command  the operation
   * @param fallback the fallback
   * @param handler  the completion handler receiving either the operation result or the fallback result. The
   *                 parameter is an {@link AsyncResult} because if the fallback is not called, the error is passed
   *                 to the handler.
   * @param <T>      the type of result
   */
  default <T> void executeWithFallback(Handler<Promise<T>> command, Function<Throwable, T> fallback,
                                              Handler<AsyncResult<T>> handler) {
    Future<T> fut = executeWithFallback(command, fallback);
    fut.onComplete(handler);
  }

  /**
   * Same as {@link #executeWithFallback(Handler, Function)} but using the circuit breaker default fallback.
   *
   * @param command the operation
   * @param <T>     the type of result
   * @return a future object completed when the operation or its fallback completes
   */
  <T> Future<T> execute(Handler<Promise<T>> command);

  /**
   * Same as {@link #executeWithFallback(Handler, Function)} but using the circuit breaker default fallback.
   *
   * @param command the operation
   * @param handler  the completion handler receiving either the operation result or the fallback result. The
   *                 parameter is an {@link AsyncResult} because if the fallback is not called, the error is passed
   *                 to the handler.
   * @param <T>     the type of result
   */
  default <T> void execute(Handler<Promise<T>> command, Handler<AsyncResult<T>> handler) {
    Future<T> fut = execute(command);
    fut.onComplete(handler);
  }

  /**
   * Same as {@link #executeAndReportWithFallback(Promise, Handler, Function)} but using the circuit breaker default
   * fallback.
   *
   * @param resultPromise the promise on which the operation result is reported
   * @param command      the operation
   * @param <T>          the type of result
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  <T> CircuitBreaker executeAndReport(Promise<T> resultPromise, Handler<Promise<T>> command);

  /**
   * Executes the given operation with the circuit breaker control. The operation is generally calling an
   * <em>external</em> system. The operation receives a {@link Promise} object as parameter and <strong>must</strong>
   * call {@link Promise#complete(Object)} when the operation has terminated successfully. The operation must also
   * call {@link Promise#fail(Throwable)} in case of failure.
   * <p>
   * The operation is not invoked if the circuit breaker is open, and the given fallback is called immediately. The
   * circuit breaker also monitor the completion of the operation before a configure timeout. The operation is
   * considered as failed if it does not terminate in time.
   * <p>
   * Unlike {@link #executeWithFallback(Handler, Function)},  this method does return a {@link Future} object, but
   * let the caller pass a {@link Future} object on which the result is reported. If the fallback is called, the future
   * is successfully completed with the value returned by the fallback function. If the fallback throws an exception,
   * the future is marked as failed.
   *
   * @param resultPromise the promise on which the operation result is reported
   * @param command      the operation
   * @param fallback     the fallback function. It gets an exception as parameter and returns the <em>fallback</em> result
   * @param <T>          the type of result
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  <T> CircuitBreaker executeAndReportWithFallback(Promise<T> resultPromise, Handler<Promise<T>> command,
                                                  Function<Throwable, T> fallback);

  /**
   * Sets a <em>default</em> {@link Function} invoked when the bridge is open to handle the "request", or on failure
   * if {@link CircuitBreakerOptions#isFallbackOnFailure()} is enabled.
   * <p>
   * The function gets the exception as parameter and returns the <em>fallback</em> result.
   *
   * @param handler the handler
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  <T> CircuitBreaker fallback(Function<Throwable, T> handler);

  /**
   * Resets the circuit breaker state (number of failure set to 0 and state set to closed).
   *
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  CircuitBreaker reset();

  /**
   * Explicitly opens the circuit.
   *
   * @return the current {@link CircuitBreaker}
   */
  @Fluent
  CircuitBreaker open();

  /**
   * @return the current state.
   */
  CircuitBreakerState state();

  /**
   * @return the current number of failures.
   */
  long failureCount();

  /**
   * @return the name of the circuit breaker.
   */
  @CacheReturn
  String name();

  @Fluent
  CircuitBreaker retryPolicy(Function<Integer, Long> retryPolicy);
}