/******************************************************
Arquivo para funções javascript que tenham como objetivo o 
mascaramento e validação de dados dos formulários.

Lista de funções:
  - addEvent: Adiciona uma função que será disparada quando ocorrer determinado evento no objeto. 
  - removeEvent: Remove um listener previamente adicionado em um objeto.
  - autoTab: A função irá adicionar a tabulação automática em todos os inputs.
  - alteraCssForm: Altera o label e a borda do campo especificado de acordo com o retorno da validação. 
  - trim: Remove caracteres na esquerda, direita ou ambos os lados da string.
  - maxLength: Controla o tamanho máximo de um campo removendo os excedentes.  
  - maskInput: Adiciona máscara a um campo. 
  - maskDec: Adiciona máscara (apenas para campos monetários e valor decimal) a um campo. 
  - isCPF: Verifica se um CPF é válido.
  - isCNPJ: Verifica se um CNPJ é válido.
  - isEmail: Verifica se um Email é válido.
  - isDate: Verifica se uma Data é válida.
  - isTime: Verifica se uma Hora (hh:mm) é válida.

TODO:
  - Validar campos no onBlur para evitar que seja copiado valores invalidos para os campos.
    Hoje está passando.
  - Executar o evento de validação dos dados do formulário no onBlur.
    Hoje está validando no submit.
  - Fazer o MaskInput aceitar array, para executar ele apenas uma vez para vários campos.
    Hoje para cada campo precisa ser criado um MaskInput
******************************************************/
    

/******************************************************
addEvent(object: Object, event: String, handler: Function(e: Event): Boolean, [scope: Object = object]): Boolean 
Adiciona uma função que será disparada quando ocorrer determinado evento no objeto. 
- object: objeto que receberá o listener 
- event: nome do evento sem o prefixo "on" (click, mouseover, ...) 
- handler: função que será chamada quando o evento ocorrer, será enviado como argumento 
              para esta função o objeto de evento, que além das propriedades normais, *sempre* irá conter: 
              - target: objeto que gerou o evento 
              - key: código do caractere em eventos de teclado 
              - stopPropagation: método para evitar a propagação do evento 
              - preventDefault: método para evitar que a ação default ocorra. O preventDefault pode ser emulado retornando "false" na função 
- scope: escopo (quem o "this" irá referenciar dentro do handler) que será usado quando a função for chamada, o default é o objeto no primeiro argumento 
******************************************************/
function addEvent(o, e, f, s){
    var r = o[r = "_" + (e = "on" + e)] = o[r] || (o[e] ? [[o[e], o]] : []), a, c, d;
    r[r.length] = [f, s || o], o[e] = function(e){
        try{
            (e = e || event).preventDefault || (e.preventDefault = function(){e.returnValue = false;});
            e.stopPropagation || (e.stopPropagation = function(){e.cancelBubble = true;});
            e.target || (e.target = e.srcElement || null);
            e.key = (e.which + 1 || e.keyCode + 1) - 1 || 0;
        }catch(f){}
        for(d = 1, f = r.length; f; r[--f] && (a = r[f][0], o = r[f][1], a.call ? c = a.call(o, e) : (o._ = a, c = o._(e), o._ = null), d &= c !== false));
        return e = null, !!d;
    }
};

/******************************************************
removeEvent(object: Object, event: String, handler: function(e: Event): Boolean, [scope: Object = object]): Boolean 
Remove um listener previamente adicionado em um objeto e retorna true em caso de sucesso. 
- object: objeto que recebeu o listener 
- event: nome do evento sem o prefixo "on" (click, mouseover, ...) 
- handler: mesma função que foi atribuida no addEvent 
- scope: escopo em que a função foi adicionada, caso você tenha fornecido um escopo diferente no addEvent, 
            é necessário que você passe como parâmetro o mesmo objeto, 
            caso contrário a remoção do evento não será realizada 
******************************************************/
function removeEvent(o, e, f, s){
    for(var i = (e = o["_on" + e] || []).length; i;)
        if(e[--i] && e[i][0] == f && (s || o) == e[i][1])
            return delete e[i];
    return false;
};

