/*
 *
 * MariaDB Client for Java
 *
 * Copyright (c) 2012-2014 Monty Program Ab.
 * Copyright (c) 2015-2017 MariaDB Ab.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to Monty Program Ab info@montyprogram.com.
 *
 * This particular MariaDB Client for Java file is work
 * derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
 * the following copyright and notice provisions:
 *
 * Copyright (c) 2009-2011, Marcus Eriksson
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this list
 * of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * Neither the name of the driver nor the names of its contributors may not be
 * used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS  AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 */

package org.mariadb.jdbc.internal.util;

import java.lang.reflect.Field;
import java.util.Properties;
import org.mariadb.jdbc.internal.util.constant.HaMode;

public enum DefaultOptions {

  USER("user", "1.0.0", "Database user name", false),
  PASSWORD("password", "1.0.0", "Password of database user", false),

  /**
   * The connect timeout value, in milliseconds, or zero for no timeout. Default: 30000 (30 seconds)
   * (was 0 before 2.1.2)
   */
  CONNECT_TIMEOUT("connectTimeout", 30_000, 0, "1.1.8",
      "The connect timeout value, in milliseconds, or zero for no timeout.", false),
  PIPE("pipe", "1.1.3", "On Windows, specify named pipe name to connect.", false),
  LOCAL_SOCKET("localSocket", "1.1.4",
      "Permits connecting to the database via Unix domain socket, if the server "
          + "allows it. \nThe value is the path of Unix domain socket (i.e \"socket\" database parameter : "
          + "select @@socket).", false),
  SHARED_MEMORY("sharedMemory", "1.1.4",
      "Permits connecting to the database via shared memory, if the server allows "
          + "it. \nThe value is the base name of the shared memory.", false),
  TCP_NO_DELAY("tcpNoDelay", Boolean.TRUE, "1.0.0",
      "Sets corresponding option on the connection socket.", false),
  TCP_ABORTIVE_CLOSE("tcpAbortiveClose", Boolean.FALSE, "1.1.1",
      "Sets corresponding option on the connection "
          + "socket.", false),
  LOCAL_SOCKET_ADDRESS("localSocketAddress", "1.1.8",
      "Hostname or IP address to bind the connection socket to a "
          + "local (UNIX domain) socket.", false),
  SOCKET_TIMEOUT("socketTimeout", new Integer[]{10000, null, null, null, null, null}, 0, "1.1.8",
      "Defined the "
          + "network socket timeout (SO_TIMEOUT) in milliseconds. Value of 0 disables this timeout. \n"
          + "If the goal is to set a timeout for all queries, since MariaDB 10.1.1, the server has permitted a "
          + "solution to limit the query time by setting a system variable, max_statement_time. The advantage is that"
          + " the connection then is still usable.\n"
          + "Default: 0 (standard configuration) or 10000ms (using \"aurora\" failover configuration).",
      false),
  INTERACTIVE_CLIENT("interactiveClient", Boolean.FALSE, "1.1.8",
      "Session timeout is defined by the wait_timeout "
          + "server variable. Setting interactiveClient to true will tell the server to use the interactive_timeout "
          + "server variable.", false),
  DUMP_QUERY_ON_EXCEPTION("dumpQueriesOnException", Boolean.TRUE, "1.1.0",
      "If set to 'true', an exception is thrown "
          + "during query execution containing a query string.", false),
  USE_OLD_ALIAS_METADATA_BEHAVIOR("useOldAliasMetadataBehavior", Boolean.FALSE, "1.1.9",
      "Metadata ResultSetMetaData.getTableName() returns the physical table name. \"useOldAliasMetadataBehavior\""
          + " permits activating the legacy code that sends the table alias if set.", false),
  ALLOW_LOCAL_INFILE("allowLocalInfile", Boolean.TRUE, "1.2.1", "Permit loading data from file",
      false),
  SESSION_VARIABLES("sessionVariables", "1.1.0",
      "<var>=<value> pairs separated by comma, mysql session variables, "
          + "set upon establishing successful connection.", false),
  CREATE_DATABASE_IF_NOT_EXISTS("createDatabaseIfNotExist", Boolean.FALSE, "1.1.8",
      "the specified database in the "
          + "url will be created if non-existent.", false),
  SERVER_TIMEZONE("serverTimezone", "1.1.8", "Defines the server time zone.\n"
      + "to use only if the jre server has a different time implementation of the server.\n"
      + "(best to have the same server time zone when possible).", false),
  NULL_CATALOG_MEANS_CURRENT("nullCatalogMeansCurrent", Boolean.TRUE, "1.1.8",
      "DatabaseMetaData use current catalog"
          + " if null.", false),
  TINY_INT_IS_BIT("tinyInt1isBit", Boolean.TRUE, "1.0.0",
      "Datatype mapping flag, handle MySQL Tiny as BIT(boolean).",
      false),
  YEAR_IS_DATE_TYPE("yearIsDateType", Boolean.TRUE, "1.0.0",
      "Year is date type, rather than numerical.", false),
  USE_SSL("useSsl", Boolean.FALSE, "1.1.0", "Force SSL on connection. (legacy alias \"useSSL\")",
      false),
  USER_COMPRESSION("useCompression", Boolean.FALSE, "1.0.0",
      "Compresses the exchange with the database through gzip."
          + " This permits better performance when the database is not in the same location.",
      false),
  ALLOW_MULTI_QUERIES("allowMultiQueries", Boolean.FALSE, "1.0.0",
      "permit multi-queries like insert into ab (i) "
          + "values (1); insert into ab (i) values (2).", false),
  REWRITE_BATCHED_STATEMENTS("rewriteBatchedStatements", Boolean.FALSE, "1.1.8",
      "For insert queries, rewrite "
          + "batchedStatement to execute in a single executeQuery.\n"
          + "example:\n"
          + "   insert into ab (i) values (?) with first batch values = 1, second = 2 will be rewritten\n"
          + "   insert into ab (i) values (1), (2). \n"
          + "\n"
          + "If query cannot be rewriten in \"multi-values\", rewrite will use multi-queries : INSERT INTO "
          + "TABLE(col1) VALUES (?) ON DUPLICATE KEY UPDATE col2=? with values [1,2] and [2,3]\" will be rewritten\n"
          + "INSERT INTO TABLE(col1) VALUES (1) ON DUPLICATE KEY UPDATE col2=2;INSERT INTO TABLE(col1) VALUES (3) ON "
          + "DUPLICATE KEY UPDATE col2=4\n"
          + "\n"
          + "when active, the useServerPrepStmts option is set to false", false),
  TCP_KEEP_ALIVE("tcpKeepAlive", Boolean.TRUE, "1.0.0",
      "Sets corresponding option on the connection socket.", false),
  TCP_RCV_BUF("tcpRcvBuf", (Integer) null, 0, "1.0.0",
      "set buffer size for TCP buffer (SO_RCVBUF).", false),
  TCP_SND_BUF("tcpSndBuf", (Integer) null, 0, "1.0.0",
      "set buffer size for TCP buffer (SO_SNDBUF).", false),
  SOCKET_FACTORY("socketFactory", "1.0.0",
      "to use a custom socket factory, set it to the full name of the class that"
          + " implements javax.net.SocketFactory.", false),
  PIN_GLOBAL_TX_TO_PHYSICAL_CONNECTION("pinGlobalTxToPhysicalConnection", Boolean.FALSE, "1.1.8",
      "", false),
  TRUST_SERVER_CERTIFICATE("trustServerCertificate", Boolean.FALSE, "1.1.1",
      "When using SSL, do not check server's"
          + " certificate.", false),
  SERVER_SSL_CERT("serverSslCert", "1.1.3",
      "Permits providing server's certificate in DER form, or server's CA"
          + " certificate. The server will be added to trustStor. This permits a self-signed certificate to be trusted.\n"
          + "Can be used in one of 3 forms : \n"
          + "* serverSslCert=/path/to/cert.pem (full path to certificate)\n"
          + "* serverSslCert=classpath:relative/cert.pem (relative to current classpath)\n"
          + "* or as verbatim DER-encoded certificate string \"------BEGIN CERTIFICATE-----\" .",
      false),
  USE_FRACTIONAL_SECONDS("useFractionalSeconds", Boolean.TRUE, "1.0.0",
      "Correctly handle subsecond precision in"
          + " timestamps (feature available with MariaDB 5.3 and later).\n"
          + "May confuse 3rd party components (Hibernate).", false),
  AUTO_RECONNECT("autoReconnect", Boolean.FALSE, "1.2.0",
      "Driver must recreateConnection after a failover.", false),
  FAIL_ON_READ_ONLY("failOnReadOnly", Boolean.FALSE, "1.2.0",
      "After a master failover and no other master found,"
          + " back on a read-only host ( throw exception if not).", false),
  RETRY_ALL_DOWN("retriesAllDown", 120, 0, "1.2.0",
      "When using loadbalancing, the number of times the driver should"
          + " cycle through available hosts, attempting to connect.\n"
          + "     * Between cycles, the driver will pause for 250ms if no servers are available.",
      false),
  FAILOVER_LOOP_RETRIES("failoverLoopRetries", 120, 0, "1.2.0",
      "When using failover, the number of times the driver"
          + " should cycle silently through available hosts, attempting to connect.\n"
          + "     * Between cycles, the driver will pause for 250ms if no servers are available.\n"
          + "     * if set to 0, there will be no silent reconnection", false),
  VALID_CONNECTION_TIMEOUT("validConnectionTimeout", 0, 0, "1.2.0",
      "When in multiple hosts, after this time in"
          + " second without used, verification that the connections haven't been lost.\n"
          + "     * When 0, no verification will be done. Defaults to 0 (120 before 1.5.8 version)",
      false),
  LOAD_BALANCE_BLACKLIST_TIMEOUT("loadBalanceBlacklistTimeout", 50, 0, "1.2.0",
      "time in second a server is"
          + " blacklisted after a connection failure.", false),
  CACHE_PREP_STMTS("cachePrepStmts", Boolean.TRUE, "1.3.0",
      "enable/disable prepare Statement cache, default true.",
      false),
  PREP_STMT_CACHE_SIZE("prepStmtCacheSize", 250, 0, "1.3.0",
      "This sets the number of prepared statements that the "
          + "driver will cache per connection if \"cachePrepStmts\" is enabled.", false),
  PREP_STMT_CACHE_SQL_LIMIT("prepStmtCacheSqlLimit", 2048, 0, "1.3.0",
      "This is the maximum length of a prepared SQL"
          + " statement that the driver will cache  if \"cachePrepStmts\" is enabled.", false),
  ASSURE_READONLY("assureReadOnly", Boolean.FALSE, "1.3.0",
      "If true, in high availability, and switching to a "
          + "read-only host, assure that this host is in read-only mode by setting the session to read-only.",
      false),
  USE_LEGACY_DATETIME_CODE("useLegacyDatetimeCode", Boolean.TRUE, "1.3.0",
      "if true (default) store date/timestamps "
          + "according to client time zone.\n"
          + "if false, store all date/timestamps in DB according to server time zone, and time information (that is a"
          + " time difference), doesn't take\n"
          + "timezone in account.", false),
  MAXIMIZE_MYSQL_COMPATIBILITY("maximizeMysqlCompatibility", Boolean.FALSE, "1.3.0",
      "maximize Mysql compatibility.\n"
          + "when using jdbc setDate(), will store date in client timezone, not in server timezone when "
          + "useLegacyDatetimeCode = false.\n"
          + "default to false.", false),
  USE_SERVER_PREP_STMTS("useServerPrepStmts", Boolean.FALSE, "1.3.0",
      "useServerPrepStmts must prepared statements be"
          + " prepared on server side, or just faked on client side.\n"
          + "     * if rewriteBatchedStatements is set to true, this options will be set to false.",
      false),
  TRUSTSTORE("trustStore", "1.3.0",
      "File path of the trustStore file (similar to java System property "
          + "\"javax.net.ssl.trustStore\"). (legacy alias trustCertificateKeyStoreUrl)\n"
          + "Use the specified file for trusted root certificates.\n"
          + "When set, overrides serverSslCert.", false),
  TRUST_CERTIFICATE_KEYSTORE_PASSWORD("trustStorePassword", "1.3.0",
      "Password for the trusted root certificate file"
          + " (similar to java System property \"javax.net.ssl.trustStorePassword\").\n"
          + "(legacy alias trustCertificateKeyStorePassword).", false),
  KEYSTORE("keyStore", "1.3.0",
      "File path of the keyStore file that contain client private key store and associate "
          + "certificates (similar to java System property \"javax.net.ssl.keyStore\", but ensure that only the "
          + "private key's entries are used).(legacy alias clientCertificateKeyStoreUrl).", false),
  KEYSTORE_PASSWORD("keyStorePassword", "1.3.0",
      "Password for the client certificate keyStore (similar to java "
          + "System property \"javax.net.ssl.keyStorePassword\").(legacy alias clientCertificateKeyStorePassword)",
      false),
  PRIVATE_KEYS_PASSWORD("keyPassword", "1.5.3",
      "Password for the private key in client certificate keyStore. (only "
          + "needed if private key password differ from keyStore password).", false),
  ENABLED_SSL_PROTOCOL_SUITES("enabledSslProtocolSuites", "1.5.0",
      "Force TLS/SSL protocol to a specific set of TLS "
          + "versions (comma separated list). \n"
          + "Example : \"TLSv1, TLSv1.1, TLSv1.2\"\n"
          + "(Alias \"enabledSSLProtocolSuites\" works too)", false),
  ENABLED_SSL_CIPHER_SUITES("enabledSslCipherSuites", "1.5.0",
      "Force TLS/SSL cipher (comma separated list).\n"
          + "Example : \"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384\"",
      false),
  CONTINUE_BATCH_ON_ERROR("continueBatchOnError", Boolean.TRUE, "1.4.0",
      "When executing batch queries, must batch "
          + "continue on error.", false),
  JDBC_COMPLIANT_TRUNCATION("jdbcCompliantTruncation", Boolean.TRUE, "1.4.0",
      "Truncation error (\"Data truncated for"
          + " column '%' at row %\", \"Out of range value for column '%' at row %\") will be thrown as error, and not as warning.",
      false),
  CACHE_CALLABLE_STMTS("cacheCallableStmts", Boolean.TRUE, "1.4.0",
      "enable/disable callable Statement cache, default"
          + " true.", false),
  CALLABLE_STMT_CACHE_SIZE("callableStmtCacheSize", 150, 0, "1.4.0",
      "This sets the number of callable statements "
          + "that the driver will cache per VM if \"cacheCallableStmts\" is enabled.", false),
  CONNECTION_ATTRIBUTES("connectionAttributes", "1.4.0",
      "When performance_schema is active, permit to send server "
          + "some client information in a key;value pair format "
          + "(example: connectionAttributes=key1:value1,key2,value2).\n"
          + "Those informations can be retrieved on server within tables performance_schema.session_connect_attrs "
          + "and performance_schema.session_account_connect_attrs.\n"
          + "This can permit from server an identification of client/application", false),
  USE_BATCH_MULTI_SEND("useBatchMultiSend", (Boolean) null, "1.5.0",
      "*Not compatible with aurora*\n"
          + "Driver will can send queries by batch. \n"
          + "If set to false, queries are sent one by one, waiting for the result before sending the next one. \n"
          + "If set to true, queries will be sent by batch corresponding to the useBatchMultiSendNumber option value"
          + " (default 100) or according to the max_allowed_packet server variable if the packet size does not permit"
          + " sending as many queries. Results will be read later, avoiding a lot of network latency when the client"
          + " and server aren't on the same host. \n"
          + "\n"
          + "This option is mainly effective when the client is distant from the server.", false),
  USE_BATCH_MULTI_SEND_NUMBER("useBatchMultiSendNumber", 100, 1, "1.5.0",
      "When option useBatchMultiSend is active,"
          + " indicate the maximum query send in a row before reading results.", false),
  LOGGING("log", Boolean.FALSE, "1.5.0", "Enable log information. \n"
      + "require Slf4j version > 1.4 dependency.\n"
      + "Log level correspond to Slf4j logging implementation", false),
  PROFILE_SQL("profileSql", Boolean.FALSE, "1.5.0", "log query execution time.", false),
  MAX_QUERY_LOG_SIZE("maxQuerySizeToLog", 1024, 0, "1.5.0", "Max query log size.", false),
  SLOW_QUERY_TIME("slowQueryThresholdNanos", (Long) null, 0L, "1.5.0",
      "Will log query with execution time superior"
          + " to this value (if defined )", false),
  PASSWORD_CHARACTER_ENCODING("passwordCharacterEncoding", "1.5.9",
      "Indicate password encoding charset. If not set,"
          + " driver use platform's default charset.", false),
  PIPELINE_AUTH("usePipelineAuth", (Boolean) null, "1.6.0", "*Not compatible with aurora*\n"
      + "During connection, different queries are executed. When option is active those queries are send using"
      + " pipeline (all queries are send, then only all results are reads), permitting faster connection "
      + "creation", false),
  ENABLE_PACKET_DEBUG("enablePacketDebug", Boolean.FALSE, "1.6.0",
      "Driver will save the last 16 MySQL packet "
          + "exchanges (limited to first 1000 bytes). Hexadecimal value of those packets will be added to stacktrace"
          + " when an IOException occur.\n"
          + "This option has no impact on performance but driver will then take 16kb more memory.",
      false),
  SSL_HOSTNAME_VERIFICATION("disableSslHostnameVerification", Boolean.FALSE, "2.1.0",
      "When using ssl, the driver "
          + "checks the hostname against the server's identity as presented in the server's certificate (checking "
          + "alternative names or the certificate CN) to prevent man-in-the-middle attacks. This option permits "
          + "deactivating this validation. Hostname verification is disabled when the trustServerCertificate "
          + "option is set", false),
  USE_BULK_PROTOCOL("useBulkStmts", Boolean.FALSE, "2.1.0",
      "Use dedicated COM_STMT_BULK_EXECUTE protocol for batch "
          + "insert when possible. (batch without Statement.RETURN_GENERATED_KEYS and streams) to have faster batch. "
          + "(significant only if server MariaDB >= 10.2.7)", false),
  AUTOCOMMIT("autocommit", Boolean.TRUE, "2.2.0",
      "Set default autocommit value on connection initialization", false),
  POOL("pool", Boolean.FALSE, "2.2.0",
      "Use pool. This option is useful only if not using a DataSource object, but "
          + "only a connection object.", false),
  POOL_NAME("poolName", "2.2.0",
      "Pool name that permits identifying threads. default: auto-generated as "
          + "MariaDb-pool-<pool-index>", false),
  MAX_POOL_SIZE("maxPoolSize", 8, 1, "2.2.0",
      "The maximum number of physical connections that the pool should "
          + "contain.", false),
  MIN_POOL_SIZE("minPoolSize", (Integer) null, 0, "2.2.0",
      "When connections are removed due to not being used for "
          + "longer than than \"maxIdleTime\", connections are closed and removed from the pool. \"minPoolSize\" "
          + "indicates the number of physical connections the pool should keep available at all times. Should be less"
          + " or equal to maxPoolSize.", false),
  MAX_IDLE_TIME("maxIdleTime", 600, Options.MIN_VALUE__MAX_IDLE_TIME, "2.2.0",
      "The maximum amount of time in seconds"
          + " that a connection can stay in the pool when not used. This value must always be below @wait_timeout"
          + " value - 45s \n"
          + "Default: 600 in seconds (=10 minutes), minimum value is 60 seconds", false),
  POOL_VALID_MIN_DELAY("poolValidMinDelay", 1000, 0, "2.2.0",
      "When asking a connection to pool, the pool will "
          + "validate the connection state. \"poolValidMinDelay\" permits disabling this validation if the connection"
          + " has been borrowed recently avoiding useless verifications in case of frequent reuse of connections. "
          + "0 means validation is done each time the connection is asked.", false),
  STATIC_GLOBAL("staticGlobal", Boolean.FALSE, "2.2.0",
      "Indicates the values of the global variables "
          + "max_allowed_packet, wait_timeout, autocommit, auto_increment_increment, time_zone, system_time_zone and"
          + " tx_isolation) won't be changed, permitting the pool to create new connections faster.",
      false),
  REGISTER_POOL_JMX("registerJmxPool", Boolean.TRUE, "2.2.0", "Register JMX monitoring pools.",
      false),
  USE_RESET_CONNECTION("useResetConnection", Boolean.FALSE, "2.2.0",
      "When a connection is closed() "
          + "(given back to pool), the pool resets the connection state. Setting this option, the prepare command "
          + "will be deleted, session variables changed will be reset, and user variables will be destroyed when the"
          + " server permits it (>= MariaDB 10.2.4, >= MySQL 5.7.3), permitting saving memory on the server if the "
          + "application make extensive use of variables. Must not be used with the useServerPrepStmts option",
      false),
  ALLOW_MASTER_DOWN("allowMasterDownConnection", Boolean.FALSE, "2.2.0",
      "When using master/slave configuration, "
          + "permit to create connection when master is down. If no master is up, default connection is then a slave "
          + "and Connection.isReadOnly() will then return true.", false),
  GALERA_ALLOWED_STATE("galeraAllowedState", "2.2.5",
      "Usually, Connection.isValid just send an empty packet to "
          + "server, and server send a small response to ensure connectivity. When this option is set, connector will"
          + " ensure Galera server state \"wsrep_local_state\" correspond to allowed values (separated by comma). "
          + "Example \"4,5\", recommended is \"4\". see galera state to know more.", false),
  USE_AFFECTED_ROWS("useAffectedRows", Boolean.FALSE, "2.3.0",
      "If false (default), use \"found rows\" for the row "
          + "count of statements. This corresponds to the JDBC standard.\n"
          + "If true, use \"affected rows\" for the row count.\n"
          + "This changes the behavior of, for example, UPDATE... ON DUPLICATE KEY statements.",
      false),
  INCLUDE_STATUS("includeInnodbStatusInDeadlockExceptions", Boolean.FALSE, "2.3.0",
      "add \"SHOW ENGINE INNODB STATUS\" result to exception trace when having a deadlock exception",
      false),
  INCLUDE_THREAD_DUMP("includeThreadDumpInDeadlockExceptions", Boolean.FALSE, "2.3.0",
      "add thread dump to exception trace when having a deadlock exception", false);

