/*-
 * Copyright (c) 2018, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 *  https://oss.oracle.com/licenses/upl/
 */

import type { Config } from "./config";

/**
 * NoSQL database allows you to store arbitrary precision decimal numbers
 * in tables by using database type <em>Number</em>, which supports all
 * numbers represented by Java BigDecimal class.  By default, the driver
 * represents them as Javascript numbers.  However this has some limitations:
 * <ul>
 * <li>The number of significant digits is limited to what can be represented
 * by Javascript number type, which is approximately 15 decimal
 * digits, so the values with higher precision cannot be reresented exactly
 * and will be rounded.</li>
 * <li>The range is limited to what can be represented by Javascript numbers,
 * which is upproximately up to +-1e308.</li>
 * <li>Javascript numbers are stored in binary and not decimal format, which
 * means that some decimal values cannot be represented exactly and are
 * subject to rounding errors when performing decimal calculations.</li>
 * </ul>
 * <p>
 * Because Javascript and Node.js do not currently have a standard way to
 * represent arbitrary precision decimal numbers, the driver allows you to use
 * a 3rd party number library of your choice.  Typically the number libraries
 * represent numbers as objects of certain prototype or class and have methods
 * to perform arithmetic and other operations.  If you enable this feature,
 * the driver will represent the column values of datatype <em>Number</em> as
 * objects of this type from the number library.  You can pass these objects
 * as record fields for put operations, as key fields for get operations and
 * they will also be returned as part of get and query results wherever the
 * column value or expression result is of datatype <em>Number</em>.  Then
 * your application can use methods from the number library to perform further
 * numeric operations on number objects or convert them to suitable display
 * representation.
 * <p>
 * In most cases you only need the following:
 * <ol>
 * <li>Install the 3rd party number library of your choice (typically from
 * NPM) globally or as a dependency of your application (note that the driver
 * does not automatically install number libraries)</li>
 * <li>In the initial {@link Config} used to create {@link NoSQLClient},
 * specify <em>dbNumber</em> property as <em>dbNumber: 'module_name'</em>
 * where module_name is the module name of the number library</li>
 * </ol>
 * See examples below.
 * <p>
 * The above is sufficient for most cases when using default settings and
 * for number libraries that provide standard method names for common
 * operations.  The driver has been tested with the following number
 * libraries:
 * <ul>
 * <li>{@link https://github.com/MikeMcl/decimal.js | decimal.js}</li>
 * <li>{@link https://github.com/MikeMcl/decimal.js-light | decimal.js-light}</li>
 * <li>{@link https://github.com/MikeMcl/bignumber.js | bignumber.js}</li>
 * <li>{@link https://github.com/MikeMcl/big.js | big.js}</li>
 * </ul>
 * <p>
 * These libraries should work out of the box with simple steps as described
 * above.  However you may use any number library of your choice as long as
 * it uses objects to represent arbitrary precision decimal numbers, provides
 * constructor function to create these objects and supports some common
 * operations.  Below we describe what the driver needs to know from the
 * number library and whether it will work out of the box as mentioned
 * above or if it may require additional configuration.
 * <p>
 * The driver needs to know the following:
 * <ol>
 * <li> Constructor function.  The constructor should be able to create
 * the number object from its string representation.  Typically the
 * number libraries export the constructor as a sole module export, so that
 * the result of <em>require('module_name')</em> is the
 * constructor.  In this case no additional configuration is required.
 * </li>
 * <li> Conversion to string.  Typically this is the instance method
 * <em>toString</em> (overriden from <em>Object</em> class), in which case no
 * additional configuration is required.</li>
 * <li> Operations for comparison, addition, subtraction, multiplication and
 * division, as may be needed by the driver for client-side query processing.
 * These are typically static (properties of the constructor itself)
 * and/or instance methods (properties of constructor's prototype), with the
 * static methods taking 2 arguments and instance
 * methods taking one argument.  The driver will look for common method names
 * (instance or static) such as "add", "plus" for addition, "subtract", "sub",
 * "minus" for subtraction, etc.  See {@link NumberLibMethods} for complete
 * list of names.  If the number library of your choice uses any of the common
 * names for each operation listed above, no additional configuration is
 * required.</li>
 * <li> Precision and rounding mode.  These are optional and if not specified,
 * default values will be used.  On the server side, NoSQL database uses Java
 * BigDecimal to do calculations for Number datatype during query processing.
 * The result of each operation is rounded accoring to Java MathContext
 * settings, which consist of precision (number of significant digits) and
 * rounding mode (how to round the result to precision), so these settings
 * need to be provided to the server for query processing.  You may choose to
 * provide them explicitly in configuration (see {@link NumberLibConfig}) or
 * let the driver figure them out from the number library settings using some
 * common property names (see {@link NumberLibPrecision} and
 * {@link NumberLibRoundingMode}).  If not found, default precision of 20 and
 * rounding mode <em>ROUND_HALF_UP</em> will be used.</li>
 * </ol>
 * <p>
 * Note that configuration for this feature is always specified as
 * <em>dbNumber</em> property of {@link Config} and thus can be provided
 * either as part of {@link Config} Javascript object or in JSON file
 * containing {@link Config}.
 * <p>
 * <em>dbNumber</em> property can be one of the following:
 * <ul>
 * <li>String specifying number library module name as discussed above.</li>
 * <li>Constructor function (not available if using JSON file).  You may
 * specify constructor function explicitly.  This is useful when you want to
 * set custom settings for the constructor (to customize created number
 * instances) before passing it to the driver instead of using default number
 * library settings.  Also see {@link NumberLibConfig#Constructor}.</li>
 * <li>{@link NumberLibConfig} object if more complex configuration is
 * desired.
 * </li>
 * </ul>
 */
