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 (isDefined(elm[i], "type") && 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 instanceof 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":
            if (isDefined(field, "length")) {
                for (i=0;i<field.length; i++) {
                    if (field[i].checked) return field[i].value;
                }
            } else if (isDefined(field, "type") && field.type == "radio") {
                return field.checked ? field.value : "";
            }
            return "";
        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;
            */
            return field.checked ? field.value : "";
        case "checkbox":
            return field.checked ? field.value : "";
        default:
            return isDefined(field, "value") ? field.value : null;
        }
    },
    getFieldType : function(field){
        var elm, re, rArr, i;
        if (field) {
            if (isDefined(field, "type")) {
                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 (isDefined(elm[i], "type") && 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 (isDefined(field, "length") && 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 (!isDefined(rule, "isEnabled")) rule.isEnabled = true;
                    if (!isDefined(rule, "ruleData"))  rule.ruleData = {};
                    if (isDefined(rule, "ini_rule") && 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 (val != null) {
                    if (typeof(val) == "object") {
                        for (sfield in val.values) this._do_rule(val.values[sfield], field, sfield);
                    } else this._do_rule(val, field);
                } else if (this.winWr.config.DebugMode) {
                    alert('Not found field "' + 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, rgxp, data, format) {
        if (!this._regexp(val, rgxp)) return false;
        var min, max , c1, c2;
        c1 = !isDefined(data, "min_value");
        c2 = !isDefined(data, "max_value");
        if (format == "date") {
            val = this._date2string(val);
            min = c1 ? val : this._date2string(data.min_value);
            max = c2 ? val : this._date2string(data.max_value);
        }else {
            if (format == "int") val = parseInt(val);
            else if (format == "float") val = parseFloat(val);
            min = c1 ? val : data.min_value;
            max = c2 ? val : data.max_value;
        }
        return (val>=min && val<=max);
    },
    _regexp : function(val, rgxp) {
        if (typeof(rgxp) == "string") {
            var md = "";
            if (rgxp.substr(0, 1) == "/") {
                while (rgxp.length > 2 && rgxp.substr(rgxp.length - 1, 1) != "/") {
                    md += rgxp.substr(rgxp.length - 1, 1);
                    rgxp = rgxp.substr(0, rgxp.length - 1);
                }
                rgxp = rgxp.substr(1, rgxp.length - 2);
            }
            rgxp = new RegExp(rgxp, md);
        }
        return rgxp.exec(val) != null;
    },
    _conv4compare : function (val, data) {
        var cmp = [val, this.getFieldValue(data.compare_field)];
    	if (isDefined(data, "data_type") && (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 (!isDefined(val) || !isDefined(data)) return null;
		if (!isDefined(data, "date_format")) {
			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 = data.date_format.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() {
        this.broadcastMessage("isValid");
    },
    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);
        this.broadcastMessage("isNotValid");
    },


// ---------- Validate rules -----------
    rule_is_required : function(val) {
        if (val && val instanceof Array) {
            if (val.length == 1) val = val[0];
            else return val.length > 1 ? true : false;
        }
        return val != "" && val != 0 && val != null;
    },
    rule_is_alphalogin : function(val) {
        return this._regexp(val, /^[A-Z0-9][\w\-@\.]*$/i);
    },
    rule_is_alphanumeric : function(val) {
        return this._regexp(val, /^[A-Z0-9][\w\-]*$/i);
    },
    rule_is_int : function(val, data) {
        return this._regexp_min_max(val, /^\-?\d+$/, data, "int");
    },
    rule_is_float : function(val, data) {
        return this._regexp_min_max(val, /^\d+(\.\d*)?$/, data, "float");
    },
    rule_is_email : function(val) {
        return this._regexp(val, /^[a-z_0-9!#*=.-]+@([a-z0-9-]+\.)+[a-z]{2,4}$/i);
    },
    rule_is_date : function(val, data) {
        if (!data.date_format) {
            data.date_format = /(\d{2,4})[-/.](\d{2})[-/.](\d{2,4})/
        }
        if (!data.date_order) {
           data.date_order = {m:2,d:3,y:1};
        }
        return this._regexp_min_max(val, data.date_format, data, "date");
    },
    rule_match_regexp : function(val, data) {
        return this._regexp(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 (isDefined(data, "max_value") && (cval > this._date2string(data.max_value, data))) return false;
		if (isDefined(data, "min_value") && (cval < this._date2string(data.min_value, data))) return false;
        return true;
    }

}, (new basicBroadcaster())]);