  private final String optionName;
  private final String description;
  private final boolean required;
  private final Object objType;
  private final Object defaultValue;
  private final Object minValue;
  private final Object maxValue;
  private final String implementationVersion;

  DefaultOptions(final String optionName, final String implementationVersion, String description,
      boolean required) {
    this.optionName = optionName;
    this.implementationVersion = implementationVersion;
    this.description = description;
    this.required = required;
    objType = String.class;
    defaultValue = null;
    minValue = null;
    maxValue = null;
  }

  DefaultOptions(final String optionName,
      final String defaultValue,
      final String implementationVersion,
      String description,
      boolean required) {
    this.optionName = optionName;
    this.implementationVersion = implementationVersion;
    this.description = description;
    this.required = required;
    objType = String.class;
    this.defaultValue = defaultValue;
    minValue = null;
    maxValue = null;
  }

  DefaultOptions(final String optionName,
      final Boolean defaultValue,
      final String implementationVersion,
      String description,
      boolean required) {
    this.optionName = optionName;
    this.objType = Boolean.class;
    this.defaultValue = defaultValue;
    this.implementationVersion = implementationVersion;
    this.description = description;
    this.required = required;
    minValue = null;
    maxValue = null;
  }

  DefaultOptions(final String optionName,
      final Integer defaultValue,
      final Integer minValue,
      final String implementationVersion,
      String description,
      boolean required) {
    this.optionName = optionName;
    this.objType = Integer.class;
    this.defaultValue = defaultValue;
    this.minValue = minValue;
    this.maxValue = Integer.MAX_VALUE;
    this.implementationVersion = implementationVersion;
    this.description = description;
    this.required = required;
  }

