function Form(f)
{
	var data = {isValid:true,id:f};
	var form = f;
	var parser = new XMLParser();
	var object = this;
	var formError = null;
	var sendUrl = formUrl;
	var formAnnotations;
	var formTag = $("form", $("#" + form));
	var loading = $(".loading", $("#" + form));
	formAnnotations = Annotation.getFormAnnotations(form);
	
	if(formTag.length > 0)
		formTag.bind("submit", function(){
			submitForm(true);
			return false;
		});
	var submit = $("input:submit, input:image, .submit", $("#" + form));
	
	submit.bind("click", function(){
		if($(this).attr("type") == undefined)
		{
			submitForm(true);
			return false;
		}
		else if(formTag.length == 0)
			submitForm(true);
	});
	
	data["fields"] = start();
	initForm();
	
	function start()
	{
		var fields = {};
		var items = $("*[@class^=formItem]", $("#" + form));
		for(var i = 0; i < items.length; i++){
			var item = $("*[@class^=formItem]:eq(" + i + ")", $("#" + form));
			var annotations = Annotation.getAnnotations(item)
			var input = $("input, select, textarea", item);
			var type = input.attr("type");
			if(type == undefined)
				type = "select";
			if(annotations.type != undefined)
				type = annotations.type;
			var name = input.attr("name");
			if(annotations.name != undefined)
				name = annotations.name;
			var errorContainer = $("*.errorMessage", item);
			if(annotations.targetErrorMessage != undefined)
				errorContainer = $("#" + annotations.targetErrorMessage);
			var field = 
			{
				name:name, 
				field:item,
				input:input, 
				annotations:annotations, 
				type:type,
				val:value(item, input, type, name), 
				error:errorContainer
			};
			fields[field.name] = field;
			field.addError = delegate(field, function(error){
				data.addError(this.name , error);
			});
		}
		return fields;
	}
	
	function value(field, input, type, name){
		if(type == "text" || type == "password" || type == "hidden" || type == "textarea")
		{
			try {
				if(type == "textarea")
					tinyMCE.triggerSave(false);
			}
			catch(err){}
			return $.trim(input.val());
		}
		if(type == "select"){
			var v = $.trim(input.val()); 
			return v == "nill" ? null : v;
		} 
		if(type == "checkbox")
			return checked(input.attr("name"));
		if(type == "date"){
			var day;
			var month;
			var year;
			var time = null;
			for(var i = 0; i < input.length; i++)
			{
				var item = $("select:eq(" + i + ")", field);
				if(item.length == 0)
				{
					item = $("input[@name=time]", field);
					if(item.length > 0 && $.trim(item.val()) != "")
						
						var v = $.trim(item.val());
						if(v != undefined)
						{
							var regExp = new RegExp("\\d\\d:\\d\\d");
							if(v.match(regExp)){
								var vs = v.split(":");
								var h = parseInt(vs[0]);
								var m = parseInt(vs[1]);
								if(h > 23)
									return false;
								if(m > 59)
									return false;
							}else{
								return false;
							}
							v = v + ":00";
						}
						time = v;
					continue;
				}
				if(item.val() == "nill")
					return null;
				if(item.is("[@name=day]"))
					day = item.val();
				if(item.is("[@name=month]"))
					month = item.val();
				if(item.is("[@name=year]"))
					year = item.val();
				
			}
			var result = year + "-";
			if(month.length < 2)
				result += "0" + (eval(month) + 1) + "-";
			else
				result += (eval(month) + 1) + "-";
			if(day.length < 2)
				result += "0" + day;
			else
				result += day;
			if(time != null)
				result += "T" + time;
			return result;
		}
		if(type == "radio")
		{
			var c = $("input[@type=radio]", field).is("[@checked]");
			if(!c)
				return null;
			return $("input[@type=radio][@checked]", field).val()
		}
		return null;
	}
	
	function submitForm(parse)
	{
		try{tinyMCE.triggerSave(true);}
		catch(err){}
		if(parse)
			parceForm();
		validateRequared();
		if(formAnnotations.validate != undefined)
			callMethod(formAnnotations.validate, data);
		if(data.isValid)
			sendFormData();
	}
	
	function initForm()
	{
		data.addError = function(f, er){
			data.isValid = false;
			data.fields[f].field.removeClass("formItem");
			data.fields[f].field.addClass("formItemError");
			data.fields[f].error.html(getMessage(form + "." + f + "." + er));
			object.setFormError("");
		}
		data.clearError = function(f){
			data.fields[f].field.removeClass("formItemError");
			data.fields[f].field.addClass("formItem");
			data.fields[f].error.html("");
			clearFormError();
		}
		data.addFormError = function(er){
			object.setFormError(er);
		}
	}
	
	this.reInitField = function(name){
		var f = data.fields[name];
		var input = $("input, select, textarea", f.field);
		f.input = input;
	}
	
	this.reInitVal = function(name, val){
		var f = data.fields[name];
		f.val = val;
	}
	
	function onError(msg){
		showLoading(false);
		if(formAnnotations.onError != undefined)
			callMethod(formAnnotations.onError, msg);
		else
			object.setFormError("error");
		data.isValid = false;
	}
	
	function clearFormError(){
		var formError = $("//*[@id = " + form + "]//*[@class=formErrorMessage]");
		if(formError.length > 0)
			formError.html("");
	}
	
	function showLoading(l){
		if(loading.length > 0){
			if(l){
				submit.css({display:"none"});
				loading.css({display:"inline"});
			}else{
				submit.css({display:"inline"});
				loading.css({display:"none"});
			}
		}
	}
	
	this.setFormError = function(error){
		var formError = $("#" + form + " .formErrorMessage");
		if(formError.length == 0)
			return;
		if(error == "")
			formError.html(getMessage(form));
		else
			formError.html(getMessage(form + "." + error));
		data.isValid = false;
	}
	
	function onGetResponse(response){
		var xml = true;
		if(formAnnotations.xml != undefined && formAnnotations.xml == "false")
			xml = false;
		var o = parser.deserialize(response);
		var name = "formResponce";
		if(formAnnotations.responce != undefined)
			name = formAnnotations.responce;
		var d = o[name];
		if(!xml && formAnnotations.onSuccess != undefined)
		{
			callMethod(formAnnotations.onSuccess, response, data);
			return;
		}
		if(d.valid == "true")
		{
			if(formAnnotations.onSuccess != undefined)
				callMethod(formAnnotations.onSuccess, d, data);
			else
				if(formAnnotations.successUrl != undefined)
					document.location.href = formAnnotations.successUrl;
		}
		else
		{
			var errors = d.errors[0].map;
			for(var i = 0; i < errors.length; i++)
				data.fields[errors[i].name].addError(errors[i].value);
			showLoading(false);	
			
		}
	}
	
	this.submit = function(){
		sendFormData();
	}
	
	this.validateAndSubmit = function(){
		submitForm(true);
	}
	
	this.validateAndSubmitNotParse = function(){
		submitForm(false);
	}
	
	function sendFormData()
	{
		var send;
		var xml = true;
		if(formAnnotations.xml != undefined && formAnnotations.xml == "false")
			xml = false;
		var method = xml ? "POST" : "GET";
		var dataType = "html";
		var d = {}
		if(xml)
			d[formAnnotations.objectName] = getFormObject();
		else
			d = getFormObject();
		if(formAnnotations.beforeSend != undefined)
			d = callMethod(formAnnotations.beforeSend, d);
		
		if(xml)
		{
			var o = parser.serialize(d);
			send = parser.toXMLString(o);
		}
		else
			send = d;	
		var url = sendUrl + formAnnotations.name;
		if(formAnnotations.url != undefined)
			url = formAnnotations.url;
		showLoading(true);
		$.ajax({
			type:method,
			data:send,
			cache:false,
			url:url,
			dataType:dataType,
			success:onGetResponse,
			error:onError
		});
	}
	
	this.loadingSwitch = function(val){
		showLoading(val);
	}
	
	
	function getFormObject()
	{
		var formData = {};
		for(var i in data.fields)
			formData[i] = data.fields[i].val;
		return formData;
	}
	
	function parceForm()
	{
		data.isValid = true;
		clearFormError();
		var fields = data.fields;
		for(var i in fields)
		{
			var field = fields[i];
			field.val = value(field.field, field.input, field.type, field.name);
			field.field.removeClass("formItemError");
			field.field.addClass("formItem");
			field.error.html("");
		}
	}
	
	function validateRequared()
	{
		for(var field in data.fields)
		{
			var f = data.fields[field];
			var ref = null;
			if(f.annotations.ref != undefined)
				ref = data.fields[f.annotations.ref];
			if(f.annotations.validation == "required" && (!f.val || f.val == '' || f.val == null))
			{
				if(ref == null || !ref.val || ref.val == '' || ref.val == null)
					f.addError("required");
			}
		}
	}
	
	function checked(name)
	{
		var res = false;
		$.each($("input:checked"), function(){
			if($(this).attr("name") == name)
				res = true;
		});
		return res;
	}
	
	function callMethod(name)
	{
		if (window[name] == undefined)
			return;
		var a = [];
		for(var i = 1; i < arguments.length; i++)
			a.push(arguments[i]);	
		window[name].apply(this, a);
	}
	
	function delegate(t, f)
	{
		return function()
		{
			f.apply(t, arguments);
		}
	}
}

Form.init = function()
{
	var forms = {};
	$(".form").each(function(){
		var id = $(this).attr("id");
		forms[id] = new Form(id);
	});
	return forms;
}

var Annotation = {};

Annotation.getFormAnnotations = function(f)
{
	var str = $("#" + f + "> .annotation").html();
	return Annotation.parseAnnotations(str);
}

Annotation.parseAnnotations = function(str){
	var annotations = str.split(',');
	var res = {};
	for(var i = 0; i < annotations.length; i++)
	{
		var s = $.trim(annotations[i]);
		var a = s.split('=');
		res[a[0]] = a[1];
	}
	return res;
}

Annotation.getAnnotations = function(formItem){
	var annotations = $("*[@class=annotation]", formItem);
	if(annotations.length == 0)
		return {};
	var str = annotations.html();
	return Annotation.parseAnnotations(str);
}