/******************************************************
autoTab(void): void 
Apenas chame a função depois das tags <form> ou então no evento onload. 
A função irá adicionar a tabulação automática em todos os inputs que tiverem 
o atributo maxlenght setado. Os inputs devem estar dentro de uma tag form, 
caso contrário eles não receberão o comportamento. 
******************************************************/
function autoTab(){
    var c = 0, lastKey = function(e){c = e.key;}, next = function(e){
        var i, j, f = (e = e.target).form.elements, l = e.value.length, m = e.maxLength;
        if(c && m > -1 && l >= m){
            for(i = l = f.length; f[--i] != e;);
            for(j = i; (j = (j + 1) % l) != i && (!f[j].type || f[j].disabled || f[j].readOnly || f[j].type.toLowerCase() == "hidden"););
            j != i && f[j].focus();
        }
    };
    for(var f, i = (f = document.forms).length; i; addEvent(f[--i], "keyup", next), addEvent(f[i], "keypress", lastKey));
};

/******************************************************
alteraCssForm(string,boolean): void 
- campo: nome do campo
- valido: <true|false> resultado da validação
Altera o label e a borda do campo especificado de acordo com o retorno
da validação. 
******************************************************/
function alteraCssForm(campo,valido,css) {
  if(!css) css = 'FORM';
  if(valido) {
    document.getElementById('lbl_'+campo).className = css+'_label';
    f[campo].className = css+'_campo';
  } else {
    document.getElementById('lbl_'+campo).className = css+'_label_erro';
    f[campo].className = css+'_campo_erro';  
  }
}
/******************************************************
alteraCssLogin(string,boolean): void 
- campo: nome do campo
- valido: <true|false> resultado da validação
Altera o label e a borda do campo especificado de acordo com o retorno
da validação. 
******************************************************/
function alteraCssLogin(campo,valido,css) {
  if(!css) css = 'FORM';
  if(valido) {
    //document.getElementById('lbl_'+campo).className = css+'_label';
    f[campo].className = css+'_campo';
  } else {
    //document.getElementById('lbl_'+campo).className = css+'_label_erro';
    f[campo].className = css+'_campo_erro';  
  }
}

/******************************************************
String.prototype.trim([chars: String = " "], [type: Integer = 0]): String
Remove caracteres na esquerda, direita ou ambos os lados da string.
- caracteres: sequência de caracteres que deverão ser removidos
- type: especifica onde irá ocorrer o trim, possíveis valores são:
          - 0 = remove em ambos os lados
          - 1 = remove caracteres na esquerda
          - 2 = remove caracteres na direita
******************************************************/
String.prototype.trim = function(c, t){
    return c = "[" + (c == undefined ? " " : c.replace(/([\^\]\\-])/g, "\\\$1")) + "]+",
    this.replace(new RegExp((t != 2 ? "^" : "") + c + (t != 1 ? "|" + c + "$" : ""), "g"), "");
};

/******************************************************
maxLength(campo: String, tammax: Integer): void
Controla o tamanho máximo de um campo removendo os excedentes.
- campo: Objeto do campo
- tammax: Tamanho máximo que deve ter o campo
******************************************************/
function maxLength(campo, tammax) {
  if (campo.value.length > tammax) campo.value = campo.value.substr(0,tammax);
}

