/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.datastax.driver.core.policies;

import com.datastax.driver.core.AtomicMonotonicTimestampGenerator;
import com.datastax.driver.core.DefaultEndPointFactory;
import com.datastax.driver.core.EndPointFactory;
import com.datastax.driver.core.TimestampGenerator;

/** Policies configured for a {@link com.datastax.driver.core.Cluster} instance. */
public class Policies {

  /**
   * Returns a builder to create a new {@code Policies} object.
   *
   * @return the builder.
   */
  public static Builder builder() {
    return new Builder();
  }

  private static final ReconnectionPolicy DEFAULT_RECONNECTION_POLICY =
      new ExponentialReconnectionPolicy(1000, 10 * 60 * 1000);
  private static final RetryPolicy DEFAULT_RETRY_POLICY = DefaultRetryPolicy.INSTANCE;
  private static final AddressTranslator DEFAULT_ADDRESS_TRANSLATOR = new IdentityTranslator();
  private static final SpeculativeExecutionPolicy DEFAULT_SPECULATIVE_EXECUTION_POLICY =
      NoSpeculativeExecutionPolicy.INSTANCE;

  private final LoadBalancingPolicy loadBalancingPolicy;
  private final ReconnectionPolicy reconnectionPolicy;
  private final RetryPolicy retryPolicy;
  private final AddressTranslator addressTranslator;
  private final TimestampGenerator timestampGenerator;
  private final SpeculativeExecutionPolicy speculativeExecutionPolicy;
  private final EndPointFactory endPointFactory;

  private Policies(
      LoadBalancingPolicy loadBalancingPolicy,
      ReconnectionPolicy reconnectionPolicy,
      RetryPolicy retryPolicy,
      AddressTranslator addressTranslator,
      TimestampGenerator timestampGenerator,
      SpeculativeExecutionPolicy speculativeExecutionPolicy,
      EndPointFactory endPointFactory) {
    this.loadBalancingPolicy = loadBalancingPolicy;
    this.reconnectionPolicy = reconnectionPolicy;
    this.retryPolicy = retryPolicy;
    this.addressTranslator = addressTranslator;
    this.timestampGenerator = timestampGenerator;
    this.speculativeExecutionPolicy = speculativeExecutionPolicy;
    this.endPointFactory = endPointFactory;
  }

