/**
 * $Id: jquery.scInput.js,v 1.5 2011-12-09 20:24:31 luis Exp $
 */

 var _scCalculatorControl = {};

(function($) {

    $.fn.listen = function() {
        return $.scInput.setInput(this);
    }; // listen

    $.scInput = {

        //---------- Public methods ------------------------------------------//

        /**
         * Initializes the input elements and binds its event handlers.
         */
        setInput: function($oElement) {
            var _this = this;

            return $oElement.each(function() {
                var $this     = $(this),
                    oSettings = _this._loadOptions($this);

                if ('textarea' != this.type || !$.browser.msie || 9 <= parseFloat($.browser.version)) {
                    if (oSettings.alignRight) {
                        $this.css('text-align', 'right');
                    }
                    $this.attr('maxlength', oSettings.maxLength);

                    _this._createWatermark($this, this.type, oSettings);

                    $this.bind('focus'   , {element: _this, settings: oSettings, callEvent: _this._onFocus   }, _this._prepareEvent)
                         .bind('blur'    , {element: _this, settings: oSettings, callEvent: _this._onBlur    }, _this._prepareEvent)
                         .bind('keydown' , {element: _this, settings: oSettings, callEvent: _this._onKeyDown }, _this._prepareEvent)
                         .bind('keyup'   , {element: _this, settings: oSettings, callEvent: _this._onKeyUp   }, _this._prepareEvent)
                         .bind('keypress', {element: _this, settings: oSettings, callEvent: _this._onKeyPress}, _this._prepareEvent)
                         .bind('paste'   , {element: _this, settings: oSettings, callEvent: _this._onPaste   }, _this._preparePasteEvent);
                }
            });
        }, // setInput

        //---------- Event methods -------------------------------------------//

        /**
         * Handles the onBlur event of the input element.
         */
        _onBlur: function(e, o) {
            if (typeof _scCalculatorControl !== "undefined") {
                if (_scCalculatorControl[o.$this.attr("id")]) {
                    return;
                }
            }

            this._applyWatermark(o.$this, o.$clone);

            if (o.isNumeric) {
                sTestValue = this._removeCurrencySymbol(o.$this.val(), o);
                sTestValue = this._removeNegativeSymbol(sTestValue, o);
                if ('' == sTestValue) {
                    o.$this.val('');
                }
            }

            if ('decimal' == o.datatype || 'currency' == o.datatype) {
                this._completeDecimal(o);
            }

            if (o.inputFocusValue != o.$this.val() && !this._isNotText(o._this)) {
                o.$this.trigger('change');
            }

            return true;
        }, // _onBlur

        /**
         * Handles the onFocus event of the input element.
         */
        _onFocus: function(e, o) {
            o.inputFocusValue = o.$this.val();

            if (o.selectOnFocus) {
                o.$this.select();
            }

            return true;
        }, // _onFocus

        /**
         * Handles the onKeyDown event of the input element.
         */
        _onKeyDown: function(e, o) {
            o.isSpecial = (null != this.specialKeys[o.pressedKey]) || e.ctrlKey || e.metaKey || e.altKey;

            return true;
        }, // _onKeyDown

        /**
         * Handles the onKeyPress event of the input element.
         */
        _onKeyPress: function(e, o) {
            if (this._isNotText(o._this)) {
                return true;
            }

            if (o.isSpecial) {
                if (this._isEnterSubmit(o, 'enter')) {
                   if (typeof nm_submit_form == 'function') {   // para filtro da consulta
                       nm_submit_form();
                   }else{
                       document.F1.submit();
                   }
                }
                else if (this._isEnterTab(o, 'enter')) {
                    var bPrevDef = this._goToNextElement(o);

                    if (bPrevDef) {
                        e.preventDefault();
                    }
                }
                return true;
            }

            e.preventDefault();

            o.originalValue = this._removeCurrencySymbol(o.originalValue, o);
            o.originalValue = this._removeNegativeSymbol(o.originalValue, o);
            this._checkDecimalsPress(o);
            o.originalValue = this._addNegativeSymbol(o.originalValue, o);
            o.originalValue = this._addCurrencySymbol(o.originalValue, o);

            if (-1 < $.inArray(o.pressedChar, o.maskCharsArray) && !o.forceMask) {
                return true;
            }

            o.newValue    = this._removeCurrencySymbol(this._getNewValue(o), o);
            o.newValue    = this._removeNegativeSymbol(o.newValue, o);
            o.maskedValue = this._maskValue(o);
            o.maskedValue = this._addNegativeSymbol(o.maskedValue, o);
            o.maskedValue = this._addCurrencySymbol(o.maskedValue, o);

            o.$this.val(o.maskedValue);
            this._setRange(o._this, o.maskedValue.length - o.charsFromRight);
            this._checkCurrencyRange(o);

            o.forceMask       = false;
            o.forceDecimalPos = false;

            return true;
        }, // _onKeyPress

        /**
         * Handles the onKeyUp event of the input element.
         */
        _onKeyUp: function(e, o) {
            if (o.isSpecial && 8 != o.pressedKey && 46 != o.pressedKey) {
                return true;
            }

            if (this._isNotText(o._this)) {
                return true;
            }

            o.newValue      = o.isSpecial ? this._getNewValue(o) : o.originalValue;
            o.newValue      = this._removeCurrencySymbol(o.newValue, o);
            o.newValue      = this._removeNegativeSymbol(o.newValue, o);
            o.maskedValue   = this._maskValue(o);
            o.maskedValue   = this._addNegativeSymbol(o.maskedValue, o);
            o.maskedValue   = this._addCurrencySymbol(o.maskedValue, o);

            if (o.maskedValue != o.$this.val()) {
                o.$this.val(o.maskedValue);
                this._setRange(o._this, o.maskedValue.length - o.charsFromRight);
            }

            o.completedZero = false;

            this._autoTab(o);

            return true;
        }, // _onKeyUp

        /**
         * Handles the onPaste event of the input element.
         */
        _onPaste: function(e, o) {
            return this._onKeyUp(e, o);
        }, // _onPaste

        /**
         * Prepares all the information that will be passed to the event
         * handlers.
         */
        _preparePasteEvent: function(e) {
            setTimeout(function () { e.data.element._prepareEvent(e); }, 1);
        }, // _preparePasteEvent

        /**
         * Prepares all the information that will be passed to the event
         * handlers.
         */
        _prepareEvent: function(e) {
            var _this = e.data.element,
                o     = e.data.settings;

            o._this          = e.target;
            o.$this          = $(o._this);
            o.pressedKey     = _this._getPressedKey(e);
            o.pressedChar    = String.fromCharCode(o.pressedKey);
            o.originalValue  = o.$this.val();
            if ('select' == o._this.type || 'select-one' == o._this.type || 'select-multiple' == o._this.type || 'checkbox' == o._this.type) {
                o.range          = 0;
                o.charsFromRight = 0;
            }
            else {
                o.range          = _this._getRange(o._this, e);
                o.charsFromRight = o.originalValue.length - o.range.end;
            }

            e.data.callEvent.call(_this, e, o);
        }, // _prepareEvent

        //---------- Mask methods --------------------------------------------//

        /**
         * Adds the currency symbol of a value.
         */
        _addCurrencySymbol: function(sValue, o) {
            if ('' == sValue) {
                return '';
            }
            else if ('currency' != o.datatype) {
                return sValue;
            }

            return ('left' == o.currencyPosition)
                   ? o.currencySymbol + ' ' + sValue
                   : sValue + ' ' + o.currencySymbol;
        }, // _addCurrencySymbol

        /**
         * Adds the currency symbol of a value.
         */
        _addNegativeSymbol: function(sValue, o) {
            if (!o.isNumeric || !o.allowNegative) {
                return sValue;
            }

            if (o.isNegative) {
                o.$this.attr('maxlength', o.maxLength + 1);
                return '-' + sValue;
            }
            else {
                o.$this.attr('maxlength', o.maxLength);
                return sValue;
            }
        }, // _addNegativeSymbol

        /**
         * Applies the current mask to a clean value.
         */
        _applyMask: function(o, sCleanValue) {
            var aCleanArray  = sCleanValue.split(''),
                sMaskedValue = '',
                aMaskArray   = this._getMaskArray(o, sCleanValue.length);

            if ('left' == o.maskOrientation) {
                aMaskArray.reverse();
                aCleanArray.reverse();
            }

            o.invalidCount = 0;

            for (var i = 0; i < aMaskArray.length; i++) {
                if (-1 < $.inArray(aMaskArray[i], o.maskCharsArray)) {
                    sMaskedValue += aMaskArray[i];
                }
                else {
                    bGetChar = true;

                    while (bGetChar) {
                        sChar = aCleanArray.shift();

                        if (this._testChar(sChar, aMaskArray[i])) {
                            bGetChar = false;
                        }
                        else {
                            o.invalidCount++;
                            sChar = '';
                        }

                        if (0 == aCleanArray.length) {
                            bGetChar = false;
                        }
                    }

                    sMaskedValue += sChar;
                }

                if (0 == aCleanArray.length) {
                    break;
                }
            }

            if ('left' == o.maskOrientation) {
                sMaskedValue = this._strReverse(sMaskedValue);
            }

            return this._matchCase(o, sMaskedValue);
        }, // _applyMask

        /**
         * Checks if the cursor is positioned at the right place when displaying
         * the currency symbol.
         */
        _checkCurrencyRange: function(o) {
            if ('currency' != o.datatype || '' == o.maskedValue) {
                return;
            }

            var oRange = this._getRange(o._this, null);

            if ('right' == o.currencyPosition) {
                var iValuePos = o.maskedValue.length - o.currencySymbol.length - 1;

                if (oRange.end > iValuePos) {
                    this._setRange(o._this, iValuePos);
                }
            }
        }, // _checkCurrencyRange

        /**
         * Checks if the value has a decimal separator when using manual
         * decimals.
         */
        _checkDecimalsPress: function(o) {
            if (!o.manualDecimals) {
                return;
            }

            var iDecimalPos = o.originalValue.indexOf(o.decimalSep),
                bDecimalKey = o.pressedChar == o.decimalSep,
                bNumberKey  = 48 <= o.pressedKey && 57 >= o.pressedKey,
                aMaskParts  = o.originalMask.split(o.decimalSep);

            if (bNumberKey && -1 == iDecimalPos) {
                o.maskList = aMaskParts[0];
            }
            else if (bDecimalKey && ((-1 == iDecimalPos) || (iDecimalPos >= o.range.start && iDecimalPos <= o.range.end))) {
                var iDecimalSize  = o.originalValue.length - o.range.end;
                o.forceMask       = true;
                o.forceDecimalPos = true;

                if (0 == iDecimalSize) {
                    o.maskList = aMaskParts[0] + o.decimalSep;
                }
                else if (iDecimalSize < o.precision) {
                    o.maskList = aMaskParts[0] + o.decimalSep + aMaskParts[1].substr(0, iDecimalSize);
                }
                else {
                    o.maskList = o.originalMask;
                }
            }
            else if (bNumberKey && o.range.start == o.range.end && o.range.end > iDecimalPos) {
                var iDecimalSize  = o.originalValue.length - iDecimalPos;
                o.forceMask       = true;

                if (o.precision == iDecimalSize) {
                    o.maskList = o.originalMask;
                }
                else {
                    o.maskList = aMaskParts[0] + o.decimalSep + aMaskParts[1].substr(0, iDecimalSize);
                }
            }
            else if (bNumberKey && o.range.start == o.range.end && o.range.end <= iDecimalPos) {
                var iDecimalSize  = o.originalValue.length - iDecimalPos;
                o.forceMask       = true;

                if (o.precision == iDecimalSize - 1) {
                    o.maskList = o.originalMask;
                }
                else {
                    o.maskList = aMaskParts[0] + o.decimalSep + aMaskParts[1].substr(0, iDecimalSize - 1);
                }
            }
            else if (!bNumberKey && -1 < iDecimalPos) {
                var iDecimalSize  = o.originalValue.length - iDecimalPos;
                o.forceMask       = true;

                if (o.precision == iDecimalSize) {
                    o.maskList = o.originalMask;
                }
                else {
                    o.maskList = aMaskParts[0] + o.decimalSep + aMaskParts[1].substr(0, iDecimalSize);
                }
            }

            o.maxLength = o.maskList.length;
        }, // _checkDecimalsPress

        /**
         * Completes a decimal value with trailing zeros.
         */
        _completeDecimal: function(o) {
            if (1 > o.precision) {
                return;
            }

            var sValue = o.$this.val();

            sValue = this._removeCurrencySymbol(sValue, o);

            if ('' != sValue) {
                if (-1 == sValue.indexOf(o.decimalSep)) {
                    sValue += o.decimalSep + this._strRepeat('0', o.precision)
                }
                else {
                    var aParts = sValue.split(o.decimalSep);

                    if (aParts[1].length < o.precision) {
                        aParts[1] += this._strRepeat('0', o.precision - aParts[1].length);
                        sValue     = aParts.join(o.decimalSep);
                    }
                }
            }

            sValue = this._addCurrencySymbol(sValue, o);

            o.$this.val(sValue);
        }, // _completeDecimal

        /**
         * Creates a decimal mask based on the input length 'iLength', the
         * decimal separator 'sDecimal', the thousands separator 'sThousands'
         * and the precision 'iPrecision'.
         */
        _createDecimalMask: function(iLength, sFormat, sDecimal, sThousands, iPrecision) {
            var sDecimals = '';

            if (0 < iPrecision) {
                iLength  -= iPrecision;
                sDecimals = sDecimal + this._strRepeat('9', iPrecision)
            }

            return this._createIntegerMask(iLength, sFormat, sThousands) + sDecimals;
        }, // _createDecimalMask

        /**
         * Creates a date mask based on the input format 'sFormat' and the date
         * separator 'sSeparator'.
         */
        _createDateMask: function(sFormat, sSeparator) {
            var aMask = new Array();

            sFormat = sFormat.replace(/dd/i,   'd')
                             .replace(/mm/i,   'm')
                             .replace(/yyyy/i, 'y')
                             .replace(/aaaa/i, 'y');

            for (var i = 0; i < sFormat.length; i++) {
                switch (sFormat.charAt(i)) {
                    case 'd':
                    case 'm':
                        aMask.push('99');
                        break;

                    case 'y':
                        aMask.push('9999');
                        break;
                }
            }

            return aMask.join(sSeparator);
        }, // _createDateMask

        /**
         * Creates a date & time mask based on the input format 'sFormat' and
         * the date separator 'sDateSep' and time separator 'sTimeSep'.
         */
        _createDateTimeMask: function(sFormat, sDateSep, sTimeSep) {
            var aPartsFormat = sFormat.split(';');

            return this._createDateMask(aPartsFormat[0], sDateSep) +
                   ' ' +
                   this._createTimeMask(aPartsFormat[1], sTimeSep);
        }, // _createDateTimeMask

        /**
         * Creates an integer mask based on the input length 'iLength' and the
         * thousands separator 'sThousands' according to a given format.
         *
         * sFormat = 1:  9,999,999,999
         * sFormat = 2:    9999999,999
         * sFormat = 3: 9,99,99,99,999
         */
        _createIntegerMask: function(iLength, sFormat, sThousands) {
            var aMask = new Array();

            if (1 == sFormat) {
                var iRepeat   = Math.floor(iLength / 3),
                    iComplete = iLength % 3;

                if (0 != iComplete) {
                    aMask.push(this._strRepeat('9', iComplete));
                }
                for (var i = 0; i < iRepeat; i++) {
                    aMask.push('999');
                }
            }
            else if (2 == sFormat) {
                if (4 > iLength) {
                    aMask.push(this._strRepeat('9', iLength));
                }
                else {
                    aMask.push(this._strRepeat('9', iLength - 3));
                    aMask.push(this._strRepeat('9', 3));
                }
            }
            else if (3 == sFormat) {
                if (4 > iLength) {
                    aMask.push(this._strRepeat('9', iLength));
                }
                else {
                    var iRepeat   = Math.floor((iLength - 3) / 2),
                        iComplete = (iLength - 3) % 2;

                    if (0 != iComplete) {
                        aMask.push(this._strRepeat('9', iComplete));
                    }
                    for (var i = 0; i < iRepeat; i++) {
                        aMask.push('99');
                    }
                    aMask.push(this._strRepeat('9', 3));
                }
            }

            return aMask.join(sThousands);
        }, // _createIntegerMask

        /**
         * Creates a time mask based on the input format 'sFormat' and the time
         * separator 'sSeparator'.
         */
        _createTimeMask: function(sFormat, sSeparator) {
            var aMask = new Array();

            sFormat = sFormat.replace(/hh/i, 'h')
                             .replace(/ii/i, 'i')
                             .replace(/ss/i, 's');

            for (var i = 0; i < sFormat.length; i++) {
                switch (sFormat.charAt(i)) {
                    case 'h':
                    case 'i':
                    case 's':
                        aMask.push('99');
                        break;
                }
            }

            return aMask.join(sSeparator);
        }, // _createTimeMask

        /**
         * Removes unallowed characters from the value.
         */
        _filterUnallowedChars: function(o, sCleanValue) {
            if ('' == o.allowedChars) {
                return this._matchCase(o, sCleanValue);
            }

            var sFiltered = '';

            for (var i = 0; i < sCleanValue.length; i++) {
                if (-1 < o.allowedChars.indexOf(sCleanValue.charAt(i).toLowerCase())) {
                    sFiltered += sCleanValue.charAt(i);
                }
            }

            return this._matchCase(o, sFiltered);
        }, // _filterUnallowedChars

        /**
         * Gets the longest size of the mask list.
         */
        _getLongestMaskSize: function(sList, sChars) {
            var aChars = sChars.split(''),
                aList  = sList.split(';'),
                iMax   = 0;

            for (var i = 0; i < aList.length; i++) {
                iMax = Math.max(iMax, this._removeMaskChars(aList[i], aChars).length);
            }

            return iMax;
        }, // _getLongestMaskSize

        /**
         * Gets the value without any mask characters.
         */
        _getCleanValue: function(o) {
            var sCleanValue = this._removeMaskChars(o.newValue, o.maskCharsArray);

            if ('integer' == o.datatype) {
                sCleanValue = this._removeZeros(o, sCleanValue, 0);
            }
            else if ('decimal' == o.datatype || 'currency' == o.datatype) {
                sCleanValue = this._removeZeros(o, sCleanValue, o.precision);
            }

            return sCleanValue;
        }, // _getCleanValue

        /**
         * Gets the mask array to be used to format the input value.
         */
        _getMaskArray: function(o, iValueLength) {
            if ('' != o.maskList) {
                var aMaskArray = o.maskList.split(';');
            }
            else {
                var aMaskArray = new Array();
            }

            o.mask = '';
            if (1 == aMaskArray.length) {
                o.mask = aMaskArray[0];
            }
            else if (1 < aMaskArray.length) {
                for (var i = 0; i < aMaskArray.length; i++) {
                    sCleanMask  = this._removeMaskChars(o.mask       , o.maskCharsArray);
                    sCleanMaskI = this._removeMaskChars(aMaskArray[i], o.maskCharsArray);

                    if ('' == o.mask) {
                        o.mask = aMaskArray[i];
                    }
                    else if (sCleanMask.length < sCleanMaskI.length && sCleanMask.length < iValueLength) {
                        o.mask = aMaskArray[i];
                    }
                }
            }

            return o.mask.split('');
        }, // _getMaskArray

        /**
         * Gets the input value with the new pressed key.
         */
        _getNewValue: function(o) {
            if (o.isSpecial) {
                return o.originalValue;
            }

            var sNewValue = o.originalValue.substr(0, o.range.start)
                          + o.pressedChar
                          + o.originalValue.substr(o.range.end);

            return sNewValue;
        }, // _getNewValue

        /**
         * Masks a value according to its configuration.
         */
        _maskValue: function(o) {
            if ('currency' == o.datatype && !o.manualDecimals && o.newValue.length > (o.maxLength - o.currencySymbol.length - 1)) {
                var sRetValue = this._removeCurrencySymbol(o.originalValue, o);
                sRetValue     = this._removeNegativeSymbol(sRetValue, o);
                return sRetValue;
            }
            else if (o.newValue.length > o.maxLength && o.newValue.substr(0,1) == 0) {
                 o.newValue = o.newValue.substr(1);
            }
            else if (o.newValue.length > o.maxLength) {
                var sRetValue = this._removeCurrencySymbol(o.originalValue, o);
                sRetValue     = this._removeNegativeSymbol(sRetValue, o);
                return sRetValue;
            }

            var sCleanValue = this._getCleanValue(o);

            if ('' == sCleanValue) {
                return '';
            }

            var sMaskedValue = 'text' == o.datatype
                             ? this._filterUnallowedChars(o, sCleanValue)
                             : this._applyMask(o, sCleanValue);

            return sMaskedValue;
        }, // _maskValue

        /**
         *
         */
        _matchCase: function(o, sValue) {
            if ('' == sValue || '' == o.lettersCase) {
                return sValue;
            }

            var sNew = '';

            if ('first' == o.lettersCase) {
                var iPos = 0;

                while (iPos < sValue.length && ' ' == sValue.charAt(iPos)) {
                    sNew += sValue.charAt(iPos++);
                }
                if (iPos < sValue.length) {
                    sNew += sValue.charAt(iPos).toUpperCase() + sValue.substr(iPos + 1);
                }
            }
            else if ('upper' == o.lettersCase || 'lower' == o.lettersCase) {
                for (var i = 0; i < sValue.length; i++) {
                    sNew += ('upper' == o.lettersCase)
                          ? sValue.charAt(i).toUpperCase()
                          : sValue.charAt(i).toLowerCase();
                }
            }
            else if ('words' == o.lettersCase) {
                var aParts = sValue.split(' ');

                for (var i = 0; i < aParts.length; i++) {
                    if (0 < aParts[i].length) {
                        aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
                    }
                }
                sNew = aParts.join(' ');
            }

            return sNew;
        }, // _matchCase

        /**
         * Removes the currency symbol of a value.
         */
        _removeCurrencySymbol: function(sValue, o) {
            if ('currency' != o.datatype) {
                return sValue;
            }

            if ('left' == o.currencyPosition) {
                if (sValue.substr(0, o.currencySymbol.length + 1) == o.currencySymbol + ' ') {
                    return sValue.substr(o.currencySymbol.length + 1);
                }
                else if (sValue.substr(0, o.currencySymbol.length) == o.currencySymbol) {
                    return sValue.substr(o.currencySymbol.length);
                }
            }
            else if ('right' == o.currencyPosition) {
                var sCurStr = sValue.substr(sValue.length - o.currencySymbol.length - 2),
                    iCurPos = sCurStr.indexOf('-');
                if (-1 < iCurPos) {
                    sValue = sValue.substr(0, sValue.length - o.currencySymbol.length - 2) + '-'
                            + sCurStr.substr(0, iCurPos) + sCurStr.substr(iCurPos + 1);
                }

                if (sValue.substr(sValue.length - o.currencySymbol.length - 1) == o.currencySymbol + ' ') {
                    return sValue.substr(0, sValue.length - o.currencySymbol.length - 2);
                }
                else if (sValue.substr(sValue.length - o.currencySymbol.length) == o.currencySymbol) {
                    return sValue.substr(0, sValue.length - o.currencySymbol.length - 1);
                }
            }

            var aValue  = sValue.split(''),
                sSymbol = o.currencySymbol + ' ',
                aSymbol = sSymbol.split('');
                sNew    = '';

            for (var i = 0; i < aValue.length; i++) {
                if (-1 == $.inArray(sValue.charAt(i), aSymbol)) {
                    sNew += sValue.charAt(i);
                }
            }

            return sNew;
        }, // _removeCurrencySymbol

        /**
         * Removes the negative symbol of a value.
         */
        _removeNegativeSymbol: function(sValue, o) {
            if (!o.isNumeric) {
                return sValue;
            }

            var hasMinus = false;

            while (-1 != (iPos = sValue.indexOf('-'))) {
                sValue   = sValue.substr(0, iPos) + sValue.substr(iPos + 1);
                hasMinus = !hasMinus;
            }

            if (o.onlyNegative) {
                o.isNegative = true;
            }
            else if (!hasMinus && '-' == o.pressedChar) {
                o.isNegative = true;
            }
            else if (hasMinus && '-' == o.pressedChar) {
                o.isNegative = false;
            }
            else if (hasMinus && '+' == o.pressedChar) {
                o.isNegative = false;
            }
            else {
                o.isNegative = hasMinus;
            }

            return sValue;
        }, // _removeNegativeSymbol

        /**
         * Removes all mask characters leaving only the clean value.
         */
        _removeMaskChars: function(sOriginalValue, aMaskCharsArray) {
            var sNewValue = '';

            for (var i = 0; i < sOriginalValue.length; i++) {
                if (-1 == $.inArray(sOriginalValue.charAt(i), aMaskCharsArray)) {
                    sNewValue += sOriginalValue.charAt(i);
                }
            }

            return sNewValue;
        }, // _removeMaskChars

        /**
         * Removes the extra zeros on the left for numerical values.
         */
        _removeZeros: function(o, sValue, iPrecision) {
            if ('' == sValue) {
                return sValue;
            }

            while ('0' == sValue.substr(0, 1) && iPrecision + 1 < sValue.length) {
                sValue = sValue.substr(1);
            }

            o.completedZero = false;
            if (!o.manualDecimals && 0 < iPrecision && iPrecision + 1 > sValue.length) {
                sValue          = this._strRepeat('0', iPrecision + 1 - sValue.length) + sValue;
                o.completedZero = true;
            }

            return sValue;
        }, // _removeZeros

        /**
         * Tests if a character 'sChar' is valid.
         */
        _testChar: function(sChar, sType) {
            if ('9' == sType) {
                oRegExp = new RegExp('[0-9]');
                return oRegExp.test(sChar);
            }
            else if ('a' == sType) {
                oRegExp = new RegExp('[a-zA-Z]');
                return oRegExp.test(sChar);
            }
            else if ('*' == sType) {
                oRegExp = new RegExp('[0-9a-zA-Z]');
                return oRegExp.test(sChar);
            }

            return sChar == sType;
        }, // _testChar

        //---------- Watermark methods ---------------------------------------//

        /**
         * Applies the watermark to the input element.
         */
        _applyWatermark: function($oElement, $oClone) {
            if (null == $oClone) {
                return;
            }

            if ('' == $oElement.val()) {
                $oElement.hide();
                $oClone.show();
            }
            else {
                $oElement.show();
                $oClone.hide();
            }
        }, // _applyWatermark

        /**
         * Creates the clone object that will display the watermark.
         */
        _createWatermark: function($oElement, sType, oSettings) {
            if ('' == oSettings.watermark) {
                return;
            }

            oSettings.$clone = $oElement.clone();

            var $oClone = oSettings.$clone;

            if ('password' == $oClone[0].type) {
                $oClone[0].type = 'text';
            }

            $oClone.attr('name', 'sc_clone_' + $oElement.attr('name'))
                   .val(oSettings.watermark)
                   .addClass(oSettings.watermarkClass)
                   .insertAfter($oElement)
                   .bind('focus', {element: $oElement, clone: $oClone}, this._removeWatermark);

            this._applyWatermark($oElement, $oClone);
        }, // _createWatermark

        /**
         * Removes the watermark of the input element.
         */
        _removeWatermark: function(e) {
            var $oElement = e.data.element,
                $oClone   = e.data.clone;

            if (null == $oClone) {
                return;
            }

            $oClone.hide();
            $oElement.show().focus().trigger('focus');
        }, // _removeWatermark

        //---------- Auto tab methods ----------------------------------------//

        /**
         * Auto tabs to the next form element.
         */
        _autoTab: function(o) {
            if (!o.autoTab) {
                return;
            }
            else if (o.isSpecial || 16 == o.pressedKey) {
                return;
            }
            else if ('decimal' == o.datatype || 'currency' == o.datatype) {
                var iPos = o.maskedValue.indexOf(o.decimalSep);

                if (-1 == iPos) {
                    return;
                }
                else if (o.maskedValue.substr(iPos + 1).length < o.precision) {
                    return;
                }
            }

            if ('mask' == o.datatype && o.longestMask <= this._removeMaskChars(o.maskedValue, o.maskChars.split('')).length) {
                this._goToNextElement(o);
            }
            else if ('mask' != o.datatype && !o.isNegative && o.maskedValue.length >= o.maxLength) {
                this._goToNextElement(o);
            }
            else if ('mask' != o.datatype && o.isNegative && (o.maskedValue.length - 1) >= o.maxLength) {
                this._goToNextElement(o);
            }
        }, // _autoTab

        /**
         * Gets the next form element if it exists.
         */
        _getNextElement: function(o) {
            var oInput      = o._this,
                oElements   = oInput.form.elements,
                iInputIndex = $.inArray(oInput, oElements) + 1;

            for (var i = iInputIndex; i < oElements.length; i++) {
                var $oTestInput = $(oElements[i]);

                if (this._isValidInput($oTestInput)) {
                    return $oTestInput;
                }
            }

            var oForms     = document.forms,
                iFormIndex = $.inArray(oInput.form, oForms) + 1;

            for (var j = iFormIndex; j < oForms.length; j++) {
                oElements = oForms[j].elements;

                for (i = 0; i < oElements.length; i++) {
                    $oTestInput = $(oElements[i]);

                    if (this._isValidInput($oTestInput)) {
                        return $oTestInput;
                    }
                }
            }

            return null;
        }, // _getNextElement

        /**
         * Moves the focus to the next form element.
         */
        _goToNextElement: function(o) {
            if ('textarea' == o._this.type) {
                return false;
            }

            $oNext = this._getNextElement(o);

            if ($oNext) {
                //o.$this.trigger('blur');
                $oNext.focus();

                return this._isNotText(o._this);
            }

            return false;
        }, // _goToNextElement

        /**
         * Tests if an input is a valid one to focus to.
         */
        _isValidInput: function(o) {
            var oInput = o.get(0);

            if (0 >= oInput.offsetHeight || 0 >= oInput.offsetWidth || oInput.disabled) {
                return false;
            }

            if ("file" == oInput.type) {
                return false;
            }

            return true;
        }, // _isValidInput

        //---------- Auxiliary methods ---------------------------------------//

        /**
         * Gets the maximum length of an input element.
         */
        _getMaxLength: function(oSettings, $oElement) {
            var iMaxLength = 0;

            if ('' != oSettings.maskList) {
                var aMaskList = oSettings.maskList.split(';');

                for (var i = 0; i < aMaskList.length; i++) {
                    iMaxLength = Math.max(iMaxLength, aMaskList[i].length);
                }
            }

            if (0 == iMaxLength) {
                if (0 < oSettings.maxLength) {
                    return oSettings.maxLength;
                }
                if ($oElement.attr('maxlength') && 0 < $oElement.attr('maxlength')) {
                    return $oElement.attr('maxlength');
                }
                if ($oElement.attr('size') && 0 < $oElement.attr('size')) {
                    return $oElement.attr('size');
                }
            }

            if ('currency' == oSettings.datatype) {
                iMaxLength += oSettings.currencySymbol.length + 1;
            }

            return iMaxLength;
        }, // _getMaxLength

        /**
         * Gets the code of the pressed key.
         */
        _getPressedKey: function(e) {
            return e.charCode || e.keyCode || e.which;
        }, // _getPressedKey

        /**
         * Gets the range selection info of the input element.
         */
        _getRange: function(oInput, e) {
            if (!$.browser.msie) {
                return {
                    start: oInput.selectionStart,
                    end  : oInput.selectionEnd
                };
            }

            var oPos = {start: 0, end: 0};

            if ('textarea' == oInput.type && 'blur' != e.type) {
                var oRange = $(oInput).getSelection();

                oPos.start = oRange.start;
                oPos.end   = oRange.end;
            }
            else {
                var oRange = document.selection.createRange();

                oPos.start = 0 - oRange.duplicate().moveStart('character', -100000);
                oPos.end   = oPos.start + oRange.text.length;
            }

            return oPos;
        }, // _getRange

        /**
         * Checks if the 'enter' tab option is enabled and which key was
         * pressed.
         */
        _isEnterTab: function(o, sKey) {
            if (!o.enterTab) {
                return false;
            }

            if ('enter' == sKey) {
                return 13 == o.pressedKey;
            }
            else {
                return 9 == o.pressedKey;
            }
        }, // _isEnterTab

        /**
         * Checks if the 'enter' submit option is enabled and which key was
         * pressed.
         */
        _isEnterSubmit: function(o, sKey) {
            if (!o.enterSubmit) {
                return false;
            }

            if ('enter' == sKey) {
                return 13 == o.pressedKey;
            }
            else {
                return 9 == o.pressedKey;
            }
        }, // _isEnterSubmit

        _isNotText: function(o) {
            return ('select' == o.type || 'select-one' == o.type || 'select-multiple' == o.type || 'checkbox' == o.type);
        }, // _isNotText

        /**
         * Loads the options for the input element '$oElement'. Looks for
         * options defined via its 'alt' attribute to extend the default values.
         * Then completes the options according to the input datatype.
         */
        _loadOptions: function($oElement) {
            if ($oElement.attr('alt')) {
                eval('var oMetadata = ' + $oElement.attr('alt'));
            }
            else {
                var oMetadata = {};
            }

            var oSettings = $.extend({}, this.defaults, oMetadata);

            oSettings = this._setDatatypeSettings(oSettings);

            oSettings.maxLength      = this._getMaxLength(oSettings, $oElement);
            oSettings.maskCharsArray = oSettings.maskChars.split('');

            return oSettings;
        }, // _loadOptions

        /**
         * Sets the parameters depending on the input element data type.
         */
        _setDatatypeSettings: function(o) {
            if ('currency' == o.datatype && '' == o.currencySymbol) {
                o.datatype = 'decimal';
            }

            if (('currency' == o.datatype || 'decimal' == o.datatype) && 0 == o.precision) {
                o.datatype = 'integer';
            }

            if ('currency' != o.datatype && 'decimal' != o.datatype) {
                o.manualDecimals = false;
            }

            switch (o.datatype) {
                case 'integer':
                    if ('left' != o.alignment && 'center' != o.alignment && 'justify' != o.alignment) {
                        o.alignRight = true;
                    }
                    o.isNumeric       = true;
                    o.maskList        = this._createIntegerMask(o.maxLength, o.thousandsFormat, o.thousandsSep);
                    o.maskOrientation = 'left';
                    o.maskChars       = o.thousandsSep;
                    o.originalMask    = o.maskList;
                    break;

                case 'decimal':
                    if ('left' != o.alignment && 'center' != o.alignment && 'justify' != o.alignment) {
                        o.alignRight = true;
                    }
                    o.isNumeric       = true;
                    o.maskList        = this._createDecimalMask(o.maxLength, o.thousandsFormat, o.decimalSep, o.thousandsSep, o.precision);
                    o.maskOrientation = 'left';
                    o.maskChars       = o.decimalSep + o.thousandsSep;
                    o.originalMask    = o.maskList;
                    break;

                case 'currency':
                    if ('left' != o.alignment && 'center' != o.alignment && 'justify' != o.alignment) {
                        o.alignRight = true;
                    }
                    o.isNumeric       = true;
                    o.maskList        = this._createDecimalMask(o.maxLength, o.thousandsFormat, o.decimalSep, o.thousandsSep, o.precision);
                    o.maskOrientation = 'left';
                    o.maskChars       = o.decimalSep + o.thousandsSep + o.currencySymbol + ' ';
                    o.originalMask    = o.maskList;
                    break;

                case 'datetime':
                    o.allowNegative = false;
                    o.maskList      = this._createDateTimeMask(o.dateFormat, o.dateSep, o.timeSep);
                    o.maskChars     = o.dateSep + o.timeSep + ' ';
                    o.originalMask  = o.maskList;
                    break;

                case 'date':
                    o.allowNegative = false;
                    o.maskList      = this._createDateMask(o.dateFormat, o.dateSep);
                    o.maskChars     = o.dateSep;
                    o.originalMask  = o.maskList;
                    break;

                case 'time':
                    o.allowNegative = false;
                    o.maskList      = this._createTimeMask(o.timeFormat, o.timeSep);
                    o.maskChars     = o.timeSep;
                    o.originalMask  = o.maskList;
                    break;

                case 'mask':
                    o.allowNegative = false;
                    o.maskList      = o.maskList;
                    o.longestMask   = this._getLongestMaskSize(o.maskList, o.maskChars);
                    o.originalMask    = o.maskList;
                    break;

                case 'text':
                    o.allowedChars = o.allowedChars.toLowerCase();
                    o.maskChars    = '';
                    break;

                default:
                    o.allowNegative = false;
                    o.maskList      = this.masks[o.datatype] ? this.masks[o.datatype] : '';
                    o.originalMask  = o.maskList;
                    break;
            }

            return o;
        }, // _setDatatypeSettings

        /**
         * Sets the range selection info of the input element.
         */
        _setRange: function(oInput, iStart, iEnd) {
            if ('undefined' == typeof iEnd) {
                iEnd = iStart;
            }

            if (oInput.setSelectionRange) {
                oInput.setSelectionRange(iStart, iEnd);
            }
            else {
                var oRange = oInput.createTextRange();
                oRange.collapse();
                oRange.moveStart('character', iStart);
                oRange.moveEnd('character', iEnd - iStart);
                oRange.select();
            }
        }, // _setRange

        /**
         * Repeats a string 's' for 'n' times.
         */
        _strRepeat: function(s, n) {
            return new Array(n + 1).join(s);
        }, // _strRepeat

        /**
         * Reverses a string 's'.
         */
        _strReverse: function(s) {
            return s.split('').reverse().join('');
        }, // _strReverse

        _debug: function(s) {
            n = $('#id-debug').html();
            $('#id-debug').html('' == n ? s : n + "\n" + s);
        },

        //---------- Constants -----------------------------------------------//

        /**
         * List of predefined masks.
         */
        masks: {
            'cep' : '99.999-999',
            'cpf' : '999.999.999-99',
            'cnpj': '99.999.999/9999-99',
            'ssn' : '999-99-9999',
            'cc'  : '9999 9999 9999 9999'
        }, // masks

        /**
         * Default values for the input element behavior.
         */
        defaults: {
            // General
            datatype        : 'text',
            maxLength       : 0,
            alignment       : '',
            alignRight      : false,
            enterTab        : false,
            enterSubmit     : false,
            selectOnFocus   : true,
            lettersCase     : '',
            // Mask
            mask            : '',
            maskChars       : '(){}[].,;:-+/ ',
            maskList        : '',
            maskOrientation : 'right',
            allowedChars    : '',
            decimalSep      : '.',
            thousandsSep    : ',',
            precision       : 2,
            dateFormat      : 'mdy',
            dateSep         : '/',
            timeFormat      : 'his',
            timeSep         : ':',
            currencySymbol  : '',
            currencyPosition: 'left',
            allowNegative   : true,
            onlyNegative    : false,
            manualDecimals  : false,
            thousandsFormat : 1,
            // Auto tab
            autoTab         : false,
            // Watermark
            watermark       : '',
            watermarkClass  : '',
            // Internal
            $clone          : null,
            completedZero   : false,
            invalidCount    : 0,
            inputFocusValue : '',
            isNegative      : false,
            isNumeric       : false,
            pressedMinus    : false,
            hasDecimal      : -1,
            originalMask    : '',
            forceMask       : false,
            forceDecimalPos : false,
            longestMask     : 0
        }, // defaults

        /**
         * List of special keys.
         */
        specialKeys: {
            8  : 'backspace',
            9  : 'tab',
            13 : 'enter',
            16 : 'shift',
            17 : 'control',
            18 : 'alt',
            27 : 'esc',
            33 : 'page up',
            34 : 'page down',
            35 : 'end',
            36 : 'home',
            37 : 'left',
            38 : 'up',
            39 : 'right',
            40 : 'down',
            45 : 'insert',
            46 : 'delete',
            93 : 'menu',
            116: 'f5',
            123: 'f12',
            224: 'command'
        } // specialKeys

    }; // scInput

}) (jQuery);