/******************************************************
maskInput(field: HTMLInputElement, mask: String): void 
Adiciona máscara a um campo. 
- field: campo que vai receber a máscara 
- mask: máscara que será aplicada 

Regras Padrões
- a = A-Z e 0-9 
- A = A-Z, acentos e 0-9 
- 9 = 0-9 
- C = A-Z e acentos 
- c = A-Z 
- * = qualquer coisa 

Regras Especiais
- E = (Except) exceção 
- O = (Only) somente 

Criação de Máscaras
- Máscara simples: nesse tipo de máscara o usuário pode digitar no máximo a mesma 
  quantidade de caracteres que a máscara contém. 
Exemplo: 
  Telefone = (99)9999-9999 
  Data = 99/99/9999 

Máscara especial "regra^exceções": esse tipo de máscara é composto por 2 partes, 
separadas por "^", o lado esquerdo especifica a regra e o direito as exceções para a regra selecionada. 
Exemplo: 
  9^abc = a regra é aceitar somente números "9" e a exceção são os caracteres a, b e c 
  c^123 = aceita somente caracteres de a-z e a exceção são os números 1, 2 e 3 

Uso das regras especiais: ela é semelhante a máscara especial, porém o lado esquerdo 
tem um significado diferente, podendo ser "E" (qualquer coisa, exceto...) ou "O" (somente...) 
Exemplo: 
  E^abc: aceita qualquer coisa, menos a, b e c 
  O^123: só permite os caracteres 1, 2 e 3

Exemplos de uso:
maskInput(f.nome, "C^ ");
maskInput(f.dtnasc, "99/99/9999");
maskInput(f.telefone, "(99) 9999-9999");
maskInput(f.email, "a^@-_.");
maskInput(f.cpf, "999.999.999-99");
maskInput(f.cnpj, "99.999.999/9999-99");  
******************************************************/
function maskInput(f, m){
    function mask(e){
        var patterns = {"1": /[A-Z]/i, "2": /[0-9]/, "4": /[À-ÿ]/i, "8": /./ },
            rules = { "a": 3, "A": 7, "9": 2, "C":5, "c": 1, "*": 8};
        function accept(c, rule){
            for(var i = 1, r = rules[rule] || 0; i <= r; i<<=1)
                if(r & i && patterns[i].test(c))
                    break;
                return i <= r || c == rule;
        }
        var k, mC, r, c = String.fromCharCode(k = e.key), l = f.value.length;
        (!k || k == 8 ? 1 : (r = /^(.)\^(.*)$/.exec(m)) && (r[0] = r[2].indexOf(c) + 1) + 1 ?
            r[1] == "O" ? r[0] : r[1] == "E" ? !r[0] : accept(c, r[1]) || r[0]
            : (l = (f.value += m.substr(l, (r = /[A|9|C|\*]/i.exec(m.substr(l))) ?
            r.index : l)).length) < m.length && accept(c, m.charAt(l))) || e.preventDefault();
    }
    for(var i in !/^(.)\^(.*)$/.test(m) && (f.maxLength = m.length), {keypress: 0, keyup: 1})
        addEvent(f, i, mask);
};

/******************************************************
formatCurrency(field: HTMLInput, [floatPoint: Integer = 2], [decimalSep: String = ","], [thousandsSep: String = "."]): String 
Formata o input de forma que ele assuma o comportamento de um campo monetário. 
- field: Campo que receberá a formatação 
- floatPoint: Número de casas decimais 
- decimalSep: String representando o separador decimal 
- thousandsSep: String representando o separador de milhar 

Exemplo de uso:
maskDec(f.salario, 2);
******************************************************/
function maskDec(o, n, dig, dec){
    o.c = !isNaN(n) ? Math.abs(n) : 2;
    o.dec = typeof dec != "string" ? "," : dec, o.dig = typeof dig != "string" ? "." : dig;
    addEvent(o, "keypress", function(e){
        if(e.key > 47 && e.key < 58){
            var o, s, l = (s = ((o = this).value.replace(/^0+/g, "") + String.fromCharCode(e.key)).replace(/\D/g, "")).length, n;
            if(o.maxLength + 1 && l >= o.maxLength) return false;
            l <= (n = o.c) && (s = new Array(n - l + 2).join("0") + s);
            for(var i = (l = (s = s.split("")).length) - n; (i -= 3) > 0; s[i - 1] += o.dig);
            n && n < l && (s[l - ++n] += o.dec);
            o.value = s.join("");
        }
        e.key > 30 && e.preventDefault();
    });
}

/******************************************************
isCPF(string): Boolean 
Retorna true se o CPF informado é válido, caso contrário, false.
******************************************************/
function isCPF(cpf){
    var c = cpf;
    if((c = c.replace(/[^\d]/g,"").split("")).length != 11) return false;
    if(new RegExp("^" + c[0] + "{11}$").test(c.join(""))) return false;
    for(var s = 10, n = 0, i = 0; s >= 2; n += c[i++] * s--);
    if(c[9] != (((n %= 11) < 2) ? 0 : 11 - n)) return false;
    for(var s = 11, n = 0, i = 0; s >= 2; n += c[i++] * s--);
    if(c[10] != (((n %= 11) < 2) ? 0 : 11 - n)) return false;
    return true;
};

