var form_property = {
    getFieldValue : function(field_name){
        var i, re, rArr, ret, cnt;
        var elm = this.form.elm.elements;
        if (!elm[field_name]) {
            rArr = /^(.*?)\[\]$/i.exec(field_name);
            if (rArr) {
                re = new RegExp("^" + rArr[1] + "\\[\\d+\\]$", "i");
                for (i=0;i<elm.length;i++) if (typeof(elm[i].type) != "undefined" && elm[i].type == "checkbox" && re.exec(elm[i].name)) {
                    field_name = elm[i].name;
                    break;
                }
            } else {
                // Multidata field
                re = new RegExp("^(" + field_name + "\\[.+?\\])$", "i");
                ret = {"__multidata" : 0, "values" : {}};
                cnt = 0;
                for (i=0; i<elm.length; i++) {
                    rArr = re.exec(elm[i].name);
                    if (rArr && rArr[1]) {
                        ret.values[rArr[1]] = this.getFieldValue(rArr[1]); //toDo: Multidata with checkbox-list
                        ret.__multidata++;
                    }
                }
                return ret.__multidata > 0 ? ret : null;
            }
        }
        var fieldWr = this.winWr.getFormElement(this.form_name, field_name);
        if (!fieldWr.elm) return null;

        var field = fieldWr.elm;
        var field_type = this.getFieldType(field);
        if (field_type && field_type.constructor == Array) {
            if (this.winWr && this.winWr.config && this.winWr.config.DebugMode) alert("Attention!\nMixed field isn't analysed at the current version.");
            return null;
        }
        switch (field_type) {
        case "select-one":
            return field.selectedIndex < 0 ? null : field.options[field.selectedIndex].value;
        case "select-multiple":
            if (field.selectedIndex < 0) return null;
            else {
                var ret = new Array();
                for (i=0; i<field.length; i++) {
                    if (field.options[i].selected) ret[ret.length] = field.options[i].value;
                }
                return ret;
            }
        case "radio":
            for (i=0;i<field.length; i++) {
                if (field[i].checked) return field[i].value;
            }
            return null;
        case "checkbox-list":
            ret = new Array();
            for (i=0; i<this._checkboxNameList.length; i++) {
                if (elm[this._checkboxNameList[i]].checked) ret[ret.length] = elm[this._checkboxNameList[i]].value;
            }
            return ret;
        case "checkbox":
            return field.checked ? field.value : null;
        default:
            return typeof(field.value) != "undefined" ? field.value : null;
        }
    },
    getFieldType : function(field){
        var elm, re, rArr, i;
        if (field) {
            if (typeof(field.type) != "undefined") {
                if (field.type == "checkbox") {
		            rArr = /^(.*?)\[\d+\]$/i.exec(field.name);
                    if (rArr) {
                        this._checkboxNameList = new Array(field.name);
                        var elm = this.form.elm.elements;
                        re = new RegExp("^" + rArr[1] + "\\[\\d+\\]$", "i");
                        for (i=0;i<elm.length;i++) {
                            if (typeof(elm[i].type) != "undefined" && elm[i].type == "checkbox" && elm[i].name != field.name && re.exec(elm[i].name)) this._checkboxNameList[this._checkboxNameList.length] = elm[i].name;
                        }
                        if (this._checkboxNameList.length > 1) return "checkbox-list";
                    }
                    return "checkbox";
                }
                if (field.tagName.toLocaleLowerCase() == "input" || field.type == "textarea" || field.type == "select-one" || field.type == "select-multiple") return field.type;
            } else if (typeof(field.length) != "undefined" && field.length > 0) {
                var eqv = true;
                var pl = new Array();
                pl[0] = this.getFieldType(field[0]);
                for (i=1; i<field.length; i++) {
                    pl[i] = this.getFieldType(field[i]);
                    if (pl[i] != pl[0]) eqv = false;
                }
                return eqv ? pl[0] : pl;
            }
        }
        return null;
    }
}

/*
Validate init example:
var validation_loginForm = new form_validation({form:"loginForm"}, {
    login:[
        {rule_name:"is_required", error_msg:"Field \"Login\" is required for fill!"},
        {rule_name:"is_alphalogin", error_msg:"Field \"Login\" must contain only Numbers, Letters, _, @, - and point.",not_empty:1}
    ],
    password:[
        {rule_name:"is_required", error_msg:"Field \"Password\" is required for fill!"}
    ]
}, _wrapper);
*/

