/**
* Script contains all functions for dealing with numbers.
* Includes localized formatting as well as validation methods.
*
* DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
*/

/**
 * Creates a class called NumberUtils.
 */
var NumberUtils = {
    version : "1.0.0",
    defaultEmptyOK: false
};

/**
 * Validates all required objects are defined for the current locale.
 * This should be called first in every script defined in this file that is locale aware.
 *
 * @return true if specified localization properties are defined; false otherwise
 */
NumberUtils.validateObjects = function() {

    if (!LocalizationUtils) {
        alert("Required LocalizationUtils class no defined.");
        return false;
    }

    if (!LocalizationUtils.numericDecimalCharacter) {
        alert("Required variable not defined. Variable: [LocalizationUtils.numericDecimalCharacter]");
        return false;
    }

    if (!LocalizationUtils.numericFormatCharacter) {
        alert("Required variable not defined. Variable: [LocalizationUtils.numericFormatCharacter]");
        return false;
    }

    if (!LocalizationUtils.numericRegExpNumberFormat) {
        alert("Required variable not defined. Variable: [LocalizationUtils.numericRegExpNumberFormat]");
        return false;
    }

    if (!LocalizationUtils.numericCurrencyFormat) {
        alert("Required variable not defined. Variable: [LocalizationUtils.numericCurrencyFormat]");
        return false;
    }

    return true;
};

/**
 * Formats a number for display as currency. Rounds number to two decimal places
 * using standard rounding rules. Forces a maximum of 2 decimal places.
 *
 * Example: 2342.2 formatted to $2342.20 for en_US.
 *
 *
 * @param strValue number to format.
 * @return formatted number as a currency.
 */
NumberUtils.formatCurrency = function(strValue) {

    if (!NumberUtils.validateObjects()) {
        return strValue;
    }

    var numberValue = NumberUtils.formatNumberWithScale(strValue, 2);

    var replacementString = LocalizationUtils.numericCurrencyFormat;

    return replacementString.replace('${number}', numberValue);

};

/**
 * Formats a number. Rounds number to two decimal places
 * using standard rounding rules. Forces a maximum of 2 decimal places.
 *
 * Example: 2342.2 formatted to 2342.20.
 *
 *
 * @param strValue number to format.
 * @return formatted number.
 */
NumberUtils.formatNumber = function(strValue) {

    if (!NumberUtils.validateObjects()) {
        return strValue;
    }

    return NumberUtils.formatNumberWithScale(strValue, 2);

};

/**
 * Formats a number to the specified scale. Rounds number to two decimal places
 * using standard rounding rules. Forces a maximum of 2 decimal places.
 *
 * Example: 2342.2 formatted to 2342.20 if a scale of 2 is passed.
 *
 * @param strValue number to format.
 * @param scale scale to format number to.
 * @return formatted number.
 */
NumberUtils.formatNumberWithScale = function(strValue, scale) {

    var formattedNumber = strValue;

    if (!NumberUtils.validateObjects()) {
        return formattedNumber;
    }

    if (!NumberUtils.isFloat(formattedNumber)) {
       return formattedNumber;
    }

    formattedNumber = new String(NumberUtils.roundNumber(formattedNumber, scale));

    while (LocalizationUtils.numericRegExpNumberFormat.test(formattedNumber)) {
       formattedNumber = formattedNumber.replace(LocalizationUtils.numericRegExpNumberFormat, '$1' + LocalizationUtils.numericFormatCharacter + '$2');
    }

    return NumberUtils.padDecimal(formattedNumber, 2);

};

/**
 * Validates a given value is a float value.
 *
 * @param value value to check if float.
 * @return true if number is a float, false otherwise.
 */
NumberUtils.isFloat = function(value) {

    if (!NumberUtils.validateObjects()) {
        return false;
    }

    var decimalCharacter = LocalizationUtils.numericDecimalCharacter;
    var seenDecimalCharacter = false;

    if (value == null) return false;

    if (value == decimalCharacter) return false;

    if (value.length === undefined) {
        return false;
    }

    // Search through string's characters one by one
    // until we find a non-numeric character.
    // When we do, return false; if we don't, return true.
    for (var i = 0; i < value.length; i++)
    {
        // Check that current character is number.
        var c = value.charAt(i);

        if ((c == decimalCharacter) && !seenDecimalCharacter) {
            seenDecimalCharacter = true;
        } else if (!NumberUtils.isDigit(c)) {
            return false;
        }
    }

    // All characters are numbers.
    return true;
};

