/*
 * 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.
 */

#pragma once

#ifndef GEODE_POOLFACTORY_H_
#define GEODE_POOLFACTORY_H_

#include <chrono>

#include "Pool.hpp"
#include "internal/chrono/duration.hpp"
#include "internal/geode_globals.hpp"

/**
 * @file
 */

namespace apache {
namespace geode {
namespace client {

class CacheImpl;
class PoolAttributes;
class Pool;

/**
 * This interface provides for the configuration and creation of instances of
 * {@link Pool}.
 * <p>Every pool needs to have at least one {@link #addLocator locator} or
 * {@link #addServer server} added
 * to it. Locators should be added unless direct connections to
 * bridge servers are desired.
 * <p>The setter methods are used to specify
 * non-default values for the other pool properties.
 * <p>Once it is configured {@link #create}
 * will produce an instance.
 * <p>The factory can be restored to its default
 * configuration by calling {@link #reset}.
 * <p>Instances of this interface can be created by calling
 * {@link Cache#getPoolFactory}.
 * <p>
 * If a subscription is going to be made using a pool then subscriptions
 * {@link #setSubscriptionEnabled must be enabled} on the pool.
 * Subscriptions are made using these APIs:
 * <ul>
 * <li>{@link QueryService#newCq}
 * <li>{@link Region#registerKeys}
 * <li>{@link Region#registerAllKeys}
 * <li>{@link Region#registerRegex}
 * </ul>
 *
 */
class APACHE_GEODE_EXPORT PoolFactory {
 public:
  /**
   * The default amount of time which we will wait for a free connection if max
   * connections is set and all of the connections are in use.
   * <p>Current value: <code>10s</code>.
   */
  static const std::chrono::milliseconds DEFAULT_FREE_CONNECTION_TIMEOUT;

  /**
   * The default interval in which the pool will check to see if
   * a connection to a given server should be moved to a different
   * server to improve the load balance.
   * <p>Current value: <code>5min</code>
   */
  static const std::chrono::milliseconds DEFAULT_LOAD_CONDITIONING_INTERVAL;

  /**
   * The default size in bytes of the socket buffer on each connection
   * established.
   * <p>Current value: <code>32768</code>.
   */
  static const int DEFAULT_SOCKET_BUFFER_SIZE = 32768;

  /**
   * The default amount of time to wait for a response from a server.
   * <p>Current value: <code>10s</code>.
   */
  static const std::chrono::milliseconds DEFAULT_READ_TIMEOUT;

  /**
   * The default number of connections to be created initially.
   * <p>Current value: <code>1</code>.
   */
  static const int DEFAULT_MIN_CONNECTIONS = 1;

  /**
   * The default maximum number of connections to be created.
   * <p>Current value: <code>-1</code>.
   */
  static const int DEFAULT_MAX_CONNECTIONS = -1;

  /**
   * The default amount of time to wait for a connection to become idle.
   * <p>Current value: <code>5s</code>.
   */
  static const std::chrono::milliseconds DEFAULT_IDLE_TIMEOUT;

  /**
   * The default number of times to retry an operation after a timeout or
   * exception.
   * <p>Current value: <code>-1</code>.
   */
  static const int DEFAULT_RETRY_ATTEMPTS = -1;

  /**
   * The default frequency, to ping servers.
   * <p>Current value: <code>10s</code>.
   */
  static const std::chrono::milliseconds DEFAULT_PING_INTERVAL;

  /**
   * The default frequency to update the locator list.
   * <p>Current value: <code>5s</code>.
   */
  static const std::chrono::milliseconds DEFAULT_UPDATE_LOCATOR_LIST_INTERVAL;

  /**
   * The default frequency that client statistics are sent to the server.
   * <p>Current value: <code>std::chrono::milliseconds::zero()</code>
   * (disabled).
   */
  static const std::chrono::milliseconds DEFAULT_STATISTIC_INTERVAL;

  /**
   * The default value for whether to establish a server to client subscription.
   * <p>Current value: <code>false</code>.
   */
  static const bool DEFAULT_SUBSCRIPTION_ENABLED = false;

  /**
   * The default redundancy for servers holding subscriptions established by
   * this
   * client.
   * <p>Current value: <code>0</code>.
   */
  static const int DEFAULT_SUBSCRIPTION_REDUNDANCY = 0;

