﻿// Tipos de Eventos e Validações Existentes no objeto MaskedEdit
EventValidation = new Object();
EventValidation.noValidation = 0;
EventValidation.numbersOnly = 1;
EventValidation.currency = 2;
EventValidation.lettersOnly = 3;
EventValidation.date = 4;
EventValidation.time = 5;
EventValidation.dateTime = 6;
EventValidation.isValid = function(val)
{
    var bResult = false;
    if (typeof(val) == "number")
    {
        for (k in EventValidation)
        {
            if (typeof(EventValidation[k]) == "number")
                if (EventValidation[k] === val)
                    bResult = true;
        }
    }
    return bResult;
};

EventType = new Object();
EventType.undefinedEvent = -1;
EventType.defaultKeyPress = 0;
EventType.maskedKeyPress = 1;
EventType.defaultOnChange = 2;
EventType.maskedOnChange = 3;
EventType.defaultOnPaste = 4;
EventType.maskedOnPaste = 5;
EventType.isValid = function(val)
{
    var bResult = false;
    if (typeof(val) == "number")
    {
        for (k in EventType)
        {
            if (typeof(EventType[k]) == "number")
                if (EventType[k] === val)
                    bResult = true;
        }
    }
    return bResult;
};

// Handler do Evento de KeyPress
function HTMLEvent(evValidation, evType)
{
    this.validation = evValidation;
    this.evType = evType;
    this.fn = null;
    this.src = null;
    this.index = -1;
}
HTMLEvent.prototype.baseFunc = function()
{
    if (arguments.length != 5)
        throw "Erro Interno: número inválido de argumentos em HTMLEvent.baseFunc";
    
    var ev = arguments[0];
    var sender = arguments[1];
    var evType = arguments[2];
    var evIndex = arguments[3];
    var evValidation = arguments[4];
    var evList = null;
    var evFunc = null;
    
    // Alguma validação anterior já invalidou a cadeia de eventos e validações,
    // podemos retornar a partir daqui e evitar overhead desnecessário
    if (ev.returnValue === false)
        return false;
    
    switch(evType)
    {
        case EventType.defaultKeyPress:
        case EventType.maskedKeyPress:
            evList = sender._arrOldKeyPresses;
            evFunc = MaskedEdit.fnMaskKeyPress;
            break;
        case EventType.defaultOnChange:
        case EventType.maskedOnChange:
            evList = sender._arrOldOnChanges;
            evFunc = MaskedEdit.fnMaskOnChange;
            break;
        case EventType.defaultOnPaste:
        case EventType.maskedOnPaste:
            evList = sender._arrOldOnPastes;
            evFunc = MaskedEdit.fnMaskOnPaste;
            break;
        default:
            throw "Tipo de evento desconhecido em HTMLEvent.baseFunc";
    }
    
    // Executa a validação de acordo com a combinação de "evType" e "evValidation"
    if (evValidation != EventValidation.noValidation && evFunc)
        evFunc(ev, sender, evType, evValidation);
    
    // Alguma validação anterior já invalidou a cadeia de eventos e validações,
    // podemos retornar a partir daqui e evitar overhead desnecessário
    if (ev.returnValue === false)
        return false;
    
    // Executa os "onkeypress" anteriores (em ordem reversa)
    if (evList)
    {
        if (evList.constructor.toString().indexOf("Array") != -1 && evList.length > 0)
        {
            var bExecuted = false;
            while(evIndex >= 0)
            {
                evIndex--;
                if (evIndex >= 0)
                {
                    if (typeof(evList[evIndex]) == "object" && evList[evIndex].constructor.toString().indexOf("HTMLEvent") != -1)
                    {
                        evList[evIndex].fn(ev);
                        bExecuted = true;
                    }
                }
                if (bExecuted)
                    evIndex = -1;
            }
        }
    }
    
    return ev.returnValue;
};

