/**
 * Create a validaton engine
 * 
 * @param string form_id			ID of the form
 * @param string alert_type		Type of alert to show the user (current values: alert(default), inline)
 * @param string message_box	ID of the element in which to insert the message (only used for inline alerts)
 * @param bool autoload			Whether to automatically register the handler for the submit event (true(default), false(not recommended))
 */
var Validate = new Activa.Class({
	forms: {},
	message: '',
	invalid: '',
	currentForm: null,
	autoload: true,
	init: function initValidate(alert_type, message_box, autoload) {
		this.message_box = message_box || '';
		// inline_each doesn't require a message_box
		if ( ( alert_type && this.message_box ) || alert_type == 'inline_each' || alert_type == 'inline_submit' ) {
			this.alert_type = alert_type;
		} else {
			this.alert_type = 'alert';
		}
		this.autoload = autoload === undefined ? true : Boolean(autoload);
	},
	addForm: function addForm(form) {
		var form_id = '';
		if ( typeof form == 'object' ) {
			form_id = form.id;
			this.forms[form.id] = form;
		} else {
			form_id = form;
			this.forms[form] = new Form(form);
		}
		this.currentForm = form_id;
	},
	validate: function validate(form_id) {
		form_id = form_id || '';
		if ( form_id ) {
			if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
				return Inline_Validate.validate(this.forms[form_id].rules, new Inline_Validate.Focus());
			} else {
				return this.forms[form_id].validate();
			}
		}
		var errors = false;
		for ( var id in this.forms ) {
			if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
				if ( !Inline_Validate.validate(this.forms[id].rules) ) {
					errors = true;
				}
			} else {
				if ( !this.forms[id].validate() ) {
					return false;
				}
			}
		}
		if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
			return !errors;
		}
		return true;
	},
	end: function end() {
		if ( !this.autoload ) {
			return;
		}
		// Add Dynamic Rule Checking, when each field is blur'd
		if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
		
			var error_on_submit = this.alert_type == 'inline_submit';
			
			for ( var form_id in this.forms ) {
				var form = this.forms[form_id];
				var success_rules = [];
				for ( i = 0; i < form.rules.length; i++ ) {
					var rule = form.rules[i];
					if ( rule instanceof ConditionalSuccess ) {
						success_rules.push(rule);
					}
				}
				(function registerSuccess(cur_form) {
					Activa.registerEvent(did(cur_form.id), 'keyup', function(e) {
						Inline_Validate.chkSuccess(success_rules, error_on_submit);
					});
					Activa.registerEvent(did(cur_form.id), 'submit', function(e) {
						Inline_Validate.chkSuccess(success_rules, error_on_submit);
					});
				}.bind(this))(form);
				for ( var i = 0, z = form.rules.length; i < z; i++ ) {
					(function registerValidate(cur_rule, error_on_submit) {
						if ( cur_rule.field ) {
							var field_id = cur_rule.field.id;
							var instance = Inline_Msg.getInstance(did(field_id), !error_on_submit);
							instance.addRule(cur_rule);
						}
					}.bind(this))(form.rules[i], error_on_submit);
				}
			}
		}
		for ( var id in this.forms ) {
			(function registerSubmit(form_id) {
				Activa.registerEvent(did(form_id), 'submit', function(e) {
					e.stopPropagation();
					if ( !this.validate(form_id) ) {
						e.preventDefault();
						this.message = this.forms[form_id].message;
						this.invalid = this.forms[form_id].invalid;
						return this.notify(this.forms[form_id], this.invalid);
					}
				}.bind(this));
			}.bind(this))(id);
		}
	},
	notify: function notify(form, field) {
		var form = did(form.id);
		if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
			// Do nothing here
			return;
		}
		if ( this.alert_type == 'inline' ) {
			var msgbox = did(this.message_box);
			if ( msgbox ) {
				msgbox.innerHTML = this.message;
				return false;
			}
		}
		alert(this.message);
		if ( field ) {
			field.focus();
		}
		return false;
	}
});

