//################################################################################################
/** @file Long Number mixin for ish.js
 * @mixin ish.type.is.numeric.large
 * @author Nick Campbell
 * @license MIT
 * @copyright 2006-2023, Nick Campbell
 */ //############################################################################################
/*global module, define */                                      //# Enable Node globals for JSHint
/*jshint maxcomplexity:9 */                                     //# Enable max complexity warnings for JSHint
(function () {
    'use strict'; //<MIXIN>

    function init(core) {
        //################################################################################################
        /** Collection of long number functionality (range, gle and precision).
         * @namespace ish.type.is.numeric.large
         * @ignore
         */ //############################################################################################
        core.oop.partial(core.type.is, {
            numeric: {
                //#########
                /** Determines if the passed value is within the passed range.
                 * @$note <code>(sNumber >= sMin && sNumber <= sMax)</code> would work for most numeric checks, but in the case of huge/tiny numbers (such as <code>NUMERIC(x,y)</code> in Oracle), the numbers would be too large/small to be represented in Javascript's numeric variables.
                 * @function ish.type.is.numeric.large.range
                 * @param {string} sNumber Value representing the number to compare.
                 * @param {string} sMin Value representing the minimum number.
                 * @param {string} sMax Value representing the maximum number.
                 * @returns {boolean} Value representing if the passed value is within the passed range.
                 */ //#####
                //# Last Updated: February 21, 2006
                range: function (sNumber, sMin, sMax) {
                    return (
                        //#### If the passed sNumber is greater then or equal to the passed sMin
                        core.type.is.numeric.cmp(sNumber, sMin) >= 0 &&
                        //#### If the passed sNumber is less then or equal to the passed sMax
                        core.type.is.numeric.cmp(sNumber, sMax) <= 0
                    );
                }, //# type.is.numeric.large.range


                //#########
                /** Determines if the passed value is greater then, less then or equal to the passed relative value.
                 * @function ish.type.is.numeric.large.cmp
                 * @param {string} sNumber Value representing the number to compare.
                 * @param {string} sRelativeTo Value representing the relative number to compare to.
                 * @returns {integer} Value representing if the passed value is greater then (<code>1</code>), less then (<code>-1</code>) or equal to (<code>0</code>) the passed relative value with <code>undefined</code> indicating one of the passed values was non-numeric.
                 */ //#####
                //# Last Updated: February 21, 2006
                cmp: function (sNumber, sRelativeTo) {
                    //#### Ensure the passed sNumber and sRelativeTo are strings
                    sNumber += "";
                    sRelativeTo += "";

                        //#### Define and init the required local vars
                    var iNumberNumericPrecision = core.type.is.numeric.precision(sNumber);
                    var iRangeNumericPrecision = core.type.is.numeric.precision(sRelativeTo);
                    var iReturn;
                    var bNumberIsPositive = (sNumber.indexOf("-") !== 0);
                    var bRangeIsPositive = (sRelativeTo.indexOf("-") !== 0);

                        //#### If the passed sNumber or sRelativeTo were non-numeric, set our iReturn value to 0
                    if (iNumberNumericPrecision === -1 || iRangeNumericPrecision === -1) {
                        //iReturn = undefined;
                    }
                        //#### Else if the signs of the passed sNumber and sRelativeTo do not match
                    else if (bNumberIsPositive != bRangeIsPositive) {
                            //#### If the bNumberIsPositive, then the sRelativeTo is negetive, so set our iReturn value to 1 (as sNumber is greater then the sRelativeTo)
                        if (bNumberIsPositive) {
                            iReturn = 1;
                        }
                            //#### Else the sNumber is negetive and the bRangeIsPositive, so set our iReturn value to -1 (as sNumber is less then the sRelativeTo)
                        else {
                            iReturn = -1;
                        }
                    }
                        //#### Else the signs of the passed sNumber and sRelativeTo match
                    else {
                            //#### If the above-determined .NumericPrecision's are specifying numbers of less then 1 billion
                        if (iRangeNumericPrecision < 10 && iNumberNumericPrecision < 10) {
                                //#### Define and init the additionally required vars
                                //####     NOTE: We know that both sNumber and sRelativeTo are numeric as non-numeric value are caught by .NumericPrecision above
                            var fNumber = parseFloat(sNumber);
                            var fRange = parseFloat(sRelativeTo);

                                //#### If the sNumber and sRelativeTo are equal, set our iReturn value to 0
                            if (fNumber == fRange) {
                                iReturn = 0;
                            }
                                //#### Else if the sNumber is greater then the sRelativeTo, set our iReturn value to 1
                            else if (fNumber > fRange) {
                                iReturn = 1;
                            }
                                //#### Else the fNumber is less then the sRelativeTo, so set our iReturn value to -1
                            else {
                                iReturn = -1;
                            }
                        }
                            //#### Else we're dealing with number ranges over 1 billion, so let's get creative...
                        else {
                                //#### If the iNumber('s)NumericPrecision is less then the iRange('s)NumericPrecision
                            if (iNumberNumericPrecision < iRangeNumericPrecision) {
                                    //#### If the bNumberIsPositive (and thanks to the check above the bRangeIs(also)Positive), return -1 (as the sNumber is a smaller positive number then the sRelativeTo, making it less)
                                if (bNumberIsPositive) {
                                    iReturn = -1;
                                }
                                    //#### Else the bNumberIs(not)Positive (and thanks to the check above the bRangeIs(also not)Positive), so return 1 (as the sNumber is a smaller negetive number then the sRelativeTo, making it greater)
                                else {
                                    iReturn = 1;
                                }
                            }
                                //#### Else if the iNumber('s)NumericPrecision is more then the iRange('s)NumericPrecision
                            else if (iNumberNumericPrecision > iRangeNumericPrecision) {
                                    //#### If the bNumberIsPositive (and thanks to the check above the bRangeIs(also)Positive), return 1 (as the sNumber is a bigger positive number then the sRelativeTo, making it greater)
                                if (bNumberIsPositive) {
                                    iReturn = 1;
                                }
                                    //#### Else the bNumberIs(not)Positive (and thanks to the check above the bRangeIs(also not)Positive), so return -1 (as the sNumber is a bigger negetive number then the sRelativeTo, making it less)
                                else {
                                    iReturn = -1;
                                }
                            }
                                //#### Else the iNumber('s)NumericPrecision is equal to the iRange('s)NumericPrecision, so additional checking is required
                            else {
                                    //#### Define and set the additionally required decimal point position variables
                                var iNumberDecimalPoint = sNumber.indexOf(".");
                                var iRangeDecimalPoint = sRelativeTo.indexOf(".");

                                    //#### If either/both of the decimal points were not found above, reset iNumberDecimalPoint/iRangeDecimalPoint to their respective .lengths (which logicially places the iRangeDecimalPoint at the end of the sCurrentRange, which is where it is located)
                                    //####    NOTE: Since this function only checks that the passed sNumber is within the passed range, the values "whole" -vs- "floating point" number distinction is ignored as for our purposes, it is unimportant.
                                if (iNumberDecimalPoint == -1) {
                                    iNumberDecimalPoint = sNumber.length;
                                }
                                if (iRangeDecimalPoint == -1) {
                                    iRangeDecimalPoint = sRelativeTo.length;
                                }

                                    //#### If the sNumber's decimal point is to the left of sRelativeTo's (making sNumber less then sRelativeTo), set our iReturn value to -1
                                if (iNumberDecimalPoint < iRangeDecimalPoint) {
                                    iReturn = -1;
                                }
                                    //#### Else if the sNumber's decimal point is to the right of sRelativeTo's (making sNumber greater then sRelativeTo), set our iReturn value to 1
                                else if (iNumberDecimalPoint > iRangeDecimalPoint) {
                                    iReturn = 1;
                                }
                                    //#### Else the sNumber's decimal point is in the same position as the sRelativeTo's decimal point
                                else {
                                        //#### Define and init the additionally required vars
                                    var iCurrentNumberNumber;
                                    var iCurrentRangeNumber;
                                    var i;

                                        //#### Default our iReturn value to 0 (as only > and < are checked in the loop below, so if the loop finishes without changing the iReturn value then the sNumber and sRelativeTo are equal)
                                    iReturn = 0;

                                        //#### Setup the value for i based on if the bNumberIsPositive (setting it to 0 if it is, or 1 if it isn't)
                                        //####    NOTE: This is done to skip over the leading "-" sign in negetive numbers (yea it's ugly, but it works!)
                                        //####    NOTE: Since at this point we know that signs of sNumber and sRelativeTo match, we only need to check bNumberIsPositive's value
                                    i = (bNumberIsPositive) ? (0) : (1);

                                        //#### Traverse the sNumber/sRelativeTo strings from front to back (based on the above determined starting position)
                                        //####     NOTE: Since everything is is the same position and the same precision, we know that sNumber's .lenght is equal to sRelativeTo's
                                    for (i; i < sNumber.length; i++) {
                                            //#### As long as we're not looking at the decimal point
                                        if (i != iNumberDecimalPoint) {
                                                //#### Determine the iCurrentNumberNumber and iCurrentRangeNumber for this loop
                                            iCurrentNumberNumber = parseInt(sNumber[i]);
                                            iCurrentRangeNumber = parseInt(sRelativeTo[i]);

                                                //#### If the iCurrentNumberNumber is less then the iCurrentRangeNumber
                                            if (iCurrentNumberNumber < iCurrentRangeNumber) {
                                                    //#### sNumber is less then sRelativeTo, so set our iReturn value to -1 and fall from the loop
                                                iReturn = -1;
                                                break;
                                            }
                                                //#### Else if the iCurrentNumberNumber is greater then the iCurrentRangeNumber
                                            if (iCurrentNumberNumber > iCurrentRangeNumber) {
                                                    //#### sNumber is greater then sRelativeTo, so set our iReturn value to 1 and fall from the loop
                                                iReturn = 1;
                                                break;
                                            }
                                        }
                                    } //# for
                                }
                            }
                        }
                    }

                        //#### Return the above determined iReturn value to the caller
                    return iReturn;
                }, //# ish.type.is.numeric.large.cmp


                //#########
                /** Determines the numeric precision of the passed value.
                 * @function ish.type.is.numeric.large.precision
                 * @param {string} sNumber Value representing the number to compare.
                 * @returns {integer} Value representing the numeric precision of the passed value.
                 */ //#####
                //# Last Updated: April 19, 2006
                precision: function (sNumber) {
                    var sCurrentChar, i, bStartCounting,
                        sValue = core.type.str.mk(sNumber).trim(),
                        iReturnVal = (/^(-)?[0-9.,]{1,}$/.test(sValue) ? 0 : -1)
                    ;

                    //# If the sValue holds only numeric characters
                    if (iReturnVal === 0) {
                        //#### Traverse the .length of the passed sValue, collecting the sCurrentChar as we go
                        for (i = 0; i < sValue.length; i++) {
                            sCurrentChar = sValue[i]; //# .substr(i, 1)

                            //#### If the sCurrentChar is.numeric
                            if (core.type.is.numeric(sCurrentChar)) {
                                //#### If we are supposed to bStartCounting, inc our iReturnVal
                                //####    NOTE: This is done so we ignore leading 0's (trailing 0's are still counted)
                                bStartCounting = (sCurrentChar !== '0');
                                iReturnVal += (bStartCounting ? 1 : 0);
                            }
                        }
                    }

                    return iReturnVal;
                } //# ish.type.is.numeric.large.precision
            }
        }); //# core.type.num

        //# .fire the plugin's loaded event
        core.io.event.fire("ish.type.is.numeric.large");

        //# Return core to allow for chaining
        return core;
    } //# init


    //# If we are running server-side
    //#     NOTE: Compliant with UMD, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
    //#     NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
    if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
        module.exports = init;
    }
    //# Else if we are running in an .amd environment, register as an anonymous module
    else if (typeof define === 'function' && define.amd) {
        define([], init);
    }
    //# Else we are running in the browser, so we need to setup the _document-based features
    else {
        return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish);
    }

    //</MIXIN>
}());