var FormCheck = new Class({
    Implements: [Events, Options],
    options: {
        msgTag: 'div',
        msgClass: 'form_check_msg',
        msgPlace: 'self_after',//self_before, self_after, parent_before, parent_after
        //style for input,textarea field only
        neutralClass: 'form_check_neutral',
        validClass: 'form_check_valid',
        invalidClass: 'form_check_invalid',
        customExp: {},
        //event handler for valid and invalid event
        onValid: $empty(),
        onInvalid: $empty(),
        onSubmit: $empty(),
        defaultErr: '未定义出错信息'
    },

    checkExp: new Hash({
        required: {vexp: /[^.*]/, msg: "此项不能为空"},
        alpha: {vexp: /^[a-z ._-]+$/i, msg: "此项只能为字母、空格、连接线及下划线"},
        alphanum: {vexp: /^[a-z0-9 ._-]+$/i, msg: "此项只能为字母、空格、连接线、下划线及数字"},
        integer: {vexp: /^[-+]?\d+$/, msg: "此项只能为整数"},
        pint: {vexp: /^\d+$/, msg: "此项只能为正整数"},
        real: {vexp: /^[-+]?\d*\.?\d+$/, msg: "此项只能为数字"},
        time: {vexp: /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/, msg: '时间格式应为hh:mm:ss'},
        date: {vexp: /^((((((0[48])|([13579][26])|([2468][048]))00)|([0-9][0-9]((0[48])|([13579][26])|([2468][048]))))-02-29)|(((000[1-9])|(00[1-9][0-9])|(0[1-9][0-9][0-9])|([1-9][0-9][0-9][0-9]))-((((0[13578])|(1[02]))-31)|(((0[1,3-9])|(1[0-2]))-(29|30))|(((0[1-9])|(1[0-2]))-((0[1-9])|(1[0-9])|(2[0-8]))))))$/, msg: "请输入正确的日期格式(yyyy-MM-dd)."},
        datetime: {vexp:/^((((((0[48])|([13579][26])|([2468][048]))00)|([0-9][0-9]((0[48])|([13579][26])|([2468][048]))))-02-29)|(((000[1-9])|(00[1-9][0-9])|(0[1-9][0-9][0-9])|([1-9][0-9][0-9][0-9]))-((((0[13578])|(1[02]))-31)|(((0[1,3-9])|(1[0-2]))-(29|30))|(((0[1-9])|(1[0-2]))-((0[1-9])|(1[0-9])|(2[0-8]))))))\s([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/, msg: '日期带时间格式应为yyyy-MM-dd hh:mm:ss'},
        email: {vexp: /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i, msg: "邮箱地址的格式不对"},
        phone: {vexp: /^\+?((\(\d{3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(-\d{3,})?$/, msg: "请输入正确的电话号"},
        mobile: {vexp: /^((\(\d{3}\))|(\d{3}\-))?(13|15|18)\d{9}$/, msg: "请输入正确的手机号码"},
        tel: {vexp: /^((\+?((\(\d{3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(-\d{3,})?)|(((\(\d{3}\))|(\d{3}\-))?(13|15)\d{9}))$/, msg:"请输入正确的电话号或者手机号"},
        idCard: {vexp: /^\d{15}(\d{2}[A-Za-z0-9])?$/,msg: "请输入正确的身份证号"},
        zip: {vexp: /^[1-9]\d{5}$/,msg: "邮政编码格式不对"},
        chinese: {vexp: /^[\u0391-\uFFE5\s]+$/,msg: "只允许输入中文"},
        unsafe: {vexp: /^(([A-Z]*|[a-z]*|\d*|[-_\~!@#\$%\^&\*\.\(\)\[\]\{\}<>\?\\\/\'\"]*)|.{0,5})$|\s/,msg: "输入含有不安全字符"},
        url: {vexp: /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&amp;%\$#\=~])*$/i, msg: "请输入正确的网址"},
        confirm: {
            vexp: function(equal, field){
                return field.get('value') == equal ? true : false;
            },
            msg: "前后两次输入数据不一致"
        },
        textNum: {
            vexp: function(min, max, field){
                return field.get('value').length >= min && field.get('value').length <= max ? true : false;
            },
            msg: "所输入的字符数须在{1}到{2}位之间"
        },
        compare: {
            vexp: function(min, max, field){
                var fValue = field.get('value').toInt();
                if($type(fValue) != 'number')return false;
                return fValue >= min && fValue <= max ? true : false;
            },
            msg: "输入值须在{1}到{2}之间"
        },
        strip: {
            vexp: function(type, field) {
                field.set('value', field.get('value').trim());
                return true;
            },
            msg: '去除空格'
        },
        cbItems: {
            vexp: function(min, max, cList){
                return cList.length >= min && cList.length <= max ? true : false;		    
            },
            msg: '复选框的选择的项目须在{1}到{2}项之间'
        }
    }),

    initialize: function(form, options){
        this.form = $(form);
        this.setOptions(options);
        this.checkExp.extend(this.options.customExp);

        this.validations = new Hash();
        this.childFieldNames = [];
        this.iniFields(this.form);

        this.form.addEvents({
            'submit': this.submitForm.bind(this),
            'reset': this.resetForm.bind(this)
        });
    },

    iniFields: function(scope){
        var fields = $(scope).getElements('*[class*=form_check]');
        fields.each(function(field){
            //set required default value
            field.store('validItems', new Hash({'required': false}));
            //set default errors number for field
            field.store('errors', 0);

            //filter type of checkbox,radio
            if(!this.childFieldNames.contains(field.get('name')) || !this.isChildField(field)){
               field.get('class').split(' ').each(function(klass){
                   klass = klass.replace('&nbsp;', ' ');
                    if(klass.match(/^form_check\[.+\]$/)){
                        var m = klass.match(/^form_check\[(.+)\]$/)[1];
                        var validTypes = m ? m.split('|'): [];
                        validTypes.each(function(type){
                            this.registerField(field, type);
                        }, this);
                    }
                }, this);
            }

            if(!this.isChildField(field)){
                //add class for input,textarea,select fields
                field.addClass(this.options.neutralClass);
            }else{
                //recored already registered chlid fields.
                this.childFieldNames.include(field.get('name'));
            }
        }, this);
    },

    registerField: function(field, type){
        //give default id property for field
        if(!field.get('id'))field.set('id', 'field_' + field.uid);
        //build key:field.uid and value:element id property value
        this.validations.set(field.uid, field.get('id'));

        //draw exp from type
        var expStr = false;
        if(this.checkExp.has(type)){
            //alert($type(this.checkExp[type].vexp) == $type(/\w+/));
            if($type(this.checkExp[type].vexp) == $type(/ /)){
                expStr = type;
                field.retrieve('validItems').set(expStr, true);
            }
        }else{
            var filter = type.match(/^([a-zA-Z_-]+)\((.*)\)$/);
            if(!!filter){
                expStr = filter[1];
                field.retrieve('validItems').set(expStr, filter[2]);
            }
        }

        if(!this.isChildField(field)){
            field.addEvent('blur', function(){
                this.validField(field, expStr);
            }.bind(this));
        }else{
            this.form.getElements('input[name=' + field.get('name') + ']').each(function(child){
                child.addEvent('blur', function(){
                    this.validChildField(field, expStr);
                }.bind(this));
            }, this);
        }
    },

    validField: function(field, expStr){
        if(this.isUnrequired(field)){
            this.removeMsg(field, expStr)
            return true;
        }

        if(this.checkExp.has(expStr)){
            //alert($type(this.checkExp[type].exp));
            var expType = $type(this.checkExp[expStr].vexp);
            if(expType == $type(/ /)){
                return this.checkExp[expStr].vexp.test(field.get('value')) ? this.removeMsg(field, expStr) : this.injectMsg(field, expStr);
            }

            if(expType == 'function'){
                var _p = this.parseParam(field, expStr);
                //replace message templete variable
                this.parseMsgTpl(field, expStr, _p);
                
                return eval('this.checkExp[expStr].vexp(' + this.buildEvalStr(_p) + ', field)') ? this.removeMsg(field, expStr) : this.injectMsg(field, expStr);
            }
        }else{
            //custom function valid
            var expFun = false;
            try{expFun = eval(expStr);}catch(e){}

            if($type(expFun) == 'function'){
                //TODO:pass the param of custom function
                var _p = this.parseParam(field, expStr);
                this.parseMsgTpl(field, expStr, _p);
                               
                return eval('expFun(' + this.buildEvalStr(_p) + ')') ? this.removeMsg(field, expStr) : this.injectMsg(field, expStr);
            }
        }
    },

    validChildField: function(field, expStr){
        if(this.isUnrequired(field))return true;

        var valueList = this.form.getElements('input[name=' + field.get('name') + ']').filter(function(input){
            return input.get('checked');
        });

        if(expStr == 'required'){
            return valueList.length > 0 ? this.removeMsg(field, expStr) : this.injectMsg(field, expStr);                
        }
        
        if(expStr == 'cbItems'){
            var _p = this.parseParam(field, expStr), _l = valueList;
            this.parseMsgTpl(field, expStr, _p);
            
            return eval('this.checkExp[expStr].vexp(' + this.buildEvalStr(_p) + ', _l)') ? this.removeMsg(field, expStr) : this.injectMsg(field, expStr);
        }
    },

    isUnrequired: function(field){
        if(!field.get('value') && !field.retrieve('validItems').get('required')){
            return true;
        }
    },

    removeMsg: function(field, expStr, reset){
        reset = reset || false;
        var msgElement = $(field.get('id') + '_' + expStr);
        if(msgElement){
            msgElement.set('tween', {
                duration: 500,
                onComplete: function(){
                    msgElement.dispose();
                }
            });
            msgElement.tween('opacity', '0', '1');

            if(!reset)field.store('errors', field.retrieve('errors') - 1);
        }
        
        if(!reset)this.setFieldStyle(field, expStr);
    },

    injectMsg: function(field, expStr){
        //TODO:message templete
        var msg = field.retrieve(expStr + '_err') || (this.checkExp[expStr] ? this.checkExp[expStr].msg : this.options.defaultErr);
        
        var msgId = field.get('id') + '_' + expStr;
        if(!$(msgId)){
            var msgElement = new Element(this.options.msgTag,{
                'id': msgId,
                'class': this.options.msgClass,
                'html': msg 
            });

            var msgPlace = this.options.msgPlace.split('_');
            //field.parentNode.appendChild(msgElement);
            msgPlace[0] == 'self' ? msgElement.inject(field, msgPlace[1]) : msgElement.inject(field.getParent(), msgPlace[1]);
            msgElement.tween('opacity', '0', '1');

            field.store('errors', field.retrieve('errors') + 1);
        }else $(msgId).set('html', msg);

        this.setFieldStyle(field, expStr);
    },

    setFieldStyle: function(field, expStr){
        if(field.retrieve('errors') == 0){
            //field.tween('opacity', '0.5', '1');
            if(!this.isChildField(field)){
                field.removeClass(this.options.neutralClass).removeClass(this.options.invalidClass).addClass(this.options.validClass);
            }
            field.fireEvent('onValid', [field, expStr], 50);
        }else{
            //field.tween('opacity', '0.5', '1');
            if(!this.isChildField(field)){
                field.removeClass(this.options.neutralClass).removeClass(this.options.validClass).addClass(this.options.invalidClass);                
            }
            field.fireEvent('onInvalid', [field, expStr], 50);
        }
    },

    submitForm: function(e){
        var event = new Event(e);
        this.fireEvent('onSubmit');
        var isValid = true;

        this.validations.each(function(fieldId, uid){
            var field = $(fieldId);
            if(!!field){
                //field.store('errors', 0);
                var isChild = this.isChildField(field);
                field.retrieve('validItems').each(function(value, expStr){
                    isChild ? this.validChildField(field, expStr) : this.validField(field, expStr);
                    if(field.retrieve('errors') > 0)isValid = false;
                }, this);
            }else{
                this.validations.erase(uid);
            }
        }, this);

        if(!isValid)event.stop();
        return isValid;        
    },

    resetForm: function(e){
        this.validations.each(function(fieldId, uid){
            var field = $(fieldId);
            if(!!field){
                field.store('errors', 0);
                if(!this.isChildField(field)){
                    field.removeClass(this.options.invalidClass).removeClass(this.options.validClass).addClass(this.options.neutralClass);
                }
                
                field.retrieve('validItems').each(function(value, expStr){
                    this.removeMsg(field, expStr, true);
                }, this);
            }
        }, this);
    },

    parseParam: function(field, expStr){
        //console.info('parse param!');
        return field.retrieve('validItems').get(expStr).split(',').map(function(str){
            if(str == 'self'){
                return field.get('value');
            }

            if(str.match(/^\$.+$/)){
                var element = $(str.match(/^\$(.+)$/)[1]);
                return element ? element.get('value') : false;
            }
            
            if(str.match(/^@.+$/)){
                var msg = str.match(/^@(.+)$/)[1];
                field.store(expStr + '_tpl', msg);
                return msg;
            }

            return str;
        });
    },

    buildEvalStr: function(paramList){
        var paramLen = paramList.length;
        var evalStr = '';//this.checkExp[expStr].vexp(

        paramList.each(function(param, index){
            evalStr += '_p[' + index + ']';
            if(index != paramLen - 1)evalStr += ',';
        });

        return evalStr;
    },
    
    parseMsgTpl: function(field, expStr, params){
        var msg = this.checkExp[expStr] ? this.checkExp[expStr].msg : field.retrieve(expStr + '_tpl');
        var tplList = msg.match(/\{\d+\}/g);
        
        if(tplList){
            tplList.each(function(tpl, index){
                var order = tpl.match(/\d+/)[0].toInt() - 1;
                msg = msg.replace(tpl, !params[order] ? '' : params[order]);
            });
        }  	
        
        field.store(expStr + '_err', msg);	
    },

    isChildField: function(field){
        return field.get('type') == 'radio' || field.get('type') == 'checkbox' ? true : false;
    }
});