/*
 * InputMask version 1.2.7
 *
 * This script contains several classes to restrict the user input on HTML inputs.
 *
 * Dependencies: 
 *  - JavaScriptUtil.js
 *  - Parsers.js (for NumberMask and DateMask)
 *
 * Author: Luis Fernando Planella Gonzalez (lfpg.dev@gmail.com)
 * Home Page: http://javascriptools.sourceforge.net

 * You may freely distribute this file, since you include this header 
 * along with the script
 */

///////////////////////////////////////////////////////////////////////////////
//Temporary variables for the masks
numbers = new Input(JST_CHARS_NUMBERS);
optionalNumbers = new Input(JST_CHARS_NUMBERS);
optionalNumbers.optional = true;
oneToTwoNumbers = new Input(JST_CHARS_NUMBERS, 1, 2);
year = new Input(JST_CHARS_NUMBERS, 1, 4, getFullYear);
dateSep = new Literal("/");
dateTimeSep = new Literal(" ");
timeSep = new Literal(":");

/*
 * Constants
 */
//Will InputMask validate the text on the onblur event?
var JST_MASK_VALIDATE_ON_BLUR = true;
//Allow negative values by default on the NumberMask
var JST_DEFAULT_ALLOW_NEGATIVE = true;
//Will the NumberMask digits be from left to right by default?
var JST_DEFAULT_LEFT_TO_RIGHT = false;
//Validates the typed text on DateMask?
var JST_DEFAULT_DATE_MASK_VALIDATE = true;
//The default message for DateMask validation errors
var JST_DEFAULT_DATE_MASK_VALIDATION_MESSAGE = "";
//The default message for DateMask validation errors
var JST_FIELD_DECIMAL_SEPARATOR = new Literal(",");

/*
 * Some prebuilt masks
 */
var JST_MASK_NUMBERS       = [numbers];
var JST_MASK_DECIMAL       = [numbers, JST_FIELD_DECIMAL_SEPARATOR, optionalNumbers];
var JST_MASK_UPPER         = [new Upper(JST_CHARS_LETTERS)];
var JST_MASK_LOWER         = [new Lower(JST_CHARS_LETTERS)];
var JST_MASK_CAPITALIZE    = [new Capitalize(JST_CHARS_LETTERS)];
var JST_MASK_LETTERS       = [new Input(JST_CHARS_LETTERS)];
var JST_MASK_ALPHA         = [new Input(JST_CHARS_ALPHA)];
var JST_MASK_ALPHA_UPPER   = [new Upper(JST_CHARS_ALPHA)];
var JST_MASK_ALPHA_LOWER   = [new Lower(JST_CHARS_ALPHA)];
var JST_MASK_DATE          = [oneToTwoNumbers, dateSep, oneToTwoNumbers, dateSep, year];
var JST_MASK_DATE_TIME     = [oneToTwoNumbers, dateSep, oneToTwoNumbers, dateSep, year, dateTimeSep, oneToTwoNumbers, timeSep, oneToTwoNumbers];
var JST_MASK_DATE_TIME_SEC = [oneToTwoNumbers, dateSep, oneToTwoNumbers, dateSep, year, dateTimeSep, oneToTwoNumbers, timeSep, oneToTwoNumbers, timeSep, oneToTwoNumbers];

//Clear the temporary variables
delete numbers;
delete optionalNumbers;
delete oneToTwoNumbers;
delete year;
delete dateSep;
delete dateTimeSep;
delete timeSep;

///////////////////////////////////////////////////////////////////////////////
/*
 * This is the main InputMask class.
 * Parameters: 
 *    fields: The mask fields
 *    control: The reference to the control that is being masked
 *    keyPressFunction: The additional function instance used on the keyPress event
 *    keyDownFunction: The additional function instance used on the keyDown event
 *    keyUpFunction: The additional function instance used on the keyUp event
 *    blurFunction: The additional function instance used on the blur event
 */