  DefaultOptions(final String optionName,
      final Long defaultValue,
      final Long minValue,
      final String implementationVersion,
      String description,
      boolean required) {
    this.optionName = optionName;
    this.objType = Long.class;
    this.defaultValue = defaultValue;
    this.minValue = minValue;
    this.maxValue = Long.MAX_VALUE;
    this.implementationVersion = implementationVersion;
    this.description = description;
    this.required = required;
  }


  DefaultOptions(final String optionName,
      final Integer[] defaultValue,
      final Integer minValue,
      final String implementationVersion,
      String description,
      boolean required) {
    this.optionName = optionName;
    this.objType = Integer.class;
    this.defaultValue = defaultValue;
    this.minValue = minValue;
    this.maxValue = Integer.MAX_VALUE;
    this.implementationVersion = implementationVersion;
    this.description = description;
    this.required = required;
  }

  public static Options defaultValues(final HaMode haMode) {
    return parse(haMode, "", new Properties());
  }

  /**
   * Generate an Options object with default value corresponding to High Availability mode.
   *
   * @param haMode current high Availability mode
   * @param pool   is for pool
   * @return Options object initialized
   */
  public static Options defaultValues(HaMode haMode, boolean pool) {
    Properties properties = new Properties();
    properties.setProperty("pool", String.valueOf(pool));
    Options options = parse(haMode, "", properties);
    optionCoherenceValidation(options);
    return options;
  }