var RuleContainer = new Activa.Class({
	containerStack: [],
	currentContainer: null,
	addRule: function addRule(rule) {
		var args = Activa.toArray(arguments);
		rule = false;
		args.forEach(function(item) {
			if ( rule === false && (item instanceof Rule || item instanceof RuleContainer) ) {
				rule = item;
			}
		});
		if ( rule === false ) {
			return;
		}
		if ( this.currentContainer instanceof RuleContainer ) {
			this.currentContainer.addRule.apply(this.currentContainer, args);
			return false;
		}
		if ( rule instanceof RuleContainer ) {
			this.containerStack.push(rule);
			this.currentContainer = this.containerStack[this.containerStack.length-1];
		}
		return rule;
	},
	endContainer: function endContainer() {
		
		// if the currentContainer is a RuleContainer and it has a currentContainer end that container
		if ( this.currentContainer instanceof RuleContainer && this.currentContainer.currentContainer instanceof RuleContainer ) {
			this.currentContainer.endContainer();
			return;
		}
		
		this.containerStack.pop();
		if ( this.containerStack.length > 0 ) {
			this.currentContainer = this.containerStack[this.containerStack.length-1];
		} else {
			this.currentContainer = null;
		}
	}
});

/**
 * Define a form to attach validation rules to
 * 
 * @param string form_id		ID of the form
 */
var Form = new Activa.Class({
	Extends: RuleContainer,
	id: '',
	rules: [],
	message: '',
	invalid: '',
	init: function initForm(form_id) {
		this.id = form_id;
		var form = did(this.id);
		if ( !form ) {
			throw new ReferenceError('form_id "' + this.id + '" is not a valid id of a form.');
		}
	},
	addRule: function addRule(rule) {
		rule = this.root.apply(this, arguments);
		if ( !rule ) {
			return;
		}
		rule.setForm(this);
		this.rules.push(rule);
	},
	validate: function validateForm(){
		var rule;
		for ( var i = 0, z = this.rules.length; i < z; i++ ) {
			rule = this.rules[i];
			if ( !rule.validate() ) {
				this.message = rule.message;
				this.invalid = rule.field;
				return false;
			}
		}
		return true;
	}
});

/**
 * Define basic validation rule.
 * 
 * @param string field		ID of field
 * @param string message	Error message to display when validation fails
 * @param string match		Match value (available for rules to use to match a value)
 * @param bool required		Whether the rule is required for the form to successfully validate (true, false(default))
 */
var Rule = new Activa.Class({
	form: null,
	field: '',
	field_id: '',
	message: '',
	match: '',
	required: false,
	init: function initRule(field, message, match, required) {
		var ftmp = null;
		if ( typeof field == 'object' ) {
			if ( field.id ) {
				ftmp = did(field.id);
				this.field_id = field.id;
			} else if ( field.name ) {
				ftmp = dbn(field.name);
				ftmp = ftmp.length ? ftmp[0] : null;
				this.field_id = field.name;
			}
		} else {
			ftmp = did(field);
			this.field_id = field;
		}
		this.field = ftmp;	
		this.message = message || '';
		this.match = match || '';
		this.required = !!(required);
	},
	setForm: function setForm(form) {
		if ( !(form instanceof Form) ) {
			throw new TypeError('form must be a valid Form object');
		}
		this.form = form;
	},
	validate: function validateRule() {
		if ( this.field.type != 'select-multiple' ) {
			if ( !this.required ) {
				if ( this.field.value.trim() == '' ) {
					return true;
				}
			}
			return false;
		}
	}
});

/**
 * Create a container for conditional rules
 * 
 * @param string field
 */