/******************************************************
isCNPJ(string): Boolean 
Retorna true se o CNPJ informado é válido, caso contrário, false. 
******************************************************/
function isCNPJ(cnpj){
    var b = [6,5,4,3,2,9,8,7,6,5,4,3,2], c = cnpj;
    if((c = c.replace(/[^\d]/g,"").split("")).length != 14) return false;
    for(var i = 0, n = 0; i < 12; n += c[i] * b[++i]);
    if(c[12] != (((n %= 11) < 2) ? 0 : 11 - n)) return false;
    for(var i = 0, n = 0; i <= 12; n += c[i] * b[i++]);
    if(c[13] != (((n %= 11) < 2) ? 0 : 11 - n)) return false;
    return true;
};

/******************************************************
isEmail(string): Boolean 
Retorna true se o email informado é válido, caso contrário, false. 
******************************************************/
function isEmail(email) {
  email.replace(/[^A-Z0-9-\.@_]/gi,"");
  if (email.length<6) {
    return false;
  } else {
    if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) return email.toLowerCase();
    else  return false;
  }
}


/******************************************************
isDate(y: Integer, m: Integer, d: Integer): Integer 
Verifica uma data e retorna 0 se for válida ou um dos códigos de erro abaixo. 
- y: ano 
- m: mês 
- d: dia 
 
isDate(date: String, matcher: RegExp, map: Object): Integer 
Verifica uma data e retorna 0 se for válida ou um dos códigos de erro abaixo. 
- date: Data em forma de string 
- matcher: Expressão regular encarregada de achar e guardar o dia, mês e ano 
- map: Objeto contendo a posição onde se encontra cada componente da data na expressão regular. 
 
Seu formato é o seguinte: {d: posicaoDoDia, m: posicaoDoMes, y: posicaoDoAno} 
Códigos de retorno
0 = Data válida 
1 = Formato de data inválido (regular expression falhou ou quantidade de argumentos != 3) 
2 = Dia não está entre 1 e 31 
3 = Mês não está entre 1 e 12 
4 = Nos meses de abril, junho, setembro e novembro não existe o dia 31 
5 = O mês de fevereiro só tem 28 dias 
6 = Anos bissexto, o mês de fevereiro só tem 29 dias

Exemplos:
    isDate("22/07/1984", /^([0-9]{1,2})[\/]([0-9]{1,2})[\/]([0-9]{1,4})$/, {d: 1, m: 2, y: 3})
    isDate("1984-07-22", /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/, {d: 3, m: 2, y: 1})
    isDate("07-22-1984", /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/, {d: 3, m: 2, y: 1})
    isDate(2000, 1, 32)
    isDate(2000, 0, 1)
    isDate(2000, 4, 31)
    isDate(2001, 2, 29)
    isDate(2004, 2, 30)
******************************************************/
function isDate (y, m, d){
  if(typeof y == "string" && m instanceof RegExp && d){
  if(!m.test(y)) return 1;
    y = RegExp["$" + d.y], m = RegExp["$" + d.m], d = RegExp["$" + d.d];
  }
  d = Math.abs(d) || 0, m = Math.abs(m) || 0, y = Math.abs(y) || 0;
  return arguments.length != 3 ? 1 : d < 1 || d > 31 ? 2 : m < 1 || m > 12 ? 3 : /4|6|9|11/.test(m) && d == 31 ? 4
    : m == 2 && (d > ((y = !(y % 4) && (y % 1e2) || !(y % 4e2)) ? 29 : 28)) ? 5 + !!y : 0;
};

/******************************************************
isTime(string): Boolean 
Retorna true se a hora (hh:mm) informada é válida, caso contrário, false. 
******************************************************/
function isTime(hora){
  hora.replace(/\./gi,":");
  hora.replace(/[^0-9:]/gi,"");
  if (hora.length!=5 || hora.indexOf(":")<1) {
    return false;
  } else {
    if (/([01][0-9]|2[0-3]):[0-5][0-9]/.test(hora)) {
      return hora;
    } else {
      return false;
    }
  }
}