  /**
   * Parse additional properties.
   *
   * @param haMode        current haMode.
   * @param urlParameters options defined in url
   * @param options       initial options
   */
  public static void parse(final HaMode haMode, final String urlParameters, final Options options) {
    Properties prop = new Properties();
    parse(haMode, urlParameters, prop, options);
    optionCoherenceValidation(options);
  }

  private static Options parse(final HaMode haMode, final String urlParameters,
      final Properties properties) {
    Options options = parse(haMode, urlParameters, properties, null);
    optionCoherenceValidation(options);
    return options;
  }

  /**
   * Parse additional properties .
   *
   * @param haMode        current haMode.
   * @param urlParameters options defined in url
   * @param properties    options defined by properties
   * @param options       initial options
   * @return options
   */
  public static Options parse(final HaMode haMode,
      final String urlParameters,
      final Properties properties,
      final Options options) {
    if (urlParameters != null && !urlParameters.isEmpty()) {
      String[] parameters = urlParameters.split("&");
      for (String parameter : parameters) {
        int pos = parameter.indexOf('=');
        if (pos == -1) {
          if (!properties.containsKey(parameter)) {
            properties.setProperty(parameter, "");
          }
        } else {
          if (!properties.containsKey(parameter.substring(0, pos))) {
            properties.setProperty(parameter.substring(0, pos), parameter.substring(pos + 1));
          }

        }
      }
    }
    return parse(haMode, properties, options);
  }