export type DBNumberConfig = string|DBNumberConstructor|NumberLibConfig;

/**
 * Constructor signature for 3rd party library number type.
 */
export interface DBNumberConstructor {
    new (val: string): unknown;
}

/**
 * This object can be set as {@link Config#dbNumber} property
 * to specify configuration for using 3rd party number libary when additional
 * information is required, as described in {@link DBNumberConfig}.
 * <p>
 * The only required property is {@link Constructor}. As menitoned,
 * <em>static</em> and <em>instance</em> properties are only needed when
 * the number library is not using common method names, or if you need to
 * customize implementation, see {@link NumberLibMethods}.
 * <p>
 * An important note about precision and rounding:
 * <p>
 * Different number libraries have different options to round the results
 * of arithmetic and other operations.  For example, some libraries like
 * <em>decimal.js</em> and <em>decimal.js-light</em> round to precision, which
 * is total number of significant digits in the number.  These libraries
 * typically have a precision value specified as configuration setting in the
 * constructor.  Other libraries, like <em>bignumber.js</em> and
 * <em>big.js</em> round to scale, or decimal places, which is number of
 * digits after decimal point.  These libraries typically have decimal
 * places value as configuration setting in the constructor.  In addition,
 * some libraries, like <em>decimal.js</em> and <em>bignumber.js</em> do
 * automatic rounding of some arithmetic operations and other libraries like
 * <em>big.js</em> and <em>decimal.js-light</em> allow only manual rounding
 * via methods such as <em>round</em>.
 * <p>
 * The above may have implication on some query results.  On the server side,
 * NoSQL database uses Java BigDecimal and rounds each arithmetic operation
 * according to precision and rounding mode settings of MathContext, as
 * mentioned in {@link DBNumberConfig}.  The driver, on the other hand, does
 * not perform rounding during query processing, other than the rounding
 * performed automatically by some number libraries as mentioned above.  This
 * means that for queries that use arithmetic expressions and some aggregate
 * functions, the query results may slightly differ when using different
 * number libraries.  From the libraries tested, <em>decimal.js</em> has the
 * closest matching behavior to query processing on the server side because
 * it automatically rounds arithmetic operations to precision using rounding
 * mode setting.
 * <p>
 * You may also use the rounding methods from the number library to perform
 * rounding after receiving query results.  This may be useful when using
 * number libraries that do not perform automatic rounding.  In addition,
 * number libraries may have other settings related to rounding.  See
 * documentation for the number library of your choice.  If required, you can
 * clone and customize the constructor and pass it as
 * {@link Constructor} property.  You may also customize
 * arithmetic operations further using <em>static</em> and <em>instance</em>
 * properties, see {@link NumberLibMethods}, in case non-standard behavior is
 * needed.
 * <p>
 * The driver need to know precision and rounding mode in order to create
 * MathContext for server-side query processing.  It will try to infer these
 * settings from the constructor as described in {@link NumberLibPrecision}
 * and {@link NumberLibRoundingMode}.  For libraries that round to scale,
 * such as <em>bignumber.js</em> and <em>big.js</em>, it is not possible
 * to infer precision from constructor, so you may need to set
 * {@link precision} property, otherwise the driver will use the default
 * value.  For {@link precision} and {@link roundingMode} properties, if
 * set, the driver will use their values instead of inferring them from
 * the constructor.
 * <p>
 * Note that setting properties {@link precision} and {@link roundingMode}
 * only affects how rounding is done on the server side.  The driver will not
 * set any properties of the constructor.  If the number library does
 * automatic rounding, it will do so based on the constructor as passed.  If
 * the number library supports precision and rounding mode settings and does
 * automatic rounding/trucation, you can ensure that the same settings are
 * used by the server and the client by letting the driver infer them from
 * the constructor (by using {@link getPrecision} and
 * {@link getRoundingMode} properties if necessary).  E.g. for libraries
 * <em>decimal.js</em> and <em>decimal.js-light</em> the driver will
 * automatically infer and use precision and rounding mode settings.
 */