var form_validation = function(ini_keys, fld_rules, winWr, isLoaded) {
    if (typeof(ini_keys) != "object") ini_keys = {};

    this.form_name = ini_keys.form ? ini_keys.form : 0;
	this.field_name = ini_keys.field ? ini_keys.field : undefined;
	this.ini_event = ini_keys.ini_event ? ini_keys.ini_event : (this.field_name ? "onclick" : "onsubmit");
	this.exception = ini_keys.exception ? ini_keys.exception : undefined;

	this.fld_rules = fld_rules ? fld_rules : {};

	this.winWr = winWr ? winWr : _wrapper;
    if (isLoaded) this.onload();
    else this.winWr.setOnloadListener(this);
}

implement(form_validation.prototype, [form_property, {
    validateExcept : false,
    validateEnable : true,
    isError        : false,
    err_array      : {},
    onload : function(evtWr) {
        var field, rules, rule, fld;
		// ini some rule
        for (field in this.fld_rules) {
			var rules = this.fld_rules[field];
			if (typeof(rules) == "object") for (i in rules) {
                var rule = rules[i];
			    if (typeof(rule) == "object") {
                    if (typeof(rule.isEnabled) == "undefined") rule.isEnabled = true;
                    if (typeof(rule.ruleData)  == "undefined") rule.ruleData = {};
                    if (typeof(rule.ini_rule)  != "undefined" && typeof(this[rule.ini_rule]) == "function") this[rule.ini_rule](rule);
                }
			}
		}
    	// Subscribe to validation
		this.form = this.winWr.getForm(this.form_name, 1);
    	if (this.form) {
    	    if (this.field_name) {
        	    if (typeof(this.field_name.constructor) == "object") {
        	        for (fld in this.field_name) this._set_subscribe(fld, (this.field_name[fld] ? this.field_name[fld] : this.ini_event), "run_validate");
        	    } else this._set_subscribe(this.field_name, this.ini_event, "run_validate");
        	} else if (this.form && this.form.elm) this.form.addListener(this, this.ini_event, "run_validate");
        	if (this.exception) {
    	        for (var fld in this.exception) this._set_subscribe(fld, (this.exception[fld] ? this.exception[fld] : this.ini_event), "set_exception");
        	}
        }
    },
    run_validate : function(evtWr) {
        if (this.validateExcept) {
            this.validateExcept = false;
            evtWr.eventDrop();
            return;
        }
        if (this.validateEnable) {
            var val, sfield;
			this.err_array = {};
		//	this.isError = false;

			if (this.checkBeforeValidation()) for (field in this.fld_rules) {
                val = this.getFieldValue(field);
                if (typeof(val) == "object" && val.__multidata) {
                    for (sfield in val.values) this._do_rule(val.values[sfield], field, sfield);
                } else this._do_rule(val, field);
			}
			if (this.checkAfterValidation()) {
			    if (this.isError) {
    				evtWr.eventDrop();
    				this.action_not_valid();
    				this.isError = false;
    			} else this.action_is_valid();
			}
		}
    },
    set_exception : function(evtWr) {
        this.validateExcept = true;
    },
    add_rule : function(field_name, rule_obj) {
        var rules = this.fld_rules[field_name];
        rules[rules.length] = rule_obj;
    },
    remove_rule : function(field_name, rule_name) {
        var ret = null;
        var rules = this.fld_rules[field_name];
        for (var i in rules) {
            if (rules[i].rule_name == rule_name) {
                ret = rules[i];
                delete rules[i];
                break;
            }
        }
        return ret;
    },
// ----- Internal functions -----
    _set_subscribe : function(field_name, event, func) {
        var field = this.winWr.getFormElement(this.form_name, field_name, 1);
	    if (field && field.elm) field.addListener(this, event, func);
    },
    _do_rule : function(val, field, sfield) {
        var rule, ruleName, i; 
        val = val.replace(/^\s*((\s*\S+)*)\s*$/, "$1");
        for (i in this.fld_rules[field]) {
            rule = this.fld_rules[field][i];
            if (typeof(rule) == "object" && rule.isEnabled) {
                ruleName = rule.rule_name;
                if (ruleName && typeof(this["rule_"+ruleName]) == "function") {
                    if ((val || !rule.not_empty) && !this["rule_"+ruleName](val,rule.ruleData)) {
                        this.err_array[sfield ? sfield : field] = rule;
                        this.isError = true;
                        return false;
                    }
                } else if (this.winWr.config.DebugMode) alert(ruleName ? "Incorrect rule - rule_" + rule.rule_name : "Rule is'n set");
            }
        }
        return true;
    },
    _regexp_min_max : function(val, regexp, min, max, format) {

        if (regexp.exec(val)) {
            if (format == "int") val = parseInt(val);
            else if (format == "float") val = parseFloat(val);
            else if (format == "date") val = this._date2string(val);
            if ((typeof(min) == "undefined" || val>=min) && (typeof(max) == "undefined" || val<=max)) return true;
        }
        return false;
    },
    _conv4compare : function (val, data) {
        var cmp = [val, this.getFieldValue(data.compare_field)];
    	if (data.data_type == 'date' || data.data_type == 'datetime') {
    		cmp[0] = this._date2string(cmp[0], data);
    		cmp[1] = this._date2string(cmp[1], data);
    	}
        return cmp;
    },
    _date2string : function(val, data) {
    	if (typeof(val) == "undefined") return val;
        var re = data.date_format; // Regexp for date
		if (typeof(re) == "undefined") {
			if (this.winWr.config.DebugMode) alert("Need set date format for check interval!");
			return null;
		}
    	var order = data.date_order; // Order of date elements in regexp. Example: {y:3,m:2,d:1,h:4,i:5,s:6}
		if (typeof(order) != "object") {
			if (this.winWr.config.DebugMode) alert("Need set order date element!");
			return null;
		}
    	var da = re.exec(val);
    	if (!da) return null;

    	if (da[order.y].length == 2) { // year has 2 digits
    		var threshold = parseInt(data.year2digits_threshold);
    		if (threshold == "NaN" || threshold == 0) threshold = 60;
    	    da[order.y] = parseInt(da[order.y]) < threshold ? "19" + da[order.y] : "20" + da[order.y];
		}
    	var ret = "";
    	for (var k in order) ret += (da[order[k]].length < 2 ? "0" : "") + da[order[k]];
    	return ret;
    },

// ---------- This actionr may be replace to other -----------
    checkBeforeValidation : function() {
        return true;
    },
    checkAfterValidation : function() {
        return true;
    },
    action_is_valid : function() {
    },
    action_not_valid : function() {
        var err_txt = "";
    	for (var i in this.err_array) err_txt += this.err_array[i].error_msg + "\n"
    	if(err_txt) alert(err_txt);
    },


// ---------- Validate rules -----------
    rule_is_required : function(val) {
        if (val && val.constructor == Array) {
            if (val.length == 1) val = val[0];
            else return val.length > 1 ? true : false;
        }
        return val != 0 && val != null;
    },
    rule_is_alphalogin : function(val) {
        return this._regexp_min_max(val, /^[A-Z0-9][\w\-@\.]*$/i);
    },
    rule_is_alphanumeric : function(val) {
        return this._regexp_min_max(val, /^[A-Z0-9][\w\-]*$/i);
    },
    rule_is_int : function(val, data) {
        return this._regexp_min_max(val, /^\-?\d+$/, data.min_value, data.max_value, "int");
    },
    rule_is_float : function(val, data) {
        return this._regexp_min_max(val, /^\d+(\.\d*)?$/, data.min_value, data.max_value, "float");
    },
    rule_is_email : function(val) {
        return this._regexp_min_max(val, /^[a-z_0-9!#*=.-]+@([a-z0-9-]+\.)+[a-z]{2,4}$/i);
    },
    rule_is_date : function(val, data) {
        cdata = {"date_format" : /(\d{4})\-(\d{2})\-(\d{2})/, "date_order" : {y:1,m:2,d:3}}
        return this._regexp_min_max(val, data.date_format, this._date2string(data.min_value), this._date2string(data.max_value), "date");
    },
    rule_match_regexp : function(val, data) {
        return this._regexp_min_max(val, data.regexp);
    },
    rule_equal_to : function(val, data) {
        var cmp = this._conv4compare(val, data);
        return cmp[0] == cmp[1];
    },
    rule_not_equal_to : function(val, data) {
        var cmp = this._conv4compare(val, data);
        return cmp[0] != cmp[1];
    },
    rule_greater_than : function(val, data) {
        var cmp = this._conv4compare(val, data);
        return cmp[0] > cmp[1];
    },
    rule_lesser_than : function(val, data) {
        var cmp = this._conv4compare(val, data);
        return cmp[0] < cmp[1];
    },
    rule_greater_or_equal_to : function(val, data) {
        var cmp = this._conv4compare(val, data);
        return cmp[0] >= cmp[1];
    },
    rule_lesser_or_equal_to : function(val, data) {
        var cmp = this._conv4compare(val, data);
        return cmp[0] <= cmp[1];
    },
    rule_date_interval : function(val, data) {
        var cval = this._date2string(val, data);
        if (typeof(cval) == "null") return false;
		if (typeof(data.max_value) != "undefined" && (cval > this._date2string(data.max_value, data))) return false;
		if (typeof(data.min_value) != "undefined" && (cval < this._date2string(data.min_value, data))) return false;
        return true;
    }

}]);