  private static Options parse(final HaMode haMode, final Properties properties,
      final Options paramOptions) {
    final Options options = paramOptions != null ? paramOptions : new Options();

    try {
      //Option object is already initialized to default values.
      //loop on properties,
      // - check DefaultOption to check that property value correspond to type (and range)
      // - set values
      for (final String key : properties.stringPropertyNames()) {
        final String propertyValue = properties.getProperty(key);
        final DefaultOptions o = OptionUtils.OPTIONS_MAP.get(key);
        if (o != null && propertyValue != null) {
          final Field field = Options.class.getField(o.optionName);
          if (o.objType.equals(String.class)) {
            field.set(options, propertyValue);
          } else if (o.objType.equals(Boolean.class)) {
            switch (propertyValue.toLowerCase()) {
              case "":
              case "1":
              case "true":
                field.set(options, Boolean.TRUE);
                break;

              case "0":
              case "false":
                field.set(options, Boolean.FALSE);
                break;

              default:
                throw new IllegalArgumentException("Optional parameter " + o.optionName
                    + " must be boolean (true/false or 0/1) was \"" + propertyValue + "\"");
            }
          } else if (o.objType.equals(Integer.class)) {
            try {
              final Integer value = Integer.parseInt(propertyValue);
              assert o.minValue != null;
              assert o.maxValue != null;
              if (value.compareTo((Integer) o.minValue) < 0
                  || value.compareTo((Integer) o.maxValue) > 0) {
                throw new IllegalArgumentException("Optional parameter " + o.optionName
                    + " must be greater or equal to " + o.minValue
                    + (((Integer) o.maxValue != Integer.MAX_VALUE) ? " and smaller than "
                    + o.maxValue : " ")
                    + ", was \"" + propertyValue + "\"");
              }
              field.set(options, value);
            } catch (NumberFormatException n) {
              throw new IllegalArgumentException("Optional parameter " + o.optionName
                  + " must be Integer, was \"" + propertyValue + "\"");
            }
          } else if (o.objType.equals(Long.class)) {
            try {
              final Long value = Long.parseLong(propertyValue);
              assert o.minValue != null;
              assert o.maxValue != null;
              if (value.compareTo((Long) o.minValue) < 0
                  || value.compareTo((Long) o.maxValue) > 0) {
                throw new IllegalArgumentException("Optional parameter " + o.optionName
                    + " must be greater or equal to " + o.minValue
                    + (((Long) o.maxValue != Long.MAX_VALUE) ? " and smaller than " + o.maxValue
                    : " ")
                    + ", was \"" + propertyValue + "\"");
              }
              field.set(options, value);
            } catch (NumberFormatException n) {
              throw new IllegalArgumentException("Optional parameter " + o.optionName
                  + " must be Long, was \"" + propertyValue + "\"");
            }
          }
        }
      }

      //special case : field with multiple default according to HA_MODE
      if (options.socketTimeout == null) {
        options.socketTimeout = ((Integer[]) SOCKET_TIMEOUT.defaultValue)[haMode.ordinal()];
      }

    } catch (NoSuchFieldException | IllegalAccessException n) {
      n.printStackTrace();
    } catch (SecurityException s) {
      //only for jws, so never thrown
      throw new IllegalArgumentException("Security too restrictive : " + s.getMessage());
    }

    return options;
  }