  /**
   * The default amount of time that messages sent from a  server to a client
   * will be tracked. The tracking is done to minimize duplicate events.
   * <p>Current value: <code>900s</code>.
   */
  static const std::chrono::milliseconds
      DEFAULT_SUBSCRIPTION_MESSAGE_TRACKING_TIMEOUT;

  /**
   * The default amount of time to wait before sending an acknowledgement to the
   * server about events received from the subscriptions.
   * <p>Current value: <code>100ms</code>.
   */
  static const std::chrono::milliseconds DEFAULT_SUBSCRIPTION_ACK_INTERVAL;

  /**
   * The default server group.
   * <p>Current value: <code>""</code>.
   */
  static const std::string DEFAULT_SERVER_GROUP;
  /**
   * Whether thread local connection is enabled.
   * <p>Current value: <code>"false"</code>.
   */
  static constexpr bool DEFAULT_THREAD_LOCAL_CONN = false;

  /**
   * Whether client is in multi user secure mode
   * <p>Current value: <code>"false"</code>.
   */
  static constexpr bool DEFAULT_MULTIUSER_SECURE_MODE = false;

  /**
   * The default value for whether to have single hop optimizations enabled.
   * <p>Current value: <code>true</code>.
   */
  static constexpr bool DEFAULT_PR_SINGLE_HOP_ENABLED = true;

  /**
   * Sets the free connection timeout for this pool.
   * If the pool has a max connections setting, operations will block
   * if all of the connections are in use. The free connection timeout
   * specifies how long those operations will block waiting for
   * a free connection before receiving
   * an {@link AllConnectionsInUseException}. If max connections
   * is not set this setting has no effect.
   *
   * @see #setMaxConnections(int)
   *
   * @param connectionTimeout is the connection timeout
   *
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>connectionTimeout</code>
   * is less than or equal to <code>std::chrono::milliseconds::zero()</code>.
   */
  PoolFactory& setFreeConnectionTimeout(
      std::chrono::milliseconds connectionTimeout);

  /**
   * Sets the load conditioning interval for this pool.
   * This interval controls how frequently the pool will check to see if
   * a connection to a given server should be moved to a different
   * server to improve the load balance.
   * <p>A value of <code>std::chrono::milliseconds::zero()</code> disables load
   * conditioning.
   *
   * @param loadConditioningInterval is the connection lifetime
   *
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>connectionLifetime</code>
   * is less than <code>std::chrono::milliseconds::zero()</code>.
   */
  PoolFactory& setLoadConditioningInterval(
      std::chrono::milliseconds loadConditioningInterval);

  /**
   * Sets the socket buffer size for each connection made in this pool.
   * Large messages can be received and sent faster when this buffer is larger.
   * Larger buffers also optimize the rate at which servers can send events
   * for client subscriptions.
   *
   * @param bufferSize is the size of the socket buffers used for reading and
   * writing on each connection in this pool.
   *
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>bufferSize</code>
   * is less than or equal to <code>0</code>.
   */
  PoolFactory& setSocketBufferSize(int bufferSize);

  /**
   * Sets the thread local connections policy for this pool.
   * If <code>true</code> then any time a thread goes to use a connection
   * from this pool it will check a thread local cache and see if it already
   * has a connection in it. If so it will use it. If not it will get one from
   * this pool and cache it in the thread local. This gets rid of thread
   * contention
   * for the connections but increases the number of connections the servers
   * see.
   * <p>If <code>false</code> then connections are returned to the pool as soon
   * as the operation being done with the connection completes. This allows
   * connections to be shared among multiple threads keeping the number of
   * connections down.
   *
   * @param threadLocalConnections if <code>true</code> then enable thread local
   * connections.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setThreadLocalConnections(bool threadLocalConnections);

  /**
   * Sets the duration to wait for a response from a server before timing out
   * the operation and trying another server (if any are available).
   *
   * @param timeout duration to wait for a response from a
   * server
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>timeout</code>
   * is less than or equal to <code>std::chrono::milliseconds::zero()</code>.
   */
  PoolFactory& setReadTimeout(std::chrono::milliseconds timeout);

  /**
   * Sets the minimum number of connections to keep available at all times.
   * When the pool is created, it will create this many connections.
   * If <code>0</code> then connections will not be made until an actual
   * operation
   * is done that requires client-to-server communication.
   *
   * @param minConnections is the initial number of connections
   * this pool will create.
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>minConnections</code>
   * is less than <code>0</code>.
   */
  PoolFactory& setMinConnections(int minConnections);