export interface NumberLibConfig {
    /**
     * String representing number library
     * module name, if the sole export is the constructor, or constructor
     * function.  Constructor function must be able to create instances from
     * number's string representation or from Javascript number (although
     * other options may also be provided by the number library). Note the
     * upper case to disambiguate from Object's <em>constructor</em> property.
     */
    Constructor: string|DBNumberConstructor;

    /**
     * In rare cases when the number library export
     * is not the constructor, you may specify the module name.  If this
     * property is set and {@link Constructor} property is specified as a
     * string, then the {@link Constructor} property specifies property name
     * of the <em>module.exports</em> object instead of the module name.
     */
    module?: string;

    /**
     * Static method mappings, that is
     * methods that are properties of the constructor itself.  If not set, or
     * for any required method not present in the mapping, the driver will try
     * to infer it from constructor.  See {@link NumberLibMethods}.
     */
    static?: NumberLibMethods;

    /**
     * Instance method mappings, that is
     * methods that are properties of the constructor's prototype.  If not
     * set, or for any required method not present in the mapping, the driver
     * will try to infer it from constructor's prototype.  See
     * {@link NumberLibMethods}.
     */
    instance?: NumberLibMethods;

    /**
     * Precision to use for rounding of
     * server-side query calculations on datatype <em>Number</em>.  If not
     * set, the driver will try to infer the value from constructor (see
     * {@link NumberLibPrecision}).  If cannot be inferred, default precision
     * of 20 is used.
     */
    precision?: number;

    /**
     * Rounding mode to use for rounding of
     * server-side query calculations on datatype <em>Number</em>.  See
     * {@link RoundingModesMap} for details on supported roundings modes.
     * This property can be specified either as rounding mode name string,
     * such as 'DOWN', 'UP', 'HALF_DOWN', etc. (with or without
     * <em>ROUND_</em> prefix) or as number library-specific constant value,
     * as long as the driver can find a mapping between rounding mode names
     * and their values in the number library (see {@link RoundingModesMap}).
     * If not set, the driver will try to infer the value from constructor
     * (see {@link NumberLibRoundingMode}). If cannot be inferred, default
     * value of <em>ROUND_HALF_UP</em> is used.
     */
    roundingMode?: unknown;

    /**
     * Specifies how to get
     * precision value from constructor.  See {@link NumberLibPrecision}.  If
     * not set, the driver will use {@link NumberLibConfig#precision}.
     */
    getPrecision?: NumberLibPrecision;

    /**
     * Specifies how to get
     * rounding mode from constructor.  See {@link NumberLibRoundingMode}. If
     * not set, the driver will use {@link NumberLibConfig#roundingMode}.
     */
    getRoundingMode?: NumberLibRoundingMode;

    /**
     * Specifies mapping between
     * rounding mode names and their constant values in the number library.
     * If not set, the driver will try to infer the mapping from constuctor or
     * module (see {@link RoundingModesMap}).
     */
    RoundingModes?: RoundingModesMap | string;
}