  /**
   * Option initialisation end : set option value to a coherent state.
   *
   * @param options options
   */
  public static void optionCoherenceValidation(final Options options) {

    //disable use server prepare id using client rewrite
    if (options.rewriteBatchedStatements) {
      options.useServerPrepStmts = false;
    }

    //pipe cannot use read and write socket simultaneously
    if (options.pipe != null) {
      options.useBatchMultiSend = false;
      options.usePipelineAuth = false;
    }

    //if min pool size default to maximum pool size if not set
    if (options.pool) {
      options.minPoolSize = options.minPoolSize == null ? options.maxPoolSize
          : Math.min(options.minPoolSize, options.maxPoolSize);
    }

  }

  /**
   * Generate parameter String equivalent to options.
   *
   * @param options options
   * @param haMode  high availability Mode
   * @param sb      String builder
   */
  public static void propertyString(final Options options, final HaMode haMode,
      final StringBuilder sb) {
    try {
      boolean first = true;
      for (DefaultOptions o : DefaultOptions.values()) {
        final Object value = Options.class.getField(o.optionName).get(options);

        if (value != null && !value.equals(o.defaultValue)) {
          if (first) {
            first = false;
            sb.append('?');
          } else {
            sb.append('&');
          }
          sb.append(o.optionName)
              .append('=');
          if (o.objType.equals(String.class)) {
            sb.append((String) value);
          } else if (o.objType.equals(Boolean.class)) {
            sb.append(((Boolean) value).toString());
          } else if (o.objType.equals(Integer.class)) {
            sb.append((Integer) value);
          } else if (o.objType.equals(Long.class)) {
            sb.append((Long) value);
          }
        }
      }
    } catch (NoSuchFieldException | IllegalAccessException n) {
      n.printStackTrace();
    }
  }

  public String getOptionName() {
    return optionName;
  }

  public String getDescription() {
    return description;
  }

  public boolean isRequired() {
    return required;
  }
}