// Objeto de máscara de digitação para inputs tipo "text"
function MaskedEdit()
{
    this.Text = "";
    this.element = null;
    this.validation = EventValidation.noValidation;
    this._oldKeyPress = null;
    
    if (arguments.length == 0)
        this.element = null;
    else if (arguments.length == 1)
    {
        var el = arguments[0];
        this.attach(el);
    }
    else
        throw "Número incorreto de argumentos ao tentar instanciar objeto MaskedEdit.";
}

// Assinatura dos métodos utilizados para atribuição de eventos,
// para diferenciar dos eventos nativos dos controles dos eventos de validação
MaskedEdit.prototype.__evSig_KeyPress__ = "MaskedEdit.prototype.__evSig_KeyPress__";
MaskedEdit.prototype.__evSig_OnChange__ = "MaskedEdit.prototype.__evSig_OnChange__";
MaskedEdit.prototype.__evSig_OnPaste__ = "MaskedEdit.prototype.__evSig_OnPaste__";

// Regular Expressions utilizadas na validação de digitação e do conteúdo completo do input
MaskedEdit.prototype._REGEX_IS_SPECIALKEY = /[\x00\x08\x09\x0D]/;
MaskedEdit.prototype._REGEX_IS_CLIPBOARDKEY = /[cvxz]/i;
MaskedEdit.prototype._REGEX_IS_LETTER = /[a-z]/i;
MaskedEdit.prototype._REGEX_IS_DIGIT = /\d/;
MaskedEdit.prototype._REGEX_IS_SLASH = /\//;
MaskedEdit.prototype._REGEX_IS_BACKSLASH = /\\/;
MaskedEdit.prototype._REGEX_IS_DOT = /\./;
MaskedEdit.prototype._REGEX_IS_DASH = /\-/;
MaskedEdit.prototype._REGEX_IS_COMMA = /\,/;
MaskedEdit.prototype._REGEX_IS_COLON = /\:/;
MaskedEdit.prototype._REGEX_IS_SEMICOLON = /\;/;
MaskedEdit.prototype._REGEX_DIGITSONLY = /^\d*$/;
MaskedEdit.prototype._REGEX_LETTERSONLY = /^[a-z]*$/i;
MaskedEdit.prototype._REGEX_DATEONLY = /^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((1[6-9]|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((1[6-9]|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((1[6-9]|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/;
MaskedEdit.prototype._REGEX_DATETIME = /^(?=\d)(?:(?!(?:(?:0?[5-9]|1[0-4])(?:\.|-|\/)10(?:\.|-|\/)(?:1582))|(?:(?:0?[3-9]|1[0-3])(?:\.|-|\/)0?9(?:\.|-|\/)(?:1752)))(31(?!(?:\.|-|\/)(?:0?[2469]|11))|30(?!(?:\.|-|\/)0?2)|(?:29(?:(?!(?:\.|-|\/)0?2(?:\.|-|\/))|(?=\D0?2\D(?:(?!000[04]|(?:(?:1[^0-6]|[2468][^048]|[3579][^26])00))(?:(?:(?:\d\d)(?:[02468][048]|[13579][26])(?!\x20BC))|(?:00(?:42|3[0369]|2[147]|1[258]|09)\x20BC))))))|2[0-8]|1\d|0?[1-9])([-.\/])(1[012]|(?:0?[1-9]))\2((?=(?:00(?:4[0-5]|[0-3]?\d)\x20BC)|(?:\d{4}(?:$|(?=\x20\d)\x20)))\d{4}(?:\x20BC)?)(?:$|(?=\x20\d)\x20))?((?:(?:0?[1-9]|1[012])(?::[0-5]\d){0,2}(?:\x20[aApP][mM]))|(?:[01]\d|2[0-3])(?::[0-5]\d){1,2})?$/;
MaskedEdit.prototype._REGEX_EMAIL = /^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$/;

// Métodos estáticos de validação
MaskedEdit.fnIsValid = function(sender, evValidation)
{
    var bResult = true;
    switch(evValidation)
    {
        case EventValidation.numbersOnly:
            bResult = MaskedEdit.prototype._REGEX_DIGITSONLY.test(sender.value) || sender.value.length == 0;
        // [TODO]: Aplicar as validações dos outros tipos de validação
    }
    return bResult;
};
MaskedEdit.fnCheckClipboardCode = function(ev, strKey)
{
    if (!$.browser.isIE)
    {
        var bHasCtrl = false;
        if ((typeof(ev.ctrlKey)!='undefined' && ev.ctrlKey) || ( ev.modifiers & Event.CONTROL_MASK))
            bHasCtrl = true;
        
        if (bHasCtrl && MaskedEdit.prototype._REGEX_IS_CLIPBOARDKEY.test(strKey))
            return true;
    }
    
    return false;
};
MaskedEdit.fnMaskOnChange = function(ev, sender, evType, evValidation)
{
    // [TODO]: Aplicar validação do evento "OnChange" para todos os tipos de validação
};
MaskedEdit.fnMaskOnPaste = function(ev, sender, evType, evValidation)
{
    // [TODO]: Aplicar validação do evento "OnPaste" para todos os tipos de validação
};
MaskedEdit.fnMaskKeyPress = function(ev, sender, evType, evValidation)
{
    var bResult = true;
    var rgValidation = null;
    var iKeyCode, strKey, objInput;
    
    iKeyCode = ev.key;
    objInput = sender;
    strKey = String.fromCharCode(iKeyCode);
    
    switch(evValidation)
    {
        case EventValidation.numbersOnly:
            rgValidation = MaskedEdit.prototype._REGEX_IS_DIGIT;
            break;
        case EventValidation.date:
            rgValidation = MaskedEdit.prototype._REGEX_IS_DIGIT;
            if (objInput.value.length == 2 || objInput.value.length == 5)
                rgValidation = MaskedEdit.prototype._REGEX_IS_SLASH;
            break;
        case EventValidation.time:
            rgValidation = MaskedEdit.prototype._REGEX_IS_DIGIT;
            if (objInput.value.length == 2)
                rgValidation = MaskedEdit.prototype._REGEX_IS_COLON;
            break;
        // [TODO]: Aplicar as validações dos outros tipo de validação
        default:
            throw "Erro Interno: tipo de validação não implementada!";
    }
    
    if (MaskedEdit.fnIsValid(objInput.value))
    {
        objInput._validValue = objInput.value;
        
        if (!rgValidation.test(strKey) &&
            !MaskedEdit.prototype._REGEX_IS_SPECIALKEY.test(strKey) && 
            !MaskedEdit.fnCheckClipboardCode(ev, strKey))
            bResult = false;
    }
    else
        bResult = false;
    
    ev.returnValue = bResult && (ev.returnValue !== false);
};

// Evento de OnKeyPress do controle MaskedEdit
MaskedEdit.prototype.kpFunc = function(event)
{
    var fnSignature = MaskedEdit.prototype.__evSig_KeyPress__;
    
    var ev = $.browser.crossBrowserEvent(event);
    var sender = null;
    var evIndex = 0;
    var evType = EventType.maskedKeyPress;
    var evValidation = EventValidation.noValidation;
    var baseFn = null;
    
    if ((this.toString() == "[object HTMLInputElement]" && this.type == "text") ||
        (this.nodeName && this.nodeType && this.type && this.type == "text"))
    {
        sender = this;
        evIndex = sender._arrOldKeyPresses.length;
        evValidation = sender._validation;
        baseFn = HTMLEvent.prototype.baseFunc;
    }
    else
    {
        sender = this.src;
        evIndex = this.index;
        evValidation = this.validation;
        baseFn = this.baseFunc;
    }
    
    return baseFn(ev, sender, evType, evIndex, evValidation);
};
// Evento de OnChange do controle MaskedEdit
MaskedEdit.prototype.ocFunc = function(event)
{
    var fnSignature = MaskedEdit.prototype.__evSig_OnChange__;
    
    var ev = $.browser.crossBrowserEvent(event);
    var sender = null;
    var evIndex = 0;
    var evType = EventType.maskedOnChange;
    var evValidation = EventValidation.noValidation;
    var baseFn = null;
    
    if ((this.toString() == "[object HTMLInputElement]" && this.type == "text") ||
        (this.nodeName && this.nodeType && this.type && this.type == "text"))
    {
        sender = this;
        evIndex = sender._arrOldOnChanges.length;
        evValidation = sender._validation;
        baseFn = HTMLEvent.prototype.baseFunc;
    }
    else
    {
        sender = this.src;
        evIndex = this.index;
        evValidation = this.validation;
        baseFn = this.baseFunc;
    }
    
    baseFn(ev, sender, evType, evIndex, evValidation);
};
// Evento de OnPaste do controle MaskedEdit
MaskedEdit.prototype.opFunc = function(event)
{
    var fnSignature = MaskedEdit.prototype.__evSig_OnPaste__;
    
    var ev = $.browser.crossBrowserEvent(event);
    var sender = null;
    var evIndex = 0;
    var evType = EventType.maskedOnPaste;
    var evValidation = EventValidation.noValidation;
    var baseFn = null;
    
    if ((this.toString() == "[object HTMLInputElement]" && this.type == "text") ||
        (this.nodeName && this.nodeType && this.type && this.type == "text"))
    {
        sender = this;
        evIndex = sender._arrOldOnPastes.length;
        evValidation = sender._validation;
        baseFn = HTMLEvent.prototype.baseFunc;
    }
    else
    {
        sender = this.src;
        evIndex = this.index;
        evValidation = this.validation;
        baseFn = this.baseFunc;
    }
    
    baseFn(ev, sender, evType, evIndex, evValidation);
};

// Métodos e propriedades públicas do controle MaskedEdit
$.browser.addMethod(MaskedEdit.prototype, "setElement", function()
{
    throw "Número incorreto de argumentos ao executar MaskedEdit.setElement";
});
$.browser.addMethod(MaskedEdit.prototype, "setElement", function(el)
{
    if (typeof el == "string")
        this.element = document.getElementById(el) ? document.getElementById(el) : null;
    else if (el)
        this.element = el;
    else
        this.element = null;
    
    // [TODO]: Verificar se o objeto this.element é um INPUT do tipo "text". Caso não seja, lançar uma nova exception
    
    if (this.element && this.element.value)
        this.Text = this.element.value;
    
    return this;
});
$.browser.addMethod(MaskedEdit.prototype, "setType", function()
{
    throw "Número incorreto de argumentos ao executar MaskedEdit.setType";
});
$.browser.addMethod(MaskedEdit.prototype, "setType", function(tp)
{
    this.validation = tp;
    
    // [TODO]: Verificar se this.validation é um tipo EventValidation válido. Caso não seja, lançar uma nova exception
    
    return this;
});
$.browser.addMethod(MaskedEdit.prototype, "attach", function(x, y)
{
    throw "Número incorreto de argumentos ao executar MaskedEdit.attach";
});
$.browser.addMethod(MaskedEdit.prototype, "attach", function()
{
    // [TODO]: Verificar se o objeto this.element é um INPUT do tipo "text". Caso não seja, lançar uma nova exception
    if (!this.element)
        throw "O elemento HTML MaskedEdit.element não foi encontrado";
    
    // [TODO]: Verificar se this.validation é um tipo EventValidation válido. Caso não seja, lançar uma nova exception
    
    var kpEvent = null;
    var ocEvent = null;
    var opEvent = null;
    var evValidation = this.validation;
    var evType = EventType.defaultKeyPress;
    var evIndex = 0;
    var evFunc = null;
    
    // Se ainda não existir o array de keypresses antigos do elemento, crie ele...
    if (!this.element._arrOldKeyPresses || this.element._arrOldKeyPresses.constructor.toString().indexOf("Array") == -1)
    {
        this.element._arrOldKeyPresses = new Array();
        this.element._arrOldOnChanges = new Array();
        this.element._arrOldOnPastes = new Array();
    }
    
    // ***** ON_KEY_PRESS ******************************************************************************************
    // Se houver alguma função no onkeypress atual do elemento, salve a mesma antes de prosseguir...
    if (this.element.onkeypress)
    {
        // o índice atual é sempre o tamanho do array de keypresses
        evIndex = this.element._arrOldKeyPresses.length;
        
        evType = EventType.defaultKeyPress;
        evValidation = EventValidation.noValidation;
        
        // Se não for um "onkeypress" nativo, atualiza o tipo de evento e o tipo de validação
        if ((this.element.onkeypress.toString().indexOf("onkeypress(event)") == -1) && 
            (this.element._validation && this.element.onkeypress.toString().indexOf(MaskedEdit.prototype.__evSig_KeyPress__) != -1))
        {
            evType = EventType.maskedKeyPress;
            evValidation = this.element._validation;
        }
        kpEvent = new HTMLEvent(evValidation, evType);
        kpEvent.index = evIndex;
        kpEvent.src = this.element;
        kpEvent.fn = this.element.onkeypress;
    }
    // Se houver algum evento de keypress pra ser salvo, armazena eles no array do elemento...
    if (kpEvent)
        this.element._arrOldKeyPresses[this.element._arrOldKeyPresses.length] = kpEvent;
        
    // ***** ON_CHANGE ********************************************************************************************
    // Se houver alguma função no onchange atual do elemento, salve a mesma antes de prosseguir...
    if (this.element.onchange)
    {
        // o índice atual é sempre o tamanho do array de onchages
        evIndex = this.element._arrOldOnChanges.length;
        
        evType = EventType.defaultOnChange;
        evValidation = EventValidation.noValidation;
        
        // Se não for um "onchange" nativo, atualiza o tipo de evento e o tipo de validação
        if ((this.element.onchange.toString().indexOf("onchange(event)") == -1) && 
            (this.element._validation && this.element.onchange.toString().indexOf(MaskedEdit.prototype.__evSig_OnChange__) != -1))
        {
            evType = EventType.maskedOnChange;
            evValidation = this.element._validation;
        }
        ocEvent = new HTMLEvent(evValidation, evType);
        ocEvent.index = evIndex;
        ocEvent.src = this.element;
        ocEvent.fn = this.element.onchange;
    }
    // Se houver algum evento de onchange pra ser salvo, armazena eles no array do elemento...
    if (ocEvent)
        this.element._arrOldOnChanges[this.element._arrOldOnChanges.length] = ocEvent;
    
    // ***** ON_PASTE (IE) *******************************************************************************************
    // Se houver alguma função no onpaste atual do elemento, salve a mesma antes de prosseguir...
    if (this.element.onpaste)
    {
        // o índice atual é sempre o tamanho do array de onpastes
        evIndex = this.element._arrOldOnPastes.length;
        
        evType = EventType.defaultOnPaste;
        evValidation = EventValidation.noValidation;
        
        // Se não for um "onpaste" nativo, atualiza o tipo de evento e o tipo de validação
        if ((this.element.onpaste.toString().indexOf("onpaste(event)") == -1) && 
            (this.element._validation && this.element.onpaste.toString().indexOf(MaskedEdit.prototype.__evSig_OnPaste__) != -1))
        {
            evType = EventType.maskedOnPaste;
            evValidation = this.element._validation;
        }
        opEvent = new HTMLEvent(evValidation, evType);
        opEvent.index = evIndex;
        opEvent.src = this.element;
        opEvent.fn = this.element.onpaste;
    }
    // Se houver algum evento de keypress pra ser salvo, armazena eles no array do elemento...
    if (opEvent)
        this.element._arrOldOnPastes[this.element._arrOldOnPastes.length] = opEvent;
    
    // ***** HTML ELEMENT UPDATES *************************************************************************************
    // atualiza o elemento com as informações atuais...
    this.element._validation = this.validation;
    this.element.onkeypress = this.kpFunc;
    this.element.onchange = this.ocFunc;
    this.element.onpaste = this.opFunc;
    
    return this;
});
$.browser.addMethod(MaskedEdit.prototype, "attach", function(el)
{
    this.setElement(el);
    this.attach();
    
    return this;
});