/**
 * Object that specifies method mappings for methods from the number library
 * that are required by the driver.  Set this as
 * {@link NumberLibConfig#static} property for mappings to static methods
 * (properties of the constructor) and to {@link NumberLibConfig#instance}
 * property for mappings to instance methods (properties of the constructor's
 * prototype).  You may also set both of the above to provide static mappings
 * for some methods and instance mappings for others.  For any required
 * method, one mapping is sufficient (otherwise the driver will prefer
 * instance over static).
 * <p>
 * Unless otherwise specified, the required operations are binary (i.e.
 * they operate on two number objects).  This means that static methods take 2
 * parameters and instance methods have <em>this</em> context as the 1st
 * number object and pass 2nd number as one parameter.
 * <p>
 * You can specify each method mapping as either string or a function.  If
 * string, the driver will use constructor's or prototype's property by that
 * name for static or instance method respectively.  You may also provide a
 * function for either static or instance method (as long as it follows the
 * rules for arguments and <em>this</em> context described in previous
 * paragraph).  This allows you to customize implementation if needed.
 * <p>
 * <p>
 * The following assumptions can be made about the argument types:
 * <ul>
 * <li>For static methods, you may assume that the first argument is the
 * number object but second argument may be either number object, string or
 * Javascript number.  This should fit the signatures of static methods
 * from number libraries, which typically assume that both arguments may be
 * either number object, string or Javascript number.</li>
 * <li>For instance methods, <em>this</em> context is the number object but
 * the argument may be number object, string or Javascript number.
 * Typically number libraries make the same assumption.</li>
 * </ul>
 * All properties are optional.  For any method mapping property, if
 * not set, the driver will look for methods with list of names as specified.
 * Unless otherwise specified, the driver will look first for each
 * instance method on that list and then for each static.  If the mapping is
 * not specified and none of the methods on the list could be found,
 * {@link NoSQLArgumentError} will result.
 * <p>
 * The above means that you only need to set mappings for methods with
 * non-typical names that would not be found on corresponding name list or if
 * you with to customize thier implementation.  In particular, for 4 tested
 * number libraries mentioned in {@link DBNumberConfig}, no mappings are
 * required.
 */
export interface NumberLibMethods {
    /**
     * Compare two numbers n1 and n2.
     * Return value of this function should be > 0 if n1 > n2, = 0 if n1 = n2
     * and < 0 if n1 < n2.  If not set, the driver will look for methods named
     * <em>comparedTo</em>, <em>compareTo</em>, <em>cmp</em> and
     * <em>compare</em> in that order
     */
    compare?: string|((n1: any, n2: any) => number);

    /**
     * Determine if two numbers are
     * equal.  Should return true/false.  This property may be useful for
     * equality checks during query processing which is usually faster than
     * numeric comparison via {@link compare}.  If not set,
     * the driver will look for instance methods <em>equals</em>,
     * <em>isEqualTo</em> and <em>eq</em> in that order. If not found, the
     * driver will use {@link compare} for equality checks.
     */
    valuesEqual?: string|((n1: any, n2: any) => boolean);

    /**
     * Add two numbers n1 and n2, return number
     * object with value of n1 + n2.  If not set, the driver will look for
     * methods named <em>plus</em> and <em>add</em> in that order.
     */
    add?: string|((n1: any, n2: any) => any);

    /**
     * Subtract number n2 from n1, return
     * number object with value of n1 - n2.  If not set, the driver will look
     * for methods named <em>minus</em>, <em>sub</em> and <em>subtract</em> in
     * that order.
     */
    subtract?: string|((n1: any, n2: any) => any);

    /**
     * Multiply numbers n1 and n2, return
     * number object with value n1 * n2.  If not set, the driver will look for
     * methods named <em>times</em>, <em>multipliedBy</em>, <em>multiply</em>
     * and <em>mul</em> in that order.
     */
    multiply?: string|((n1: any, n2: any) => any);

    /**
     * Divide number n1 by n2, return number
     * object with value of n1 / n2.  If not set, the driver will look for
     * methods named <em>dividedBy</em>, <em>divide</em> and <em>div</em> in
     * that order.
     */
    divide?: string|((n1: any, n2: any) => any);
}

/**
 * Value of {@link NumberLibConfig#getPrecision} property.
 * This property may be:
 * <ul>
 * <li>String, specifying the property name of the constructor which contains
 * precision. Nested paths (with '.'s) are allowed.</li>
 * <li>Function, specifying how to get precision value from the constructor.
 * The function takes constructor as sole parameter and returns precision
 * value as Javascript number.</li>
 * </ul>
 * If {@link NumberLibConfig#getPrecision} is not set, the driver will use
 * value {@link NumberLibConfig#precision} property.  If that is also not set,
 * the driver will check constructor properties <em>precision</em> and
 * <em>PRECISION</em>. If not found, default precision of 20 is assumed.
 */
export type NumberLibPrecision =
    string | ((cons: DBNumberConstructor) => number);

/**
 * Value of of {@link NumberLibConfig#getRoundingMode} property.
 * <p>
 * This property may be:
 * <ul>
 * <li>String, specifying the property name of the constructor which contains
 * rounding mode.  Nested paths (with '.'s) are allowed.</li>
 * <li>Function, specifying how to get rounding mode value from the
 * constructor. The function takes constructor as sole parameter and returns
 * rounding mode value as one of the constant values used by the number
 * library, which is typically numeric, but does not have to be so.</li>
 * </ul>
 * If {@link NumberLibConfig#getRoundingMode} is not set, the driver will use
 * value {@link NumberLibConfig#roundingMode} property.  If that is also not
 * set, the driver will check constructor properties <em>rounding</em>,
 * <em>roundingMode</em>, <em>ROUNDING_MODE</em> and <em>RM</em> in that
 * order.  If not found, default rounding mode ROUND_HALF_UP is assumed.
 * <p>
 * Also see {@link RoundingModesMap} for explanation on rounding modes.
 */