function InputMask(fields, control, keyPressFunction, keyDownFunction, keyUpFunction, blurFunction) {
    
    //Check if the fields are a String
    if (isInstance(fields, String)) {
        fields = maskBuilder.parse(fields);
    } else if (isInstance(fields, Field)) {
        fields = [fields];
    }
    
    //Check if the fields are a correct array of fields
    if (isInstance(fields, Array)) {
        for (var i = 0; i < fields.length; i++) {
            var field = fields[i];
            if (!isInstance(field, Field)) {
                alert("Invalid field: " + field);
                return;
            }
        }
    } else {
        alert("Invalid field array: " + fields);
        return;
    }
    
    this.fields = fields;
    this.fieldValues = null;

    //Validate the control
    if (isInstance(control, String)) {
        control = getObject(control);
    }
    if (!isValidControlToMask(control)) {
        alert("Invalid control to mask");
        return;
    } else {
        this.control = control;
        prepareForCaret(control);
    }
    
    //Set the control's reference to the mask descriptor
    this.control.mask = this;
    this.control.pad = false;
    this.control.keyDownFlag = false;
    this.control.ignore = false;

    //Set the function calls
	this.keyDownFunction = keyDownFunction || null;
	this.keyPressFunction = keyPressFunction || null;
	this.keyUpFunction = keyUpFunction || null;
	this.blurFunction = blurFunction || null;

    //The onKeyDown event will detect special keys
    function onKeyDown (event) {
        if (window.event) {
            event = window.event;
        }
        if (this.keyDownFlag) {
            return false;
        }
        this.pad = false;
        this.ignore = false;
        
        var keyCode = typedCode(event);
        if (keyCode == 32) {
            //IE hack: check if any fields allows spaces
            var allowed = false;
            if (window.event) {
                for (var i = 0; i < this.mask.fields.length; i++) {
                    var field = this.mask.fields[i];
                    if (field.isAccepted && field.isAccepted(" ")) {
                        allowed = true;
                        break;
                    }
                }
            }
            return allowed;
        } else if (keyCode <= 46) {
            if (event.ctrlKey && (keyCode == 13 || keyCode == 39)) {
                this.pad = true;
            } else {
                this.ignore = true;
            }
            return true;
        }

        this.keyDownFlag = true;

        //Check for extra function on keydown
        if (this.mask.keyDownFunction != null) {
            var ret = this.mask.keyDownFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }
        return true;
    }
    this.control.onkeydown = onKeyDown;
    
    //The KeyUp event will apply the mask
	function onKeyUp (event) {
        if (window.event) {
            event = window.event;
        }
        //Check if it's not an ignored key
        this.keyDownFlag = false;
        if (this.ignore != true) {
            applyMask(this.mask, false);
        }
        //Check for extra function on keydown
        if (this.mask.keyUpFunction != null) {
            var ret = this.mask.keyUpFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }

        return true;
    }
    this.control.onkeyup = onKeyUp;
    
    //The Blur event will apply the mask again, to ensure the user will not paste an invalid value
    if (JST_MASK_VALIDATE_ON_BLUR) {
    	function onBlur (event) {
            if (window.event) {
                event = window.event;
            }
            applyMask(this.mask, true);
            
            //Check for extra function on keydown
            if (this.mask.blurFunction != null) {
                this.mask.blurFunction(event, this.mask);
            }
    
            return true;
        }
        this.control.onblur = onBlur;
    }
    
	if (keyPressFunction != null) {
	    this.control.onkeypress = keyPressFunction;
	}
    
    //Method to determine if the mask is all complete
    this.isComplete = function() {

        //Ensures the field values will be parsed
        applyMask(this, true);

        //Check if there is some value
        if (this.fieldValues == null && ((this.fields != null) || (this.fields.length > 0))) {
            return false;
        }

        //Check for completed values
        for (var i = 0; i < this.fields.length; i++) {
            var field = this.fields[i];
            if (field.input && !field.isComplete(this.fieldValues[i]) && !field.optional) {
                return false;
            }
        }
        return true;
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * This is the main NumberMask class.
 * Parameters: 
 *    parser: The NumberParser instance used by the mask
 *    control: The reference to the control that is being masked
 *    maxIntegerDigits: The limit for integer digits (excluding separators). 
 *                      Default: -1 (no limit)
 *    allowNegative: Should negative values be allowed? Default: see the 
 *                   value of the JST_DEFAULT_ALLOW_NEGATIVE constant.
 *    keyPressFunction: The additional function instance used on the keyPress event
 *    keyDownFunction: The additional function instance used on the keyDown event
 *    keyUpFunction: The additional function instance used on the keyUp event
 *    blurFunction: The additional function instance used on the blur event
 */
function NumberMask(parser, control, maxIntegerDigits, allowNegative, keyPressFunction, keyDownFunction, keyUpFunction, blurFunction, leftToRight) {
    //Validate the parser
    if (!isInstance(parser, NumberParser)) {
        alert("Illegal NumberParser instance");
        return;
    }
    this.parser = parser;
    
    //Validate the control
    if (isInstance(control, String)) {
        control = getObject(control);
    }
    if (!isValidControlToMask(control)) {
        alert("Invalid control to mask");
        return;
    } else {
        this.control = control;
        prepareForCaret(control);
    }

    //Get the additional properties
    this.maxIntegerDigits = maxIntegerDigits || -1;
    this.allowNegative = allowNegative || JST_DEFAULT_ALLOW_NEGATIVE;
    this.leftToRight = leftToRight || JST_DEFAULT_LEFT_TO_RIGHT;

    //Set the control's reference to the mask and other aditional flags
    this.control.mask = this;
    this.control.keyDownFlag = false;
    this.control.ignore = false;
    this.control.swapSign = false;
    this.control.toDecimal = false;
    this.control.oldValue = this.control.value;
    
    //The onKeyDown event will detect special keys
    function onKeyDown (event) {
        if (window.event) {
            event = window.event;
        }
        if (this.keyDownFlag) {
            return false;
        }
        this.pad = false;
        this.ignore = false;
        
        var keyCode = typedCode(event);
        if (keyCode == 32) {
            return false;
        } else if (keyCode <= 46) {
            this.ignore = true;
            return true;
        }

        this.keyDownFlag = true;

        //Check for extra function on keydown
        if (this.mask.keyDownFunction != null) {
            var ret = this.mask.keyDownFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }
        
        //Store the old value
        this.oldValue = this.value;
        
        return true;
    }
    this.control.onkeydown = onKeyDown;
    
    //The KeyUp event will apply the mask
	function onKeyUp (event) {
        //Check an invalid parser
        if (this.mask.parser.decimalDigits < 0 && !this.mask.leftToRight) {
            alert("A NumberParser with unlimited decimal digits is not supported on NumberMask when the leftToRight property is false");
            this.value = "";
            this.keyDownFlag = true;
            return false;
        }

        if (window.event) {
            event = window.event;
        }
        //Check if it's not an ignored key
        this.keyDownFlag = false;
        if (this.ignore != true) {
            applyNumberMask(this.mask, false);
        }
        //Check for extra function on keyup
        if (this.mask.keyUpFunction != null) {
            var ret = this.mask.keyUpFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }

        return true;
    }
    this.control.onkeyup = onKeyUp;

    //The KeyPress event will filter the keys
	function onKeyPress (event) {
        if (window.event) {
            event = window.event;
        }
        var mask = this.mask;

        //Check for extra function on keypress
        if (mask.keyPressFunction != null) {
            var ret = this.mask.keyPressFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }
        if (this.ignore) {
            return true;
        } else {
            //Check for the minus sign
            if (String.fromCharCode(typedCode(event)) == '-') {
                if (mask.allowNegative) {
                    this.swapSign = true;
                }
                return false;
            }
            //Check for the decimal separator
            if (mask.leftToRight && String.fromCharCode(typedCode(event)) == mask.parser.decimalSeparator) {
                this.toDecimal = true;
                return false;
            }
            this.swapSign = false;
            this.toDecimal = false;
            return onlyNumbers(String.fromCharCode(typedCode(event)));
        }
	}
    this.control.onkeypress = onKeyPress;
    
    if (JST_MASK_VALIDATE_ON_BLUR) {
        //The Blur event will apply the mask again, to ensure the user will not paste an invalid value
    	function onBlur (event) {
            if (window.event) {
                event = window.event;
            }
            applyNumberMask(this.mask, true);
            
            //Check for extra function on keydown
            if (this.mask.blurFunction != null) {
                this.mask.blurFunction(event, this.mask);
            }
    
            return true;
        }
        this.control.onblur = onBlur;
    }
    
    //Method to determine if the mask is all complete
    this.isComplete = function() {
        return this.control.value != "";
    }
    
    //Returns the control value as a number
    this.getAsNumber = function() {
        var number = this.parser.parse(this.control.value);
        if (isNaN(number)) {
            number = null;
        }
        return number;
    }

    //Sets the control value as a number
    this.setAsNumber = function(number) {
        var value = "";
        if (isInstance(number, Number)) {
            value = this.parser.format(number);
        }
        this.control.value = value;
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * This is the main DateMask class.
 * Parameters: 
 *    parser: The DateParser instance used by the mask
 *    control: The reference to the control that is being masked
 *    validate: Validate the control on the onblur event? Default: The JST_DEFAULT_DATE_MASK_VALIDATE value
 *    validationMessage: Message alerted on validation on fail. The ${value} 
 *       placeholder may be used as a substituition for the field value, and ${mask} 
 *       for the parser mask. Default: the JST_DEFAULT_DATE_MASK_VALIDATION_MESSAGE value
 *    keyPressFunction: The additional function instance used on the keyPress event
 *    keyDownFunction: The additional function instance used on the keyDown event
 *    keyUpFunction: The additional function instance used on the keyUp event
 *    blurFunction: The additional function instance used on the blur event
 */
function DateMask(parser, control, validate, validationMessage, keyPressFunction, keyDownFunction, keyUpFunction, blurFunction) {
    
    //Validate the parser
    if (!isInstance(parser, DateParser)) {
        alert("Illegal DateParser instance");
        return;
    }
    this.parser = parser;
    
    //Add some functionality to the original onblur event
    this.extraBlurFunction = blurFunction || null;
    function maskBlurFunction (event, dateMask) {
        var control = dateMask.control;
        if (dateMask.validate && control.value.length > 0) {
            if (!dateMask.parser.isValid(control.value)) {
                var msg = dateMask.validationMessage;
                if (!isEmpty(msg)) {
                    msg = replaceAll(msg, "${value}", control.value);
                    msg = replaceAll(msg, "${mask}", dateMask.parser.mask);
                    alert(msg);
                }
                control.value = "";
                control.focus();
            }
        }
        if (dateMask.extraBlurFunction != null) {
            return dateMask.extraBlurFunction(event, dateMask);
        }
        return true;
    }
    
    //Build the fields array
    var fields = new Array();
    var old = '';
    var mask = this.parser.mask;
    while (mask.length > 0) {
        var field = mask.charAt(0);
        var size = 1;
        var maxSize = -1;
        var padFunction = null;
        while (mask.charAt(size) == field) {
            size++;
        }
        mask = mid(mask, size);
        switch (field) {
            case 'd': case 'M': case 'h': case 'H': case 'm': case 's': 
                maxSize = 2;
                break;
            case 'y':
                padFunction = getFullYear;
                if (size == 2) {
                    maxSize = 2;
                } else {
                    maxSize = 4;
                }
                break;
            case 'S':
                maxSize = 3;
                break;
        }
        var input;
        if (maxSize == -1) {
            input = new Literal(field);
        } else {
            input = new Input(JST_CHARS_NUMBERS, size, maxSize);
            input.padFunction = padFunction;
        }
        fields[fields.length] = input;
    }
    
    //Initialize the superclass
    this.base = InputMask;
    this.base(fields, control, keyPressFunction, keyDownFunction, keyUpFunction, maskBlurFunction);
    
    //Store the additional variables
    this.validate = validate == null ? JST_DEFAULT_DATE_MASK_VALIDATE : booleanValue(validate);
    this.validationMessage = validationMessage || JST_DEFAULT_DATE_MASK_VALIDATION_MESSAGE;
    this.control.dateMask = this;
    
    
    //Returns the control value as a date
    this.getAsDate = function() {
        return this.parser.parse(this.control.value);
    }

    //Sets the control value as a date
    this.setAsDate = function(date) {
        var value = "";
        if (isInstance(date, Date)) {
            value = this.parser.format(date);
        }
        this.control.value = value;
    }
}


///////////////////////////////////////////////////////////////////////////////
/*
 * This class limits the size of an input (mainly textAreas, that does not have a 
 * maxLength attribute). Optionally, can use another element to output the number 
 * of characters on the element and/or the number of characters left.
 * Like the masks, this class uses the keyUp and blur events, may use additional
 * callbacks for those events.
 * Parameters:
 *     control: The textArea reference or name
 *     maxLength: The maximum text length
 *     output: The element to output the number of characters left. Default: none
 *     outputText: The text. May use two placeholders: 
 *         ${size} - Outputs the current text size
 *         ${left} - Outputs the number of characters left
 *         ${max} - Outputs the maximum number characters that the element accepts
 *         Examples: "${size} / ${max}", "You typed ${size}, and have ${left} more characters to type"
 *         Default: "${left}"
 *     updateFunction: If set, this function will be called when the text is updated. So, custom actions
 *         may be performed. The arguments passed to the function are: The control, the text size, the maximum size
 *         and the number of characters left.
 *     keyUpFunction: The additional handler to the keyUp event. Default: none
 *     blurFunction: The additional handler to the blur event. Default: none
 */
function SizeLimit(control, maxLength, output, outputText, updateFunction, keyUpFunction, blurFunction) {
    //Validate the control
    if (isInstance(control, String)) {
        control = getObject(control);
    }
    if (!isValidControlToMask(control)) {
        alert("Invalid control to limit size");
        return;
    } else {
        this.control = control;
        prepareForCaret(control);
    }
    
    if (!isInstance(maxLength, Number)) {
        alert("Invalid maxLength");
        return;
    }

    //Get the additional properties
    this.control = control;
    this.maxLength = maxLength;
    this.output = output || null;
    this.outputText = outputText || "${left}";
    this.updateFunction = updateFunction || null;
    this.keyUpFunction = keyUpFunction || null;
    this.blurFunction = blurFunction || null;

    //Set the control's reference to the mask descriptor
    this.control.sizeLimit = this;

    //The KeyUp event handler
    function onKeyUp (event) {
        if (window.event) {
            event = window.event;
        }

        //Check for extra function on keypress
        if (this.sizeLimit.keyUpFunction != null) {
            var ret = this.sizeLimit.keyUpFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }
        return checkSizeLimit(this, false);
    }
    this.control.onkeyup = onKeyUp;
    
    // Method used to update the limit    
    this.update = function() {
        checkSizeLimit(this.control, true);
    }
    
    //The Blur event handler
    function onBlur (event) {
        if (window.event) {
            event = window.event;
        }

        //Check for extra function on blur
        if (this.sizeLimit.blurFunction != null) {
            var ret = this.sizeLimit.blurFunction(event, this.mask);
            if (ret == false) {
                return false;
            }
        }
        return checkSizeLimit(this, true);
    }
    this.control.onblur = onBlur;
    
    //Initially check the size limit
    this.update();
}

//Function to return the typed key code
function typedCode(event) {
    var asc = 0;
    if (event.keyCode) {
        asc = event.keyCode;
    } else if (event.which) {
        asc = event.which;
    }
    return asc;
}

//Function to determine if a given object is a valid control to mask
function isValidControlToMask(control) {
    if (control == null) {
        return false;
    } else if (!(control.type) || (!inArray(control.type, ["text", "textarea", "password"]))) {
        return false;
    } else {
        return true;
    }
}

//Function to handle the mask format
function applyMask(mask, isBlur) {
    var fields = mask.fields;

    //Return if there are fields to process
    if ((fields == null) || (fields.length == 0)) {
        return;
    }

    var control = mask.control;
    if (control.value == "" && isBlur) {
        return true;
    }
    var out = "";
    var fieldValues = [];
    var caret = getCaret(control);
    
    //Ensures the values are the same size as the fields
    for (var i = 0; i < fields.length; i++) {
        fieldValues[i] = "";
    }
        
    //Return if there are fields to process
    var value = control.value;
    var fixedPositionLiterals = [];
    
    //Remove all literals
    for (var i = 0; i < fields.length; i++) {
        var field = fields[i];
        if (field.literal) {
            if (i > 0) {
                //Check for this field's fixed position (if any)
                if (fields[i - 1].max == -1) {
                    var descriptor = {};
                    descriptor.field = field;
                    descriptor.position = value.indexOf(field.text);
                    if (descriptor.position >= 0) {
                        fixedPositionLiterals[fixedPositionLiterals.length] = descriptor;
                    }
                }
            }
            value = replaceAll(value, field.text, "");
        }
    }
    
    //Build the output
    var pos = 0;
    for (var i = 0; (i < fields.length) && (pos < value.length); i++) {
        var field = fields[i];
        if (field.literal) {
            var fixed = false;
            for (var j = 0; j < fixedPositionLiterals.length; j++) {
                var descriptor = fixedPositionLiterals[j];
                if (descriptor.field.text == field.text) {
                    fixed = true;
                    break;
                }
            }
            if (!fixed) {
                out += field.text;
                if (caret != null && caret <= out.length) {
                    caret += field.text.length;
                }
            }
        } else {
            var upTo = field.upTo(value, pos);

            //Not accepted
            if (upTo == -1) {
                break;
            } else {
                var fieldValue = field.transformValue(value.substring(pos, upTo + 1));
                if (control.pad) {
                    fieldValue = field.pad(fieldValue);
                }
                fieldValues[i] = fieldValue;
                out += fieldValue;
                pos = upTo + 1;
                if (!field.isComplete(fieldValue)) {
                    break;
                }
            }
        }
    }

    //Insert for this field's fixed position (if any)
    for (var i = 0; i < fixedPositionLiterals.length; i++) {
        var descriptor = fixedPositionLiterals[i];
        out = insertString(out, descriptor.position, descriptor.field.text);
    }

    //Completes the value
    if (control.maxLength > 0) {
        out = left(out, control.maxLength);
    }
    mask.fieldValues = fieldValues;
    
    //Update the control value
    control.value = out;
    if (caret != null && !isBlur) {
        setCaret(control, caret);
    }
    
    return false;
}

//Function to handle the number mask format
function applyNumberMask(numberMask, isBlur) {
    var control = numberMask.control;
    var value = control.value;
    if (value == "" && isBlur) {
        return true;
    }
    var parser = numberMask.parser;
    var maxIntegerDigits = numberMask.maxIntegerDigits;
    var swapSign = false;
    var toDecimal = false;
    var leftToRight = numberMask.leftToRight;
    if (control.swapSign == true) {
        swapSign = true;
        control.swapSign = false;
    }
    if (control.toDecimal == true) {
        toDecimal = value.indexOf(parser.decimalSeparator) < 0;
        control.toDecimal = false;
    }

    var intPart = 0;
    var decPart = 0;
    var pos = value.indexOf(parser.decimalSeparator);
    var hasDecimal = (pos >= 0);
    var decSize = 0;
    var isNegative = false;
    
    //Check if the handling will be from left to right or right to left
    if (leftToRight) {
        if (hasDecimal) {
            intPart = parser.parse(left(value, pos + 1));
            decSize = replaceAll(value, ")", "").substr(pos + 1).length;
            decPart = parser.parse(value.substr(pos + 1));
        } else {
            intPart = parser.parse(value);
            decSize = 0;
        }
        intPart = Math.abs(intPart);
        value = parser.parse(value);
        isNegative = (value < 0)
        value = Math.abs(value);
    } else {
        value = replaceAll(value, parser.groupSeparator, '');
        value = replaceAll(value, parser.decimalSeparator, '');
        value = parser.parse(value);
        isNegative = (value < 0)
        value = Math.abs(value) / Math.pow(10, parser.decimalDigits)
        intPart = Math.floor(value)
        decPart = value - intPart;
    }
    //Validate the input
    if (isNaN(value)) {
        control.value = "";
        return true;
    }
    if ((maxIntegerDigits >= 0 && String(intPart).length > maxIntegerDigits) || 
            (leftToRight && parser.decimalDigits >= 0 && decSize > parser.decimalDigits)) {
        control.value = control.oldValue;
        return false;
    }
    //Check will swap the sign
    if (swapSign) {
        isNegative = !isNegative;
    }
    //Again, check if the handling will be from left to right or right to left
    if (leftToRight) {
        var digits = parser.decimalDigits;
        if (!isBlur) {
            parser.decimalDigits = !hasDecimal ? 0 : decSize;
        }
        if (isNegative) {
            value = -value;
        }
        value = parser.format(value);
        parser.decimalDigits = digits;
        if (toDecimal) {
            value = replaceAll(value, ")", "") + parser.decimalSeparator;
        }
    } else {
        value = parser.round(intPart + decPart);
        if (isNegative) {
            value = -value;
        }
        value = parser.format(value);
    }

    //Retrieve the caret    
    var caret = getCaret(control);
    var caretPos, hadSymbol;
    var caretToEnd = toDecimal || caret == null;
    if (caret != null && !isBlur) { 
        if (!caretToEnd) {
            caretPos = 0
            for (var i = 0; i < caret; i++) {
                if (onlyNumbers(control.value.charAt(i))) {
                    caretPos++;
                }
            }
        }
        hadSymbol = control.value.indexOf(parser.currencySymbol) >= 0 || control.value.indexOf(parser.decimalSeparator) >= 0;
    }
    
    //Update the value
    control.value = value;
    
    if (caret != null && !isBlur) {    
        //If a currency symbol was inserted, set caret to end    
        if (!hadSymbol && ((value.indexOf(parser.currencySymbol) >= 0) || (value.indexOf(parser.decimalSeparator) >= 0))) {
            caretToEnd = true;
        }
        //Restore the caret
        if (!caretToEnd) {
            var newCaretPos = 0;
            var digits = 0;
            //Retrieve the new caret position
            for (var i = 0; i < value.length; i++) {
                if (onlyNumbers(value.charAt(i))) {
                    newCaretPos = i;
                    digits++;
                }
                if (digits == caretPos) {
                    break;
                }
            }
            setCaret(control, newCaretPos + 1);
        } else {
            setCaretToEnd(control);
        }
    }
    return false;
}

//Function to check the text limit
function checkSizeLimit(control, isBlur) {
    var sizeLimit = control.sizeLimit;
    var max = sizeLimit.maxLength;
    var diff = max - control.value.length;
    if (control.value.length > max) {
        control.value = left(control.value, max);
        setCaretToEnd(control);
    }
    var size = control.value.length;
    var charsLeft = max - size;
    if (sizeLimit.output != null) {
        var text = sizeLimit.outputText;
        text = replaceAll(text, "${size}", size);
        text = replaceAll(text, "${left}", charsLeft);
        text = replaceAll(text, "${max}", max);
        setValue(sizeLimit.output, text);
    }
    if (isInstance(sizeLimit.updateFunction, Function)) {
        sizeLimit.updateFunction(control, size, max, charsLeft);
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Field Type Classes

/*
 * General input field type
 */
function Field() {
    this.literal = false;
    this.input = false;
}

/*
 * Literal field type
 */
function Literal(text) {
    this.base = Field;
    this.base();
    this.text = text;
    this.literal = true;
    
    //Return if the character is in the text
    this.isAccepted = function(chr) {
        return onlySpecified(chr, this.text);
    }
}

/*
 * User input field type
 */
function Input(accepted, min, max, padFunction, optional) {
    this.base = Field;
    this.base();
    this.accepted = accepted;
    if (min != null && max == null) {
        max = min;
    }
    this.min = min || 1;
    this.max = max || -1;
    this.padFunction = padFunction || null;
    this.input = true;
    this.upper = false;
    this.lower = false;
    this.capitalize = false;
    this.optional = booleanValue(optional);

    //Ensures the min/max consistencies
    if (this.min < 1) {
        this.min = 1;
    }
    if (this.max == 0) {
        this.max = -1;
    }
    if ((this.max < this.min) && (this.max >= 0)) {
        this.max = this.min;
    }
    
    //Returns the index up to where the texts matches this input
    this.upTo = function(text, fromIndex) {
        text = text || "";
        fromIndex = fromIndex || 0;
        if (text.length < fromIndex) {
            return -1;
        }
        var toIndex = -1;
        for (var i = fromIndex; i < text.length; i++) {
            if (this.isAccepted(text.substring(fromIndex, i + 1))) {
                toIndex = i;
            } else {
                break;
            }
        }
        return toIndex;
    }
    
    //Return if the text is accepted
    this.isAccepted = function(text) {
        return ((this.accepted == null) || onlySpecified(text, this.accepted)) && ((text.length <= this.max) || (this.max < 0));
    }

    //Return if the text length is ok
    this.checkLength = function(text) {
        return (text.length >= this.min) && ((this.max < 0) || (text.length <= this.max));
    }
    
    //Return if the text is complete
    this.isComplete = function(text) {
        text = String(text);
        if (text.length < this.min) {
            return false;
        }
        return (this.max < 0) || (text.length == this.max);
    }

    //Apply the case transformations when necessary
    this.transformValue = function(text) {
        text = String(text);
        if (this.upper) {
            return text.toUpperCase();
        } else if (this.lower) {
            return text.toLowerCase();
        } else if (this.capitalize) {
            return capitalize(text);
        } else {
            return text;
        }
    }
    
    //Pads the text
    this.pad = function(text) {
        text = String(text);
        if ((this.max >= 0) || (text.length <= this.max)) {
            var value;
            if (this.padFunction != null) {
                value = this.padFunction(text, this.min, this.max);
            } else {
                value = text;
            }
            //Enforces padding
            if (this.max > 0) {
                var padChar = ' ';
                if (this.accepted.indexOf(' ') > 0) {
                    padChar = ' ';
                } else if (this.accepted.indexOf('0') > 0) {
                    padChar = '0';
                } else {
                    padChar = this.accepted.charAt(0);
                }
                return left(lpad(value, this.max, padChar), this.max);
            } else {
                //If has no max limit
                return value;
            }
        } else {
            return text;
        }
    }
}

/*
 * Lowercased input field type
 */
function Lower(accepted, min, max, padFunction, optional) {
    this.base = Input;
    this.base(accepted, min, max, padFunction, optional);
    this.lower = true;
}

/*
 * Uppercased input field type
 */
function Upper(accepted, min, max, padFunction, optional) {
    this.base = Input;
    this.base(accepted, min, max, padFunction, optional);
    this.upper = true;
}

/*
 * Capitalized input field type
 */
function Capitalize(accepted, min, max, padFunction, optional) {
    this.base = Input;
    this.base(accepted, min, max, padFunction, optional);
    this.capitalize = true;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * The FieldBuilder class is used to build input fields
 */
function FieldBuilder() {
    
    /**
     * Builds a literal input with the given text
     */
    this.literal = function(text) {
        return new Literal(text);
    }

    /* 
     * Build an input field with the given accepted chars. 
     * All other parameters are optional 
     */
    this.input = function(accepted, min, max, padFunction, optional) {
        return new Input(accepted, min, max, padFunction, optional);
    }

    /* 
     * Build an uppercase input field with the given accepted chars. 
     * All other parameters are optional 
     */
    this.upper = function(accepted, min, max, padFunction, optional) {
        return new Upper(accepted, min, max, padFunction, optional);
    }

    /* 
     * Build an lowercase field with the given accepted chars. 
     * All other parameters are optional 
     */
    this.lower = function(accepted, min, max, padFunction, optional) {
        return new Lower(accepted, min, max, padFunction, optional);
    }

    /* 
     * Build an capitalized input field with the given accepted chars. 
     * All other parameters are optional 
     */
    this.capitalize = function(accepted, min, max, padFunction, optional) {
        return new Capitalize(accepted, min, max, padFunction, optional);
    }
    
    /* 
     * Build an input field accepting any chars.
     * All parameters are optional 
     */
    this.inputAll = function(min, max, padFunction, optional) {
        return this.input(null, min, max, padFunction, optional);
    }

    /* 
     * Build an uppercase input field accepting any chars.
     * All parameters are optional 
     */
    this.upperAll = function(min, max, padFunction, optional) {
        return this.upper(null, min, max, padFunction, optional);
    }

    /* 
     * Build an lowercase field accepting any chars.
     * All parameters are optional 
     */
    this.lowerAll = function(min, max, padFunction, optional) {
        return this.lower(null, min, max, padFunction, optional);
    }

    /* 
     * Build an capitalized input field accepting any chars.
     * All parameters are optional 
     */
    this.capitalizeAll = function(min, max, padFunction, optional) {
        return this.capitalize(null, min, max, padFunction, optional);
    }
    
    /* 
     * Build an input field accepting only numbers.
     * All parameters are optional 
     */
    this.inputNumbers = function(min, max, padFunction, optional) {
        return this.input(JST_CHARS_NUMBERS, min, max, padFunction, optional);
    }
    
    /* 
     * Build an input field accepting only letters.
     * All parameters are optional 
     */
    this.inputLetters = function(min, max, padFunction, optional) {
        return this.input(JST_CHARS_LETTERS, min, max, padFunction, optional);
    }

    /* 
     * Build an uppercase input field accepting only letters.
     * All parameters are optional 
     */
    this.upperLetters = function(min, max, padFunction, optional) {
        return this.upper(JST_CHARS_LETTERS, min, max, padFunction, optional);
    }

    /* 
     * Build an lowercase input field accepting only letters.
     * All parameters are optional 
     */
    this.lowerLetters = function(min, max, padFunction, optional) {
        return this.lower(JST_CHARS_LETTERS, min, max, padFunction, optional);
    }

    /* 
     * Build an capitalized input field accepting only letters.
     * All parameters are optional 
     */
    this.capitalizeLetters = function(min, max, padFunction, optional) {
        return this.capitalize(JST_CHARS_LETTERS, min, max, padFunction, optional);
    }
}
//Create a FieldBuilder instance
var fieldBuilder = new FieldBuilder();

///////////////////////////////////////////////////////////////////////////////
/*
 * The MaskBuilder class is used to build masks in a easier to use way
 */
function MaskBuilder() {

    /* 
     * Parses a String, building a mask from it.
     * The string may contain these characters:
     * #, 0 or 9 - A number               
     * a or A - A letter
     * ? or _ - Any character
     * l or L - A lowercase letter
     * u or U - An uppercase letter
     * c or C - A capitalized letter
     * \\ - A backslash
     * \#, \0, ... - Those literal characters
     * any other character - A literal text
     */
    this.parse = function(string) {
        if (string == null || !isInstance(string, String)) {
            return this.any();
        }
        var fields = new Array();
        var start = null;
        var lastType = null;
        //helper function
        var switchField = function(type, text) {
            switch (type) {
                case '_':
                    return fieldBuilder.inputAll(text.length);
                case '#':
                    return fieldBuilder.inputNumbers(text.length);
                case 'a':
                    return fieldBuilder.inputLetters(text.length);
                case 'l': 
                    return fieldBuilder.lowerLetters(text.length);
                case 'u': 
                    return fieldBuilder.upperLetters(text.length);
                case 'c': 
                    return fieldBuilder.capitalizeLetters(text.length);
                default:
                    return fieldBuilder.literal(text);
            }
        }
        //Let's parse the string
        for (var i = 0; i < string.length; i++) {
            var c = string.charAt(i);
            if (start == null) {
                start = i;
            }
            var type;
            var literal = false;
            //Checks for the escaping backslash
            if (c == '\\') {
                if (i == string.length - 1) {
                    break;
                }
                string = left(string, i) + mid(string, i + 1);
                c = string.charAt(i);
                literal = true;
            }
            //determine the field type
            if (literal) {
                type = '?';
            } else {
                switch (c) {
                    case '?': case '_':
                        type = '_';
                        break;
                    case '#': case '0': case '9':
                        type = '#';
                        break;
                    case 'a': case 'A':
                        type = 'a';
                        break;
                    case 'l': case 'L':
                        type = 'l';
                        break;
                    case 'u': case 'U':
                        type = 'u';
                        break;
                    case 'c': case 'C':
                        type = 'c';
                        break;
                    default:
                        type = '?';
                }
            }
            if (lastType != type && lastType != null) {
                var text = string.substring(start, i);
                fields[fields.length] = switchField(lastType, text);
                start = i;
                lastType = type;
            } else {
                lastType = type
            }
        }
        //Use the last field
        if (start < string.length) {
            var text = string.substring(start);
            fields[fields.length] = switchField(lastType, text);
        }
        return fields;
    }
    
    /* 
     * Build a mask that accepts the given characters
     * May also specify the max length
     */
    this.accept = function(accepted, max) {
        return [fieldBuilder.input(accepted, max)];
    }

    /* 
     * Build a mask that accepts any characters
     * May also specify the max length
     */
    this.any = function(max) {
        return [fieldBuilder.any(max)];
    }

    /* 
     * Build a numeric mask
     * May also specify the max length
     */
    this.numbers = function(max) {
        return [fieldBuilder.inputNumbers(max)];
    }
    
    /* 
     * Build a decimal input mask
     */
    this.decimal = function() {
        var decimalField = fieldBuilder.inputNumbers();
        decimalField.optional = true;
        return [fieldBuilder.inputNumbers(), JST_FIELD_DECIMAL_SEPARATOR, decimalField];
    }
    
    /* 
     * Build a mask that only accepts letters
     * May also specify the max length
     */
    this.letters = function(max) {
        return [fieldBuilder.inputLetters(max)];
    }
    
    /* 
     * Build a mask that only accepts uppercase letters
     * May also specify the max length
     */
    this.upperLetters = function(max) {
        return [fieldBuilder.upperLetters(max)];
    }
    
    /* 
     * Build a mask that only accepts lowercase letters
     * May also specify the max length
     */
    this.lowerLetters = function(max) {
        return [fieldBuilder.lowerLetters(max)];
    }
    
    /* 
     * Build a mask that only accepts capitalized letters
     * May also specify the max length
     */
    this.capitalizeLetters = function(max) {
        return [fieldBuilder.capitalizeLetters(max)];
    }
}
//Create a MaskBuilder instance
var maskBuilder = new MaskBuilder();