  /**
   * Sets the max number of client to server connections that the pool will
   * create. If all of
   * the connections are in use, an operation requiring a client to server
   * connection
   * will block until a connection is available.
   *
   * @see #setFreeConnectionTimeout(int)
   *
   * @param maxConnections is the maximum number of connections in the pool.
   * <code>-1</code> indicates that there is no maximum number of connections
   *
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>maxConnections</code>
   * is less than <code>minConnections</code>.
   */
  PoolFactory& setMaxConnections(int maxConnections);

  /**
   * Sets the amount of time a connection can be idle before expiring the
   * connection. If the pool size is greater than the minimum specified by
   * {@link PoolFactory#setMinConnections(int)}, connections which have been
   * idle for longer than the idleTimeout will be closed.
   *
   * @param idleTimeout is the duration that an idle connection
   * should live no less than before expiring, actual time may be longer
   * depending on clock resolution. A duration std::chrono::milliseconds::zero()
   * indicates that connections should never expire.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setIdleTimeout(std::chrono::milliseconds);

  /**
   * Set the number of times to retry a request after timeout/exception.
   * @param retryAttempts is the number of times to retry a request
   * after timeout/exception. -1 indicates that a request should be
   * tried against every available server before failing
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>idleTimout</code>
   * is less than <code>0</code>.
   */
  PoolFactory& setRetryAttempts(int retryAttempts);

  /**
   * The frequency with which servers must be pinged to verify that they are
   * still alive.
   * Each server will be sent a ping every <code>pingInterval</code> if there
   * has not
   * been any other communication with the server.
   *
   * These pings are used by the server to monitor the health of
   * the client. Make sure that the <code>pingInterval</code> is less than the
   * maximum time between pings allowed by the bridge server.
   *
   * @param pingInterval is the amount of time  between pings.
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>pingInterval</code>
   * is less than <code>0</code>.
   *
   * @see CacheServer#setMaximumTimeBetweenPings(int)
   */
  PoolFactory& setPingInterval(std::chrono::milliseconds pingInterval);

  /**
   * The frequency with which client updates the locator list. To disable this
   * set its value to std::chrono::milliseconds::zero().
   *
   * @param updateLocatorListInterval is the amount of time
   * between checking locator list at locator.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setUpdateLocatorListInterval(
      std::chrono::milliseconds updateLocatorListInterval);

  /**
   * The frequency with which the client statistics must be sent to the server.
   * Doing this allows <code>GFMon</code> to monitor clients.
   * <p>A value of <code>std::chrono::milliseconds::zero()</code> disables the
   * sending of client statistics to the server.
   *
   * @param statisticInterval is the amount of time between
   * sends of client statistics to the server.
   *
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>statisticInterval</code>
   * is less than <code>std::chrono::milliseconds::zero()</code>.
   */
  PoolFactory& setStatisticInterval(
      std::chrono::milliseconds statisticInterval);

  /**
   * Configures the group which contains all the servers that this pool connects
   * to.
   * @param group is the server group that this pool will connect to.
   * If the value is <code>null</code> or <code>""</code> then the pool connects
   * to all servers.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setServerGroup(std::string group);

  /**
   * Adds a locator, given its host and port, to this factory.
   * The locator must be a server locator and will be used to discover other
   * running
   * bridge servers and locators.
   * @param host is the host name or ip address that the locator is listening
   * on.
   * @param port is the port that the locator is listening on.
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if the <code>host</code> is an unknown
   * host
   * according to {@link java.net.InetAddress#getByName} or if the port is
   * outside
   * the valid range of [1..65535] inclusive.
   * @throws IllegalStateException if the locator has already been {@link
   * #addServer added} to this factory.
   */
  PoolFactory& addLocator(const std::string& host, int port);

  /**
   * Adds a server, given its host and port, to this factory.
   * The server must be a bridge server and this client will
   * directly connect to the server without consulting a server locator.
   * @param host is the host name or ip address that the server is listening on.
   * @param port is the port that the server is listening on.
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if the <code>host</code> is an unknown
   * host
   * according to {@link java.net.InetAddress#getByName} or if the port is
   * outside
   * the valid range of [1..65535] inclusive.
   * @throws IllegalStateException if the server has already been {@link
   * #addLocator added} to this factory.
   */
  PoolFactory& addServer(const std::string& host, int port);