var Conditional = new Activa.Class({
	Extends: RuleContainer,
	form: '',
	rules: {},
	elses: [],
	message: '',
	condField: '',
	field: '',
	field_id: '',
	init: function initConditional(field) {
		var ftmp = null;
		if ( typeof field == 'object' ) {
			if ( field.id ) {
				ftmp = did(field.id);
				this.field_id = field.id;
			} else if ( field.name ) {
				ftmp = dbn(field.name);
				ftmp = ftmp.length ? ftmp[0] : null;
				this.field_id = field.name;
			}
		} else {
			ftmp = did(field);
			this.field_id = field;
			this.field = ftmp;
		}
		if ( !ftmp ) {
			throw new ReferenceError('field "' + this.field_id + '" must be a valid element id or name.');
		}
		this.condField = ftmp;
	},
	addRule: function addConditionalRule(matching, rule) {
		var args = Activa.toArray(arguments);
		rule = this.root.apply(null, args);
		if ( !rule ) {
			return;
		}
		if ( this.form ) {
			rule.setForm(this.form);
		}
		if ( matching === null ) {
			this.elses.push(rule);
		} else {
			if ( !this.rules[matching] ) {
				this.rules[matching] = [];
			}
			this.rules[matching].push(rule);
		}
	},
	setForm: function setConditionalForm(form) {
		if ( !(form instanceof Form) ) {
			throw new TypeException('form must be a valid Form object');
		}
		this.form = form;
		for ( var match in this.rules ) {
			this.rules[match].forEach(function(rule) {
				rule.setForm(form);
			});
		}
		this.elses.forEach(function(rule) {
			rule.setForm(form);
		});
	},
	validate: function validateConditional() {
		var validate_else = true;
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			if ( this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)) ) {
				validate_else = false;
				var rule = null;
				for ( var i = 0, z = this.rules[match].length; i < z; i++ ) {
					rule = this.rules[match][i];
					if ( !rule.validate() ) {
						this.message = rule.message;
						this.field = rule.field;
						return false;
					}
				}
			}
		}
		if ( validate_else ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			if ( this.condField && ((toggled && !this.condField.checked) || (!toggled && this.condField.value != match)) ) {
				var rule = null;
				for ( var i = 0, z = this.elses.length; i < z; i++ ) {
					rule = this.elses[i];
					if ( !rule.validate() ) {
						this.message = rule.message;
						this.field = rule.field;
						return false;
					}
				}
			}
		}
		return true;
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		var errors = false;
		var validate_else = true;
		from_success = from_success === undefined ? false :Boolean(from_success);
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			var valid = (this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)));
			if ( valid ) {
				validate_else = false;
			}
			var rule = null;
			for ( var i = 0, z = this.rules[match].length; i < z; i++ ) {
				rule = this.rules[match][i];
				if ( valid ) {
					if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
						errors = true;
					}
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		}
		var rule = null;
		for ( var i = 0, z = this.elses.length; i < z; i++ ) {
			rule = this.elses[i];
			if ( validate_else ) {
				if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
					errors = true;
				}
			} else {
				Inline_Validate.validateFalse(rule);
			}
		}
		
		return !errors;
	}
});

/**
 * Create a container for conditional rules
 * 
 * @param Rule field
 */