/**
 * Checks if the given character is a number.
 *
 * @param character character to check.
 * @return true if character is a number, false otherwise.
 */
NumberUtils.isDigit = function(character) {
    return ((character >= "0") && (character <= "9"));
};

/**
 * Rounds the given number using the scale provided.
 *
 * @param number number to round.
 * @param scale the scale to round the decimal too.
 * @return rounded number.
 */
NumberUtils.roundNumber = function(number, scale) {
    return Math.round(number*Math.pow(10,scale))/Math.pow(10,scale);
};

/**
 * Pads the decimal value with zero.
 *
 * @param number number to pad.
 * @param scale the scale to pad the number too.
 * @return padded number as a string.
 */
NumberUtils.padDecimal = function(number, scale) {

    var stringNumber = number.toString();

    if (!NumberUtils.validateObjects()) {
        return stringNumber;
    }

    // Add decimal character to end of number if it does not exist.
    if (stringNumber.lastIndexOf(LocalizationUtils.numericDecimalCharacter) == -1) {
        stringNumber = stringNumber + LocalizationUtils.numericDecimalCharacter;
    }

    while(stringNumber.indexOf(LocalizationUtils.numericDecimalCharacter) >= stringNumber.length-scale) {
        stringNumber = stringNumber + '0';
    }

    return stringNumber;

};

/**
 * Pads a number with 'count' number of leading zeros
 *
 * @param num to pad
 * @param count number of leading zeros to add
 * @return number padded with leading zeros
 */
NumberUtils.zeroPad = function(num,count) {
    var numZeropad = num + '';
    while(numZeropad.length < count) {
        numZeropad = "0" + numZeropad;
    }
    return numZeropad;
};


/**
 *
 * Returns true if all characters in string s are numbers.
 * Accepts non-signed integers only. Does not accept floating
 * point, exponential notation, etc.
 *
 * We don't use parseInt because that would accept a string
 * with trailing non-numeric characters.
 *
 * By default, returns defaultEmptyOK if s is empty.
 * There is an optional second argument called emptyOK.
 * emptyOK is used to override for a single function call
 * the default behavior which is specified globally by defaultEmptyOK.
 * If emptyOK is false (or any value other than true),
 * the function will return false if s is empty.
 * If emptyOK is true, the function will return true if s is empty.
 *
 * EXAMPLE FUNCTION CALL:     RESULT:
 * isInteger ("5")            true
 * isInteger ("")             defaultEmptyOK
 * isInteger ("-5")           false
 * isInteger ("", true)       true
 * isInteger ("", false)      false
 * isInteger ("5", false)     true
 *
 * @param s value to check.
 * @return true if integer; false otherwise.
 */
NumberUtils.isInteger = function(s) {
    var i;

    if (StringUtils.isEmpty(s))
       if (NumberUtils.isInteger.arguments.length == 1) return NumberUtils.defaultEmptyOK;
       else return (NumberUtils.isInteger.arguments[1]);

    // Search through string's characters one by one
    // until we find a non-numeric character.
    // When we do, return false; if we don't, return true.

    for (i = 0; i < s.length; i++)
    {
        // Check that current character is number.
        var c = s.charAt(i);

        if (!NumberUtils.isDigit(c)) return false;
    }

    // All characters are numbers.
    return true;
};

/**
 * Returns true if string s is an integer within the range of integer arguments a and b, inclusive.
 *
 * @param s string value that represents the integer to check in range.
 * @param a lower value of the range.
 * @param b higher value of the range.
 * @return true if 's' is between 'a' & 'b'; false, otherwise.
 */
NumberUtils.isIntegerInRange = function(s, a, b)
{   if (StringUtils.isEmpty(s))
       if (NumberUtils.isIntegerInRange.arguments.length == 1) return NumberUtils.defaultEmptyOK;
       else return (NumberUtils.isIntegerInRange.arguments[1]);

    // Catch non-integer strings to avoid creating a NaN below,
    // which isn't available on JavaScript 1.0 for Windows.
    if (!NumberUtils.isInteger(s, false)) return false;

    // Now, explicitly change the type to integer via parseInt
    // so that the comparison code below will work both on
    // JavaScript 1.2 (which typechecks in equality comparisons)
    // and JavaScript 1.1 and before (which doesn't).
    var num = parseInt (s);
    return ((num >= a) && (num <= b));
};

/**
 * Checks whether an integer is empty.
 *
 * @param value to check if empty
 * @return true if value is empty; false otherwise.
 */
NumberUtils.isIntegerEmpty = function(value) {
    return value == -2147483648;
};