export type NumberLibRoundingMode =
    string | ((cons: DBNumberConstructor) => unknown);

/**
 * Specifies constant values used for rounding modes in the number library.
 * <p>
 * Rounding mode specifies rounding behavior of numerical and/or rounding
 * operations in the number library, specifically indicating how the least
 * significant digit of the result should be calculated.  There are several
 * well known rounding modes.  NoSQL database supports rounding modes
 * used by Java BigDecimal and specified as
 * {@link https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/math/RoundingMode.html | RoundingMode}
 * enumeration. Also see documentation for the number library of your choice
 * on supported rounding modes.
 * <p>
 * This object may be specified as {@link NumberLibConfig#RoundingModes}
 * property and it helps the driver determine the rounding mode constant
 * values (which are usually Javascript numbers) for different rounding modes
 * used by the number library.  The driver uses it to infer which rounding
 * mode is used by the library or set as {@link NumberLibConfig#roundingMode}
 * property, see {@link NumberLibRoundingMode}.
 * <p>
 * In most cases, it is not necessary to set
 * {@link NumberLibConfig#RoundingModes} and thus use this object.
 * Specifically, this property is not necessary if:
 * <ul>
 * <li>You wish to keep default rounding mode <em>ROUND_HALF_UP</em></li>
 * <li>You set {@link NumberLibConfig#roundingMode} property as a name string
 * of the rounding mode (such as 'ROUND_UP', 'ROUND_DOWN', etc.), see
 * {@link NumberLibRoundingMode}.</li>
 * <li>The driver can infer rounding mode constants from the constructor or
 * its properties, see below.</li>
 * </ul>
 * If not set, the driver will try to infer rounding mode constants by:
 * <ol>
 * <li>Looking for an object containing these constants by checking properties
 * <em>RoundingModes</em> and <em>RoundingMode</em> of the constructor and of
 * the module (if specified).  If not found assume the constructor itself
 * contains the constants.</li>
 * <li>Looking for properties with rounding mode names such as
 * <em>ROUND_UP</em>, <em>ROUND_DOWN</em>, etc. in the object determined
 * above, with and without <em>ROUND_</em> prefix.</li>
 * </ol>
 * <p>
 * Note that you may also set {@link NumberLibConfig#RoundingModes} to string
 * in which case it will be used as a property name or path ('.'s allowed) of
 * constructor or module to the object containing rounding mode constants
 * instead of checking candidate names as described above.
 * <p>
 * The properties listed correspond to each rounding mode supported
 * by the driver.  Note that some number libraries, such as <em>big.js</em>,
 * support only a subset of these.  In this case the driver will infer all it
 * can.  If neither {@link NumberLibConfig#RoundingModes} nor
 * {@link NumberLibConfig#roundingMode} properties are set and rounding mode
 * constants cannot be inferred, the driver will assume default rounding mode
 * <em>ROUND_HALF_UP</em>.
 * <p>
 * Note that some number libraries may support rounding modes not listed here
 * and not supported by Java BigDecimal, such as <em>ROUND_HALF_CEIL</em> and
 * <em>ROUND_HALF_FLOOR</em>.  Using of these rounding modes will result in
 * {@link NoSQLArgumentError}.
 * <p>
 * Although numeric rounding mode values are usually used, if the library
 * of your choice using other type for rounding mode constants you may specify
 * them as well.  Any value is allowed except <em>undefined</em> and
 * <em>null</em>.
 */
export interface RoundingModesMap {
    /**
     * Constant value for ROUND_UP rounding mode.
     */
    UP?: unknown;
    
    /**
     * Constant value for ROUND_DOWN rounding mode.
     */
    DOWN?: unknown;
    
    /**
     * Constant value for ROUND_CEILING rounding mode.
     */
    CEILING?: unknown;
    
    /**
     * Constant value for ROUND_FLOOR rounding mode.
     */
    FLOOR?: unknown;
    
    /**
     * Constant value for ROUND_HALF_UP rounding mode.
     */
    HALF_UP?: unknown;
    
    /**
     * Constant value for ROUND_HALF_DOWN rounding mode.
     */
    HALF_DOWN?: unknown;
    
    /**
     * Constant value for ROUND_HALF_EVEN rounding mode.
     */
    HALF_EVEN?: unknown;
}