var ConditionalRule = new Activa.Class({
	Extends: Conditional,
	condRule: null,
	init: function initConditionalRule(rule) {
		this.condRule = rule;
		this.field = rule.field;
		if ( this.form && rule ) {
			rule.setForm(this.form);
		}
	},
	addRule: function addConditionalRuleRule(rule) {
		var args = Activa.toArray(arguments);
		// If there is more than 1 arg, we want to pass to a child RuleContainer
		if ( args.length > 1 || this.currentContainer instanceof RuleContainer ) {
			this.root.apply(null, args);
		} else {
			this.root("all", rule);
		}
	},
	setForm: function set_form(form) {
		this.root(form);
		if ( this.condRule ) {
			this.condRule.setForm(form);
		}
	},
	validate: function validateConditionalRule() {
		if ( this.condRule.validate() ) {
			for ( var i = 0; i < this.rules["all"].length; i++ ) {
				var rule = this.rules["all"][i];
				if ( !rule.validate() ) {
					this.message = rule.message;
					this.field = rule.field;
					return false;
				}
			}
		}
		return true;
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		from_success = from_success === undefined ? false :Boolean(from_success);
		var errors = false;
		if ( this.condRule.validate() ) {
			for ( var i = 0; i < this.rules["all"].length; i++ ) {
				var rule = this.rules["all"][i];
				if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		} else {
			Inline_Validate.validateFalse(this);
		}
		return !errors;
	}
});

var ConditionalSuccess = new Activa.Class({
	Extends: Conditional,
	message: '',
	required: true,
	init: function initConditionalSuccess(field, message, required) {
		this.field = did(field);
		this.message = message;
		this.required = required === undefined? true : Boolean(required);
	},
	addRule: function addConditionalSuccessRule(type, rule) {
		var type = type === undefined? 'hide_errors' : type;
		var args = Activa.toArray(arguments);
		// If there is more than 1 arg, we want to pass to a child RuleContainer
		if ( args.length > 2 || this.currentContainer instanceof RuleContainer ) {
			this.root.apply(null, args);
		} else {
			this.root(type, rule);
		}
	},
	validate: function validate () {
		return this.validateRecursive(null);
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		from_success = from_success === undefined ? false :Boolean(from_success);
		var errors = false;
		if ( this.rules && this.rules['hide_errors'] ) {
			for ( i = 0; i < this.rules['hide_errors'].length; i++ ) {
				var rule = this.rules['hide_errors'][i];
				if ( !rule.validate() && from_success ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		}
		if ( this.rules && this.rules['show_errors'] ) {
			for ( x = 0; x < this.rules['show_errors'].length; x++ ) {
				var rule2 = this.rules['show_errors'][x];
				if ( !Inline_Validate.validateRule(rule2, focus, true) ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule2);
				}
			}
		}
		if ( this.required ) {
			return !errors;
		}
		return true;
	}
});

var ConditionalError = new Activa.Class({
	Extends: Conditional,
	message: '',
	rule_outcomes: [],
	init: function initConditionalError(field, message) {
		this.field = did(field);
		this.message = message;
	},
	addRule: function addConditionalErrorRule(type, rule, outcome) {
		var type = type === undefined? 'all' : type;
		var outcome = outcome === undefined? true : Boolean(outcome);
		//var args = Activa.toArray(arguments);
		this.root(type, rule);
		this.rule_outcomes.push(outcome);
	},
	validate: function validate() {
		return this.validateRecursive(null);
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		var errors = false;
		if ( this.rules && this.rules['all'] ) {
			for ( var i = 0; i < this.rules['all'].length; i++ ) {
				var rule = this.rules['all'][i];
				if ( Inline_Validate.validateRuleTest(rule) === this.rule_outcomes[i] ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		}
		Inline_Msg.getInstance(did(this.field.id)).error(this.message, errors, true);
		return !errors;
	}
});

/**
 *
 * Validation Rules
 * 
 */



var Rule_Alphanumeric = new Activa.Class({
	Extends: Rule,
	validate: function validateAlphanumeric() {
		if ( this.root() ) {
			return true;
		}
		return /^[a-zA-Z0-9]+$/.test(this.field.value);
	}
});

var Rule_Alpha = new Activa.Class({
	Extends: Rule,
	validate: function validateAlpha() {
		if ( this.root() ) {
			return true;
		}
		return /^[a-zA-Z]+$/.test(this.field.value);
	}
});

var Rule_Between = new Activa.Class({
	Extends: Rule,
	min: 0,
	max: 0,
	init: function initBetween(field, message, match, required) {
		this.root(field, message, match, required);
		var parts = match.split('-');
		this.min = parseFloat(parts[0] || 0);
		this.max = parseFloat(parts[1] || 0);
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var flen = parseFloat(this.field.value.length);
		return (flen >= this.min && flen <= this.max);
	}
});

var Rule_Captcha = new Activa.Class({
	Extends: Rule,
	validate: function validateCaptcha() {
		if ( this.root() ) {
			return true;
		}
		return this.field.value.trim() != '';
	}
});

var Rule_Checkbox = new Activa.Class({
	Extends: Rule,
	validate: function validateCheckbox() {
		if ( this.root() ) {
			return true;
		}
		return !!(this.field.checked);
	}
});

var Rule_Checkboxes = new Activa.Class({
	Extends: Rule,
	validate: function validateCheckboxes() {
		var boxes = Activa.toArray(dbn(this.field_id+'[]', this.form || null));
		var count = 0;
		boxes.forEach(function isChecked(box) {
			if ( box.checked ) {
				count++;
			}
		});
		return (count == this.match);
	}
});

var Rule_CreditCardNumber = new Activa.Class({
	Extends: Rule,
	validate: function validateCreditCardNumber() {
		if ( this.root()  ) {
			return true;
		}
		this.field.value = this.field.value.replace(/[^\d]/g, '');
		var len = this.field.value.length;
		if ( !(/^[\d]+$/.test(this.field.value)) || len < 13 || len > 16 ) {
			return false;
		}
		return true;
	}
});

var Rule_Email = new Activa.Class({
	Extends: Rule,
	regex: null,
	init: function initEmail(field, message, match, required) {
		this.root(field, message, match, required);
		this.regex = new RegExp(match);
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		return (this.regex.test(this.field.value));
	}
});

var Rule_Equals_Field = new Activa.Class({
	Extends: Rule,
	allowempty: false,
	init: function initEqualsField(field, message, match, required, allowempty) {
		this.root(field, message, match, required);
		this.allowempty = allowempty === undefined? false : Boolean(allowempty);
	},
	validate: function validateEqualsField() {
		if ( this.root() ) {
			return true;
		}
		var field = did(this.match);
		if ( !this.allowempty ) {
			if ( this.field.value == '' || ( field && field.value == '' ) ) {
				return false;
			}
		}
		return (field && this.field.value == field.value); 
	}
});

var Rule_Equals = new Activa.Class({
	Extends: Rule,
	validate: function validateEquals() {
		if ( this.root() ) {
			return true;
		}
		return (this.field.value == this.match);
	}
});

var Rule_Greater = new Activa.Class({
	Extends: Rule,
	min: 0,
	init: function initGreater(field, message, match, required) {
		this.root(field, message, match, required);
		this.min = parseFloat(match) || 0;
	},
	validate: function validateGreater() {
		if ( this.root() ) {
			return true;
		}
		var val = parseFloat(this.field.value);
		return (!isNaN(val) && val > this.min);
	}
});

var Rule_Length = new Activa.Class({
	Extends: Rule,
	len: 0,
	init: function initLength(field, message, match, required) {
		this.root(field, message, match, required);
		this.len = parseInt(this.match) || 0;
	},
	validate: function validateLength() {
		if ( this.root() ) {
			return true;
		}
		var len = parseInt(this.field.value.length);
		return (!isNaN(len) && len == this.len);
	}
});

var Rule_Less = new Activa.Class({
	Extends: Rule,
	max: 0,
	init: function initLess(field, message, match, required) {
		this.root(field, message, match, required);
		this.max = parseFloat(match) || 0;
	},
	validate: function validateLess() {
		if ( this.root() ) {
			return true;
		}
		var val = parseFloat(this.field.value);
		return (!isNaN(val) && val < max);
	}
});

var Rule_Longer = new Activa.Class({
	Extends: Rule,
	min: 0,
	init: function initLonger(field, message, match, required) {
		this.root(field, message, match, required);
		this.min = parseInt(match) || 0;
	},
	validate: function validateLonger() {
		if ( this.root() ) {
			return true;
		}
		return (parseInt(this.field.value.length) > this.min);
	}
});

var Rule_Match = new Activa.Class({
	Extends: Rule,
	regex: null,
	init: function initMatch(field, message, match, required) {
		this.root(field, message, match, required);
		this.regex = new RegExp(match);
	},
	validate: function validateMatch() {
		if ( this.root() ) {
			return true;
		}
		return (this.regex.test(this.field.value));
	}
});

var Rule_Notequals = new Activa.Class({
	Extends: Rule,
	validate: function validateNotEquals() {
		if ( this.root() ) {
			return true;
		}
		return (this.field.value != this.match);
	}
});

var Rule_Numeric = new Activa.Class({
	Extends: Rule,
	validate: function validateNumeric() {
		if ( this.root() ) {
			return true;
		}
		return /^[0-9]+$/.test(this.field.value);
	}
});

var Rule_Phone_Int = new Activa.Class({
	Extends: Rule,
	validate: function validatePhoneInt() {
		if ( this.root() ) {
			return true;
		}
		var val = this.field.value = this.field.value.trim().replace(/[\s-\(\)]/g, '');
		return /^\+?[\d+]{10,16}$/.test(val);
	}
});

var Rule_Phone = new Activa.Class({
	Extends: Rule,
	validate: function validatePhone() {
		if ( this.root() ) {
			return true;
		}
		var val = this.field.value = this.field.value.trim().replace(/[\s-\(\)]/g, '');
		return /^\d{10}$/.test(val);
	}
});

var Rule_RadioList = new Activa.Class({
	Extends: Rule,
	validate: function validateRadioList() {
		var radios = Activa.toArray(dbn(this.field_id));
		var value = false; 
		radios.forEach(function(radio) {
			if ( radio.checked ) {
				if ( this.match && this.match != '' && this.match != '0' ) {
					value = radio.value == this.match;
				} else {
					value = true;
				}
			}
		}.bind(this));
		return value;
	}
});

var Rule_Range = new Activa.Class({
	Extends: Rule_Between,
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var flen = parseFloat(this.field.value);
		return (flen > this.min && flen < this.max);
	}
});

var Rule_Shorter = new Activa.Class({
	Extends: Rule,
	max: 0,
	init: function initShorter(field, message, match, required) {
		this.root(field, message, match, required);
		this.max = parseInt(match) || 0;
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var val = parseInt(this.field.value.length);
		return (!isNaN(val) && val < this.max);
	}
});

var Rule_Text = new Activa.Class({
	Extends: Rule,
	validate: function validateText() {
		if ( this.root() ) {
			return true;
		}
		return !this.field.value.trim() == '';
	}
});

var Rule_Show = new Activa.Class({
	Extends: Rule,
	validate: function validateText() {
		Activa.DOM.showID(this.field);
		return true;
	}
});

var Rule_Hide = new Activa.Class({
	Extends: Rule,
	validate: function validateText() {
		Activa.DOM.hideID(this.field);
		return true;
	}
});

var Inline_Validate = new Activa.Class({
});

Inline_Validate.statics({
	validate: function validate(rules, focus, from_success) {
		var focus = focus;
		var errors = false;
		for ( var i = 0; i < rules.length; i++ ) {
			var rule = rules[i];
			if ( !this.validateRule(rule, focus, from_success) ) {
				errors = true;
			}
		}
		return !errors;
	},
	validateRule: function validateRule(rule, focus, from_success) {
		var errors = false;
		// From Success means its updating from a success conditional. Which is updated every keyup on the 
		// conditionals fields, and child fields.
		from_success = from_success === undefined ? false :Boolean(from_success);
		if ( rule instanceof RuleContainer ) {
			if ( !rule.validateRecursive(focus, from_success) ) {
				errors = true;
			}
		} else {
			//console.log(rule, rule.validate());
			if ( !rule.validate() ) {
				Inline_Msg.getInstance(did(rule.field.id)).error(rule.message, true, focus || from_success? true : false);
				if ( focus ) {
					focus.focusField(rule.field.id);
				}
				errors = true
				//Inline_Validate.validateFalse(rule);
			} else {
				Inline_Msg.getInstance(did(rule.field.id)).error(rule.message, false, focus || from_success? true : false);
			}
		}
		return !errors;
	},
	validateRuleTest: function validateRule(rule) {
		var error = false;
		/*
		if ( rule instanceof RuleContainer ) {
			
		} else {
			return rule.validate();
		}
		*/
		return rule.validate();
	},
	chkSuccess: function check_success(rules, error_on_submit) {
		var arr_rules = Activa.toArray(rules);
		arr_rules.forEach(function(rule) {
			var inst = Inline_Msg.getInstance(rule.field)
			var had_errors = inst.hasErrors();
			var show = rule.validateRecursive(null, true);
			if ( show && !inst.showing_success ) {
				inst.showing_success = true;
				inst.success(rule.message, true, had_errors);
			} else if ( !show ) {
				inst.showing_success = false;
				inst.success(rule.message, false);
			}
		});
	},
	validateFalse: function validate_false(rule) {
		
		if ( !(rule instanceof RuleContainer) ) {
			var inst = Inline_Msg.getInstance(did(rule.field.id));
			inst.error(rule.message, false, true);
		} else if ( rule instanceof ConditionalSuccess ) {
			// no support for nested success yet
		} else if ( rule instanceof ConditionalError ) {
			// no handles itself
		} else if ( rule instanceof ConditionalRule ) {
			if ( !rule.condRule.validate() ) {
				for ( var i = 0; i < rule.rules["all"].length; i++ ) {
                    var cur_rule = rule.rules["all"][i];
                    if ( cur_rule instanceof RuleContainer ) {
                        this.validateFalse(cur_rule);
                        continue;
                    }
                    // Hide Error
                    var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
                    inst.error(cur_rule.message, false, true);
                }
			}
		} else if ( rule instanceof Conditional ) {
			var validate_else = true;
			for ( var match in rule.rules ) {
				var toggled = (rule.condField.type == 'checkbox' || rule.condField.type == 'radio');
				var valid = rule.condField && ((toggled && rule.condField.checked) || (!toggled && rule.condField.value == match));
				if ( valid ) {
					validate_else = false;
					var cur_rule = null;
					for ( var i = 0, z = rule.rules[match].length; i < z; i++ ) {
						cur_rule = rule.rules[match][i];
						if ( cur_rule instanceof RuleContainer ) {
							this.validateFalse(cur_rule);
							continue;
						}
						var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
						//inst.error(cur_rule.message, !cur_rule.validate());
						inst.error(cur_rule.message, false, true);
					}
				}
			}
			if ( validate_else ) {
				var cur_rule = null;
				for ( var i = 0, z = rule.elses.length; i < z; i++ ) {
					cur_rule = rule.elses[i];
					if ( cur_rule instanceof RuleContainer ) {
						this.validateFalse(cur_rule);
						continue;
					}
					var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
					//inst.error(cur_rule.message, !cur_rule.validate());
					inst.error(cur_rule.message, false, true);
				}
			}
		} else {
			var inst = Inline_Msg.getInstance(did(rule.field.id));
			inst.error(rule.message, false, true);
		}
	}
});

Inline_Validate.Focus = new Activa.Class({
	focused: false,
	field: null,
	focusField: function focus_field(field_id) {
		if ( this.focused ) {
			return false;
		}
		this.field = did(field_id);
		this.field.focus();
		this.focused = true;
	}
});

var Inline_Msg = new Activa.Class({
	field: null,
	holder: null,
	errors: {},
	showing: false,
	rules: [],
	has_focus: false,
	showing_success: false,
	do_blur: true,
	init: function construct(field, do_blur) {
		this.do_blur = do_blur === undefined ? true : Boolean(do_blur);
		this.field = field;
		var blur_type = 'blur';
		var node_name = this.field.nodeName.toUpperCase();
		this.chkHolder();
		if ( node_name == 'SELECT' || (node_name == 'INPUT' && (this.field.type.toUpperCase() == 'CHECKBOX' || this.field.type.toUpperCase() == 'radio')) ) {
			blur_type = 'change';
		}
		Activa.registerEvent(this.field, blur_type, function(e) {
			if ( this.do_blur ) {
				this.validate(true);
			}
			this.has_focus = false;
		}.bind(this));
		Activa.registerEvent(this.field, 'focus', function(e) {
			this.has_focus = true;
		}.bind(this));
	},
	hasErrors: function has_errors() {
		if ( this.holder && this.holder.childNodes ) {
			return this.holder.childNodes.length > 0
		}
		return false;
	},
	chkHolder: function check_holder(){
		// grabs the holder if it exists from php side
		var holder = Activa.DOM.next(this.field);
		if ( !holder || holder.nodeName.toUpperCase() != Inline_Msg.holder_tag || !Activa.DOM.hasClass(holder, Inline_Msg.css_class) ) {
			this.showing = false;
		} else {
			this.holder = holder;
			this.showing = true;
		}
	},
	mkHolder: function make_holder(){
		// Create the holder Element if needed
		var holder = Activa.DOM.next(this.field);
		if ( !holder || holder.nodeName.toUpperCase() != Inline_Msg.holder_tag || !Activa.DOM.hasClass(holder, Inline_Msg.css_class) ) {
			this.holder = document.createElement(Inline_Msg.holder_tag);
			Activa.DOM.addClass(this.holder, Inline_Msg.css_class);
			this.field.parentNode.insertBefore(this.holder, this.field.nextSibling);
		}
	},
	destroyHolder: function destroy_holder() {
		if ( this.holder && this.holder.parentNode ) {
			this.holder.parentNode.removeChild(this.holder);
		}
	},
	addRule: function add_rule(rule) {
		if ( this.rules.indexOf(rule) < 0 ) {
			this.rules.push(rule);
			if ( rule instanceof Conditional ) {
				if ( rule.rules ) {
					for ( var match in rule.rules ) {
						for ( var i = 0; i < rule.rules[match].length; i++ ) {
							var rule2 = rule.rules[match][i];
							Inline_Msg.getInstance(did(rule2.field.id)).addRule(rule);
						}
					}
				}
			}
		}
	},
	validate: function validate(force) {
		force = force === undefined? false : Boolean(force);
		if ( force ) {
			this.has_focus = true;
		}
		//Inline_Validate.validate(this.rules, new Inline_Validate.Focus());
		Inline_Validate.validate(this.rules, false, true);
		if ( force ) {
			this.has_focus = false;
		}
	},
	error: function error(message, show, from_submit) {
		from_submit = from_submit === undefined ? false : Boolean(from_submit);
		if ( !from_submit && !this.has_focus ) {
			return;
		}
		return this.msg(message, Inline_Msg.message_types.error, show);
	},
	success: function success(message, show, had_errors) {
		return this.msg(message, Inline_Msg.message_types.success, show, had_errors);
	},
	msg: function msg(message, type, show, had_errors) {
		var show = show === undefined ? true : Boolean(show);
		var index = message+type.css;
		if ( !this.showing && show ) {
			this.show();
		}
		/*
		if ( show ) {
			Activa.DOM.addClass(this.holder, type.holder_css);
		} else {
			Activa.DOM.removeClass(this.holder, type.holder_css);
		}
		*/
		
		// IF Error doesn't exist on this field
		if ( !Inline_Msg.exists(index, this.errors) && show ) {
			var msg = document.createElement(Inline_Msg.tag);
			msg.appendChild(document.createTextNode(message));
			Activa.DOM.addClass(msg, type.css);
			this.holder.appendChild(msg);
			this.errors[index] = msg;
			//type.callback(message, type, show, msg).apply(this);
		} else {
			var msg = this.errors[index];
		}
		
		for ( var i = 0; i <= 1; i++ ) {
			if ( i == 0 ) {
				if ( typeof type.callback != 'function' ) {
					if ( !show ) {
						this.hideMsg(message, type.css);
						this.chkHide();
					}
				} else {
					type.callback.call(this, message, type, show, msg, had_errors);
				}
			} else {
				this.updateHolderCss();
			}
		}
		return msg;
	},
	show: function show() {
		//this.holder.style.display = '';
		this.mkHolder();
		this.showing = true;
	},
	hide: function hide() {
		if ( typeof Inline_Msg.hide_inline_msg == 'function' ) {
			Inline_Msg.hide_inline_msg.call(this);
		} else {
			//this.holder.style.display = 'none';
			this.destroyHolder();
			this.showing = false;
		}
	},
	hideMsg: function hideMsg(message, type_css) {
		var index = message+type_css;
		delete this.errors[index];
		if ( !this.holder ) {
			return;
		}
		var length = this.holder.childNodes.length;
		for ( i = 0; i < length; i++ ) {
			var node = this.holder.childNodes[i];
			if ( Activa.DOM.hasClass(node, type_css) && node.innerHTML == message ) {
				this.holder.removeChild(node);
			}
		}
	},
	chkHide: function check_hide() {
		var hide = true;
		if ( !this.holder ) {
			return;
		}
		if ( this.holder.childNodes.length > 0 ) {
			for ( i = 0; i < this.holder.childNodes.length; i++ ) {
				var node = this.holder.childNodes[i];
				if ( node.nodeName && node.nodeName.toUpperCase() == Inline_Msg.tag ) {
					hide = false;
				}
			}
		}
		if ( hide ) {
			this.hide();
		}
	},
	updateHolderCss: function update_holder_css() {
		if ( !this.holder ) {
			return;
		}
		var types = {};
		for ( var key in Inline_Msg.message_types ) {
			types[key] = false;
		}
		if ( this.holder.childNodes.length > 0 ) {
			for ( i = 0; i < this.holder.childNodes.length; i++ ) {
				var node = this.holder.childNodes[i];
				if ( node.nodeName && node.nodeName.toUpperCase() == Inline_Msg.tag ) {
					for ( var key in types ) {
						if ( Activa.DOM.hasClass(node, Inline_Msg.message_types[key].css) ) {
							types[key] = true;
							Activa.DOM.addClass(this.holder, Inline_Msg.message_types[key].holder_css);
						}
					}
				}
			}
		}
		for ( var key in types ) {
			if ( types[key] == false ) {
				var type = Inline_Msg.message_types[key];
				Activa.DOM.removeClass(this.holder, type.holder_css);
			}
		}
	}
});

Inline_Msg.statics({
	/*
	css_error: 'error',
	css_success: 'success',
	css_class: 'dev_inline',
	css_holder_error: 'dev_inline_errors',
	css_holder_success: 'dev_inline_success',
	*/
	message_types: {
		error: {
			css: 'error', 
			holder_css: 'dev_inline_errors', 
			callback: null
		},
		success: {
			css: 'success', 
			holder_css: 'dev_inline_success', 
			callback: null
		}
	},
	hide_inline_msg: null,
	css_class: 'dev_inline',
	holder_tag: 'P',
	tag: 'STRONG',
	instances: {},
	getInstance: function getInstance(field, do_blur) {
		var instance = null;
		if ( instance = this.exists(field.id, this.instances) ) {
		} else {
			instance = this.instances[field.id] = new Inline_Msg(field, do_blur);
		}
		return instance;
	},
	exists: function field_exists(field_id, obj) {
		for ( var key in obj ) { 
			if ( key == field_id ) {
				return obj[key];
			}
		}
		return false;
	}
});

