
var valObj = {
    email: {
        filter: /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/,
        example: 'i.e. username@gmail.com',
        message: 'Email required'
    },
    password: {
        filter: /^(?=.*[0-9]+.*)(?=.*[a-zA-Z]+.*)[0-9a-zA-Z]{8,}$/,
        example: 'i.e. 8 or more chars - at least 1 letter and 1 number',
        message: '8 or more chars - at least 1 letter and 1 number'
    },
    phone: {
        filter: /^\(?[0-9]{3}\)?(\-| ?|.)[0-9]{3}(\-| ?|.)[0-9]{4}$/,
        example: 'i.e. (555) 555-5555, 5555555555, 555 555 5555',
        message: 'Phone number required'
    },
    _name: {
        filter: /[a-zA-Z]{2,25}/i,
        example: 'i.e. Name',
        message: 'Required'
    },
    birthdate: {
        filter: /\d{2}\/\d{2}\/\d{4}/,
        example: 'i.e. 12/21/1980',
        message: 'Birthdate required'
    },
    address: {
        filter: /^([0-9]{1,10}[ 0-9a-zA-Z\.\- ]{2,50}|P.*O.* Box [0-9]{1,10})$/i,
        example: 'i.e. 1111 Lazy A. Dr, PO Box 233, P.O. Box 555, etc',
        message: 'Address required'
    },
    city: {
        filter: /[a-zA-Z ]{4,25}/i,
        example: 'i.e. City name',
        message: 'Required'
    },
    state: {
        filter: /^[a-zA-Z]{2}$/i,
        example: 'i.e. CA, PA, NV, etc.',
        message: 'State required'
    },
    zipcode: {
        filter: /^[0-9]{5}$/i,
        example: 'i.e. 93514, 15825, 90210, etc.',
        message: '5 digit Zipcode required'
    },
    ccnum: {
        filter: /^[0-9]{13,16}$/i,
        example: 'Only Visa, MasterCard or Amex is accepted (no spaces)',
        message: 'Visa, MasterCard or Amex required (no spaces)'
    },
    cccvs: {
        filter: /^[0-9]{3,4}$/i,
        example: 'i.e. 212, 112, 4422, etc.',
        message: 'CVS required'
    },
    month: {
        filter: /^[0-9]{1,2}$/i,
        example: 'i.e. 1 (jan), 8 (aug), 12 (dec), etc.',
        message: 'Month required'
    },
    day: {
        filter: /^[0-9]{1,2}$/i,
        example: 'i.e. 1 - 31',
        message: 'Day required'
    },
    year: {
        filter: /^[0-9]{4}$/i,
        example: 'i.e. 1980, 2000, 2010, etc.',
        message: 'Year required'
    },
    number: { // integer or decimal
        filter: /(^\d+$)|(^\d*\.\d+$)|(^\d+\.\d*$)/i,
        example: 'i.e. 1, 2, 3, 3.5, etc.',
        message: 'Number required'
    },
    integer: {
        filter: /^\d+$/i,
        example: 'i.e. 1, 2, 3, etc.',
        message: 'Number required'
    },
    selection: {
        filter: /[a-zA-Z0-9]/i,
        example: 'please choose',
        message: 'Selection required'
    },
    accept: {
        filter: /(true)/i,
        example: 'Accept the terms to continue',
        message: 'Acceptance required'
    },
    filexls: {
        filter: /^[0-9a-zA-Z\_\:\\\/]{2,50}.xls$/i,
        example: 'i.e. instAbrvReadingFormA20110131.xls',
        message: 'Excel file required'
    },
    fullname: {
        filter: /^[a-zA-Z]{2,25}[ +][a-zA-Z]{2,25}/i,
        example: 'i.e. First Last',
        message: 'First and Last name required'
    },
    valFilters: /(email|password|phone|_name|birthdate|address|city|state|zipcode|ccnum|cccvs|month|day|year|number|integer|selection|accept|filexls|fullname)/i,
    useStyles: true,
    bValidate: true,
    noValidation: function () {
        this.bValidate = false;
    },
    validate: function () {
        // escape validation i.a.
        // i.e. for clearing on canceling form
        if (!this.bValidate) {
            return !this.bValidate;
        }
        var bValid = true,
            optRegEx = /(optional)/i;
        for (idx = 0, len = document.forms[0].elements.length; idx < len; idx++) {
            var node = document.forms[0].elements[idx];
            var valFilter = node.className.match(this.valFilters) !== null ? node.className.match(this.valFilters)[0] : null;
            if (valFilter !== null) {
                var bOptional = (node.className.search(optRegEx) > -1) ? true : false;
                var validation = valObj[valFilter];
                if (node.value.match(validation.filter) === null || !valObj.additionalValidation(node, validation)) {

                    // special processing if optional field
                    if (bOptional && node.value.length === 0 || node.disabled === true) {
                        this.log(node.name + ' passed ' + valFilter + " special optional");
                        continue;
                    }

                    // special processing if it's a checkbox that need to be accepted/checked
                    if (node.type === 'checkbox' && (node.checked + '').match(validation.filter) !== null) {
                        this.log(node.name + ' passed ' + valFilter + " special checkbox");
                        continue;
                    }

                    this.log(node.name + ' failed ' + valFilter);
                    if (node.parentNode.lastChild.className !== 'warn') {
                        // append warning span w/validation message i.a.
                        var warn = document.createElement("span"),
                        warntext = document.createTextNode(bOptional ? 'Failed validation' : validation.message);
                        warn.className = 'warn';

                        if (this.useStyles) {
                            // absolute positioning
                            warn.style.top = (node.offsetTop - 5) + 'px';
                            warn.style.left = (node.offsetLeft + node.offsetWidth) + 'px';
                            warn.style.position = 'absolute';
                            warn.style.zIndex = '1000';
                            warn.style.color = 'red';
                            warn.style.backgroundColor = 'white';
                            warn.style.filter = 'alpha(opacity=80)'; // IE
                            warn.style.opacity = 0.8; // CSS3
                        }
                        else {
                            warn.id = "warn_" + node.className;
                        }
                        warn.appendChild(warntext);
                        warn.setAttribute("title", validation.example);
                        node.parentNode.appendChild(warn);

                        var nodeType = node.type;
                        this.log('form field type:' + nodeType);

                        if (nodeType === 'text' || nodeType === 'password') {
                            // add keystroke listener to remove message when value is typed/entered and valid
                            node.onkeyup = function () {
                                var bSuccess = valObj.valField(this);
                                if (bSuccess) {
                                    this.onkeyup = null;
                                    this.onblur = null;
                                }
                            };
                            // add blur listener, i.e. when a user double clicks the field to see form history
                            // then clicks old value then tabs to next field.
                            node.onblur = function () {
                                var bSuccess = valObj.valField(this);
                                if (bSuccess) {
                                    this.onkeyup = null;
                                    this.onblur = null;
                                }
                            };
                        }
                        else if (nodeType === 'select-one' || nodeType === 'checkbox' || nodeType === 'file') {
                            // add selection change listener to remove message when value is valid
                            node.onchange = function () {
                                var bSuccess = valObj.valField(this);
                                if (bSuccess) {
                                    this.onchange = null;
                                }
                            };
                        }

                    }
                    bValid = false;
                } else {
                    this.log(node.name + ' passed ' + valFilter);
                    //this.log('   ' + node.value + ' match ' + validation.filter);
                }
                //console.dir(validation);
            }
        }
        return bValid;
    },
    valField: function (node) {
        //console.dir(node);
        var warnNode = node.parentNode.lastChild;
        var valFilter = node.className.match(this.valFilters) !== null ? node.className.match(this.valFilters)[0] : null;
        if (valFilter !== null) {
            var validation = valObj[valFilter];
            this.log('valField() node.value ' + node.value + ' - validation.filter ' + validation.filter);
            if (node.type !== 'checkbox' && warnNode.className === 'warn' && node.value.match(validation.filter) !== null) {
                if (valObj.additionalValidation(node, validation)) {
                    this.log('valField() valid');
                    node.parentNode.removeChild(warnNode);
                }
                else {
                    this.log('valField() not valid');
                    return false;
                }
            }
            else if (node.type === 'checkbox' && warnNode.className === 'warn' && (node.checked + '').match(validation.filter) !== null) {
                this.log('valField() checkbox valid');
                node.parentNode.removeChild(warnNode);
            }
            else {
                this.log('valField() not valid');
                return false;
            }
        }
        return true;
    },
    clearWarning: function (arg) {
        for (var argIdx = 0, argsLen = arguments.length; argIdx < argsLen; argIdx++) {
            var node = arguments[argIdx];
            for (var idx = 0, len = node.parentNode.childNodes.length; idx < len; idx++) {
                var nodeCheck = node.parentNode.childNodes[idx];
                if (nodeCheck.className === 'warn') {
                    var parent = nodeCheck.parentNode;
                    parent.removeChild(nodeCheck);
                    this.log('clearSubWarns() removed');
                }
                else {
                    this.log('clearSubWarns() not removed className:' + nodeCheck.className);
                }
            }
        }
    },
    optional: function (node) {
        //add optional valObj class
        node.className += ' optional';
        this.clearWarning(node);
    },
    required: function (node) {
        // remove optional valObj class
        node.className = node.className.replace('optional', '');
        this.clearWarning(node);
    },
    bDebug: true,
    log: function (message) {
        if (typeof console !== 'undefined' && this.bDebug) {
            console.log(message);
        }
    },
    luhnCheck: function (cardNumber) {
        var bPassed = false;
        var no_digit = cardNumber.length;
        var oddoeven = no_digit & 1;
        var sum = 0;
        for (var count = 0; count < no_digit; count++) {
            var digit = parseInt(cardNumber.charAt(count));
            if (!((count & 1) ^ oddoeven)) {
                digit *= 2;
                if (digit > 9) digit -= 9;
            }
            sum += digit;
        }
        if (sum == 0)
            bPassed = false;
        else if (sum % 10 == 0)
            bPassed = true;
        return bPassed;
    },
    approvedCCTypeCheck: function (cardNumber) {
        return cardNumber.match(/^(4|34|37|51|52|53|54|55)/) != null ? true : false;
    },
    additionalValidation: function (node, validation) {
        var bValid = true;

        if (node.type === 'text' && node.className.search(/placeholder/i) > -1) {
            if (node.value === node.title) {
                bValid = false;
                this.log('valObj additionalValidation placeholder: ' + bValid);
            }
        }
        else if (validation === valObj.ccnum) {
            // luhn check
            bValid = valObj.luhnCheck(node.value);
            this.log('Luhn check passed: ' + bValid);
            if (bValid) {
                // approved credit card types check
                bValid = valObj.approvedCCTypeCheck(node.value);
                this.log('Approved credit card type check passed: ' + bValid);
            }
        }
        return bValid;
    }
};