  /**
   * The default load balancing policy.
   *
   * <p>The default load balancing policy is {@link DCAwareRoundRobinPolicy} with token awareness
   * (so {@code new TokenAwarePolicy(new DCAwareRoundRobinPolicy())}).
   *
   * <p>Note that this policy shuffles the replicas when token awareness is used, see {@link
   * TokenAwarePolicy#TokenAwarePolicy(LoadBalancingPolicy, boolean)} for an explanation of the
   * tradeoffs.
   *
   * @return the default load balancing policy.
   */
  public static LoadBalancingPolicy defaultLoadBalancingPolicy() {
    // Note: balancing policies are stateful, so we can't store that in a static or that would screw
    // thing
    // up if multiple Cluster instance are started in the same JVM.
    return new TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build());
  }

  /**
   * The default reconnection policy.
   *
   * <p>The default reconnection policy is an {@link ExponentialReconnectionPolicy} where the base
   * delay is 1 second and the max delay is 10 minutes;
   *
   * @return the default reconnection policy.
   */
  public static ReconnectionPolicy defaultReconnectionPolicy() {
    return DEFAULT_RECONNECTION_POLICY;
  }

  /**
   * The default retry policy.
   *
   * <p>The default retry policy is {@link DefaultRetryPolicy}.
   *
   * @return the default retry policy.
   */
  public static RetryPolicy defaultRetryPolicy() {
    return DEFAULT_RETRY_POLICY;
  }

  /**
   * The default address translator.
   *
   * <p>The default address translator is {@link IdentityTranslator}.
   *
   * @return the default address translator.
   */
  public static AddressTranslator defaultAddressTranslator() {
    return DEFAULT_ADDRESS_TRANSLATOR;
  }

  /**
   * The default timestamp generator.
   *
   * <p>This is an instance of {@link AtomicMonotonicTimestampGenerator}.
   *
   * @return the default timestamp generator.
   */
  public static TimestampGenerator defaultTimestampGenerator() {
    return new AtomicMonotonicTimestampGenerator();
  }

  /**
   * The default speculative retry policy.
   *
   * <p>The default speculative retry policy is a {@link NoSpeculativeExecutionPolicy}.
   *
   * @return the default speculative retry policy.
   */
  public static SpeculativeExecutionPolicy defaultSpeculativeExecutionPolicy() {
    return DEFAULT_SPECULATIVE_EXECUTION_POLICY;
  }

  public static EndPointFactory defaultEndPointFactory() {
    return new DefaultEndPointFactory();
  }

  /**
   * The load balancing policy in use.
   *
   * <p>The load balancing policy defines how Cassandra hosts are picked for queries.
   *
   * @return the load balancing policy in use.
   */
  public LoadBalancingPolicy getLoadBalancingPolicy() {
    return loadBalancingPolicy;
  }

  /**
   * The reconnection policy in use.
   *
   * <p>The reconnection policy defines how often the driver tries to reconnect to a dead node.
   *
   * @return the reconnection policy in use.
   */
  public ReconnectionPolicy getReconnectionPolicy() {
    return reconnectionPolicy;
  }

  /**
   * The retry policy in use.
   *
   * <p>The retry policy defines in which conditions a query should be automatically retries by the
   * driver.
   *
   * @return the retry policy in use.
   */
  public RetryPolicy getRetryPolicy() {
    return retryPolicy;
  }

  /**
   * The address translator in use.
   *
   * @return the address translator in use.
   */
  public AddressTranslator getAddressTranslator() {
    return addressTranslator;
  }

  /**
   * The timestamp generator to use.
   *
   * @return the timestamp generator to use.
   */
  public TimestampGenerator getTimestampGenerator() {
    return timestampGenerator;
  }

  /**
   * The speculative execution policy in use.
   *
   * @return the speculative execution policy in use.
   */
  public SpeculativeExecutionPolicy getSpeculativeExecutionPolicy() {
    return speculativeExecutionPolicy;
  }

  public EndPointFactory getEndPointFactory() {
    return endPointFactory;
  }

  /** A builder to create a new {@code Policies} object. */
  public static class Builder {
    private LoadBalancingPolicy loadBalancingPolicy;
    private ReconnectionPolicy reconnectionPolicy;
    private RetryPolicy retryPolicy;
    private AddressTranslator addressTranslator;
    private TimestampGenerator timestampGenerator;
    private SpeculativeExecutionPolicy speculativeExecutionPolicy;
    private EndPointFactory endPointFactory;

    /**
     * Sets the load balancing policy.
     *
     * @param loadBalancingPolicy see {@link #getLoadBalancingPolicy()}.
     * @return this builder.
     */
    public Builder withLoadBalancingPolicy(LoadBalancingPolicy loadBalancingPolicy) {
      this.loadBalancingPolicy = loadBalancingPolicy;
      return this;
    }

    /**
     * Sets the reconnection policy.
     *
     * @param reconnectionPolicy see {@link #getReconnectionPolicy()}.
     * @return this builder.
     */
    public Builder withReconnectionPolicy(ReconnectionPolicy reconnectionPolicy) {
      this.reconnectionPolicy = reconnectionPolicy;
      return this;
    }

    /**
     * Sets the retry policy.
     *
     * @param retryPolicy see {@link #getRetryPolicy()}.
     * @return this builder.
     */
    public Builder withRetryPolicy(RetryPolicy retryPolicy) {
      this.retryPolicy = retryPolicy;
      return this;
    }

    /**
     * Sets the address translator.
     *
     * @param addressTranslator see {@link #getAddressTranslator()}.
     * @return this builder.
     */
    public Builder withAddressTranslator(AddressTranslator addressTranslator) {
      this.addressTranslator = addressTranslator;
      return this;
    }

    /**
     * Sets the timestamp generator.
     *
     * @param timestampGenerator see {@link #getTimestampGenerator()}.
     * @return this builder.
     */
    public Builder withTimestampGenerator(TimestampGenerator timestampGenerator) {
      this.timestampGenerator = timestampGenerator;
      return this;
    }

    /**
     * Sets the speculative execution policy.
     *
     * @param speculativeExecutionPolicy see {@link #getSpeculativeExecutionPolicy()}.
     * @return this builder.
     */
    public Builder withSpeculativeExecutionPolicy(
        SpeculativeExecutionPolicy speculativeExecutionPolicy) {
      this.speculativeExecutionPolicy = speculativeExecutionPolicy;
      return this;
    }

    public Builder withEndPointFactory(EndPointFactory endPointFactory) {
      this.endPointFactory = endPointFactory;
      return this;
    }

    /**
     * Builds the final object from this builder.
     *
     * <p>Any field that hasn't been set explicitly will get its default value.
     *
     * @return the object.
     */
    public Policies build() {
      return new Policies(
          loadBalancingPolicy == null ? defaultLoadBalancingPolicy() : loadBalancingPolicy,
          reconnectionPolicy == null ? defaultReconnectionPolicy() : reconnectionPolicy,
          retryPolicy == null ? defaultRetryPolicy() : retryPolicy,
          addressTranslator == null ? defaultAddressTranslator() : addressTranslator,
          timestampGenerator == null ? defaultTimestampGenerator() : timestampGenerator,
          speculativeExecutionPolicy == null
              ? defaultSpeculativeExecutionPolicy()
              : speculativeExecutionPolicy,
          endPointFactory == null ? defaultEndPointFactory() : endPointFactory);
    }
  }
}