  /**
   * Set proxy info for SNI connection.  Used for connecting via SNI proxy.
   */
  PoolFactory& setSniProxy(std::string hostname, uint16_t port);

  /**
   * If set to <code>true</code> then the created pool will have
   * server-to-client
   * subscriptions enabled.
   * If set to <code>false</code> then all <code>Subscription*</code> attributes
   * are ignored at the time of creation.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setSubscriptionEnabled(bool enabled);

  /**
   * Sets the redundancy level for this pools server-to-client subscriptions.
   * If <code>0</code> then no redundant copies are kept on the servers.
   * Otherwise an effort is made to maintain the requested number of
   * copies of the server-to-client subscriptions. At most, one copy per server
   * is
   *  made up to the requested level.
   * @param redundancy is the number of redundant servers for this client's
   * subscriptions.
   * @return a reference to <code>this</code>
   * @throws IllegalArgumentException if <code>redundancyLevel</code>
   * is less than <code>-1</code>.
   */
  PoolFactory& setSubscriptionRedundancy(int redundancy);

  /**
   * Sets the messageTrackingTimeout attribute which is the time-to-live period
   * for subscription events the client has received from the server. It is used
   * to minimize duplicate events. Entries that have not been modified for this
   * amount of time  are expired from the list.
   *
   * @param messageTrackingTimeout is the duration to set the timeout to.
   * @return a reference to <code>this</code>
   *
   * @throws IllegalArgumentException if <code>messageTrackingTimeout</code>
   * is less than or equal to <code>0</code>.

   */
  PoolFactory& setSubscriptionMessageTrackingTimeout(
      std::chrono::milliseconds messageTrackingTimeout);

  /**
   * Sets the is the interval to wait before sending acknowledgements to the
   * bridge server for events received from the server subscriptions.
   *
   * @param ackInterval is the duration to wait before sending  event
   * acknowledgements.
   *
   * @throws IllegalArgumentException if <code>ackInterval</code>
   * is less than or equal to <code>0</code>.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setSubscriptionAckInterval(
      std::chrono::milliseconds ackInterval);

  /**
   * Sets whether Pool is in multi user secure mode.
   * If its in multiuser mode then app needs to get RegionService instance of
   * Cache.
   * Default value is false.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setMultiuserAuthentication(bool multiuserAuthentication);

  /**
   * Resets the configuration of this factory to its defaults.
   * @return a reference to <code>this</code>
   */
  PoolFactory& reset();

  /**
   * Creates a new Pool for connecting a client to a set of Geode Cache
   * Servers.
   * using this factory's settings for attributes.
   *
   * @param name is the name of the pool, used when connecting regions to it
   * @throws IllegalStateException if a pool with <code>name</code> already
   * exists
   * @throws IllegalStateException if a locator or server has not been added.
   * @return the newly created pool.
   */
  std::shared_ptr<Pool> create(std::string name);

  /**
   * By default setPRSingleHopEnabled is true.<br>
   * The client is aware of the locations of partitions on servers hosting
   * partitioned regions.
   * Using this information, the client routes client cache operations
   * directly to the server which is hosting the required partition for the
   * cache operation.
   * If setPRSingleHopEnabled is false the client can do an extra hop on servers
   * to go to the required partition for that cache operation.
   * The setPRSingleHopEnabled setting avoids extra hops only for the following
   * cache operations:<br>
   * 1. {@link Region#put(Object, Object)}<br>
   * 2. {@link Region#get(Object)}<br>
   * 3. {@link Region#destroy(Object)}<br>
   * If true, works best when {@link PoolFactory#setMaxConnections(int)} is set
   * to -1.
   * @param enabled is a boolean indicating whether PR Single Hop optimization
   * should be enabled or not.
   * @return a reference to <code>this</code>
   */
  PoolFactory& setPRSingleHopEnabled(bool enabled);

  ~PoolFactory() = default;

  PoolFactory(const PoolFactory&) = default;

 private:
  explicit PoolFactory(const Cache& cache);
  std::shared_ptr<PoolAttributes> m_attrs;
  bool m_isSubscriptionRedundancy;
  bool m_addedServerOrLocator;
  const Cache& m_cache;

  friend class Cache;
  friend class PoolManager;
  friend class PoolManagerImpl;
  friend class CacheFactory;
  friend class CacheXmlCreation;
};

}  // namespace client
}  // namespace geode
}  // namespace apache

#endif  // GEODE_POOLFACTORY_H_
