var SWITCH_FIELD_ERROR_CLASSES=['field-invalid','field-valid'];
function DOCUMENT_IFORM(params){	
	var 
		FORM_TEMPLATES={
			cid:{
				f_type:'text',
				f_rule:/^c|с?\d+$/,
				f_min:1
				},
			fio:{
				f_type:'text',
				f_min:5
				},
			 freeText:{
				f_type:'text'
				},	
			lastname:{
				f_type:'text'
				},	
			login:{
				f_type:'text',
				f_rule:/^[a-zA-Z0-9._\-]+$/,
				f_min:3
				},
			jur_status:{
				f_type:'select',
				f_values:new Array(1,2)
				},
			mail:{
				f_type:'text',
				f_rule:/^[a-zA-Z0-9._\-]+\@[a-zA-Z0-9._\-]+\.\w{2,4}$/,
				f_min:6
				},
			subject:{
				f_type:'text'
				},
			body:{
				f_type:'textarea',
				f_min:3
				},
			url:{
				f_type:"text",
				f_rule:/^(http:\/\/|https:\/\/)?[a-zA-Z0-9._\-]+\.\w{2,4}(\/[a-zA-Z0-9._]*)*$/
				},
			phone:{
				f_type:'text',
				f_rule:/^[\d\-\+\s]+$/,
				f_min:6
				},
			pwd:{
				f_type:'password',
				f_rule:/[a-zA-Z0-9._\-]+/,
				f_min:6
				},
			pwd2:{
				f_type:'password',
				f_rule:/[a-zA-Z0-9._\-]+/,
				f_min:6
				},
			file_img:{
				f_type:'file'
				},
			itemName:{
				f_type:'text',
				f_rule:/^[a-zA-Z0-9._\-]+$/,
				f_min:1
				},
			itemCaption:{
				f_type:'text',
				f_rule:/^[a-zA-Zа-яА-ЯёЁ0-9._\-\s\(\)\—\"«»]+$/,
				f_min:1
				},/* " */
			itemCaptionFree:{
				f_type:'text',
				f_rule:/^[a-zA-Zа-яА-ЯёЁ0-9._\-\s\(\)\—\?\,]+$/
				},
			itemDescription:{
				f_type:'textarea'
				},
				itemDescriptionLatex:{
					f_type:'textarea',
					f_eval:function(){
						new LatexTextarea(this);
						}
					},
			itemDsc:{
				f_type:'textarea',
				f_min:3
				},
			itemEventCount:{
				f_rule:/\d/,
				f_max:3
				},
			tags:{
				f_type:'text',
				f_rule:/^[a-zA-Zа-яА-ЯёЁ0-9._\-\s\(\)\—\.\,]+$/,
				f_min:1,
				f_suggest:{
					control:'SuggestTags'
					}
				},
			tag:{
				f_type:'text',
				f_rule:/^[a-zA-Zа-яА-ЯёЁ0-9._\-\s\(\)\—\.]+$/,
				f_min:1
				},
			
			radioGroup:{
				f_type:'radio'
				},
			itemSelect:{
				f_type:'select'
				},
			count:{
				f_rule:/^\d+$/
				},
			message:{
				f_type:'textarea',
				f_rule:/[^\s]+/,
				f_min:1
				},
				messageLatex:{
					f_type:'textarea',
					f_rule:/[^\s]+/,
					f_min:1,
					f_eval:function(){
						new LatexTextarea(this);
						}						
					},
			date:{
				f_type:'text',
				f_rule:/^\d{1,2}\.\d{1,2}\.\d{2,4}$/,
				f_min:6
				},
			date_calendar:{
				f_type:'text',
				f_rule:/^\d{1,2}\.\d{1,2}\.\d{2,4}$/,
				f_onfocus:function(data){
					killAllCalendars();
					if (!data.field){
						return;
						}
					var calend=new Calendar(data.field,new Date(),getOffsetDaysFromDate(365),null,data.f_onselect);
					}
				},
			time:{
				f_type:'text',
				f_rule:/^(?:\d{1,2}[\.\:]\d{1,2})?$/,
				f_max:5
				},
			hour:{
				f_type:'text',
				f_rule:/^\d{1,2}$/,
				f_minvalue:0,
				f_maxvalue:23,
				f_min:1
				},
			hourEx:{
				f_type:'text',
				f_rule:/^\d{1,2}$/,
				f_minvalue:0,
				f_maxvalue:99,
				f_min:1
				},
			minute:{
				f_type:'text',
				f_rule:/^\d{1,2}$/,
				f_minvalue:0,
				f_maxvalue:59,
				f_min:1
				},
			city:{
				f_type:'text',
				f_rule:/^[\dа-яА-Я][\d\sа-яА-Я\.\-\/\_\(\)\,]*$/,
				f_min:1
				},			
			street:{
				f_type:'text'
				},
			house:{
				f_type:'text',
				f_rule:/\d*/
				},
			subway:{
				f_type:'text'
				},
			hidden:{
				f_type:'hidden'
				}
		},

		DEFAULT_FIELD_PARAMS={
			'f_type':'',
			'f_template':'',
			'f_rule':'',
			'f_min':'',
			'f_minvalue':'',
			'f_maxvalue':'',
			'f_max':'',
			'f_values':'',
			'f_errblock':'',
			'f_addcheck':'',
			'f_onclick':'',
			'f_skip':'',
			'f_onfocus':'',
			'f_eval':'',
			'f_field_equal':''
			},
		defaultParams={			
			'onresult':null
			};

	this.params=concatObjects(params,defaultParams);	
	if (!this.params.form/*  || !this.params.fields */) {
		return false;
		}
	this.form=(typeof this.params.form=='object')?this.params.form:document.forms[this.params.form];
	this.form.obj=this;
	this.fields=this.params.fields;
	this.suggestedFields=[];
	this.result={};
	this.checkRes=false;
	
	var formSubmiting=this.params.onresult;
	
	/* getting fields */
	if (!this.fields){
		this.fields={};
		var fields=$tag(['input','textarea','select'],this.form);
		for (var i=0;i<fields.length;i++){
			var f=fields[i];
			var ft=searchInClass(f,'field_');
			if ((!f.name && !f.id) || ft=='ignore'){
				continue;
				}			
			this.fields[f.name||f.id]=FORM_TEMPLATES[ft]||{f_type:(f.type||f.tagName.toLowerCase())};
			}
		}
	this.putEvents=function(){		
		var fields=this.fields,
			form=this.form,
			$this=this;
		for (fName in fields){
			 var 
			 	f=$id(fName)||form[fName],
				fData=fields[fName],
				fData=this.getFieldData(fData);
			 if (!f) {
				continue;
			 	}
			var onclickFunction=function(){				
				 var errBlock=$id(this.id+'_error')
				 $this.clearError(errBlock,this);
				 
				 /* if (errBlock){
					 errBlock.className='hidden'
					 errBlock.innerHTML=''
				 	} */
				
			 	} 
			 /* EVENTS.append(f,['onclick','onkeypress'],onclickFunction); */
			 $(f).click(onclickFunction).keypress(onclickFunction);
			 if (fData.f_onclick && typeof fData.f_onclick=='function'){
				 if (fData.f_type=='radio'){
					var els=$name(f.name) || f;
				 	for (var i=0; i<els.length; i++){
						var el=els[i];						
						EVENTS.append(el,'onclick',fData.f_onclick);
						}
				 	}
				else{
					EVENTS.append(f,['onclick','onkeypress'],fData.f_onclick);
					}
			 	}
			
				
			if (fData.f_onfocus && is_function(fData.f_onfocus)) {
				if (fData.f_type=='radio'){
					var els=$name(f.name) || f;
				 	for (var i=0; i<els.length; i++){
						var el=els[i];						
						EVENTS.append(el,'onfocus',fData.f_onfocus);
						}
				 	}
				else{
					var 
						func=fData.f_onfocus,
						funcData=fData;
					EVENTS.append(
						f,
						'onfocus',
						function(){
							funcData.field=this;
							func(funcData)
							}
						);
					}
			 	}
			if (fData.f_eval && is_function(fData.f_eval)) {
				fData.f_eval.call(f);
				}
			 if (fData.f_suggest && fData.f_suggest.control){
				 var controlName=fData.f_suggest.control;
				 if (is_function(window[controlName])){
					var sg=new window[controlName](f,fData.f_suggest);
					}
				/*  if (fData.f_rule && !sgData['f_rule']){
					 sgData['f_rule']=fData.f_rule;
				 	} */				 
				 this.suggestedFields.push(f);			 
			 	}
				
			}
		}
	
	
	this.checking=function(){
		var fields=this.fields,
			form=this.form,
			res=true,
			toFocus='';
		
		for (fName in fields){
			var 
				fParam=this.getFieldData(fields[fName]),
				f=$id(fName)||form[fName];				
			if (!f || f.disabled || !$displaying(f)){				
				continue;
				}
			if (fParam.f_type=='radio'){
				continue;
				}
			var 
				fVal=f.value,
				errBlock=$id(fParam.errblock)||$id(fName+'_error');
			f.obj=this;
			fParam.err=this.checkField(fVal,fParam);
			try{
				if(!fParam.err && fParam.f_field_equal && fields[fParam.f_field_equal] && fVal!=$value(fParam.f_field_equal)){
					fParam.err='значение не совпадает';
					}
				}
			catch (e){}			
			
			if (fParam.err){				
				res=false;
				if (this.writeError(errBlock,fParam.err,f)){
					
					}	
					//f.onkeypress=this.clearErrorByClick;
									
				if (!toFocus){
					toFocus=f;
					}
				}
			else{
				this.clearError(errBlock);
				}
			if (res && is_function(params.addcheck) ){
				res=(params.addcheck)(this.getResult());
				}
			}
		if (toFocus) toFocus.focus();		
		return res;
		}
	
	this.preSubmiting=function(){		
		var submitRes,
			obj=this.obj;
		obj.checkRes=obj.checking();		
		if (obj.checkRes){
			obj.doSubmit();
			}		
		return false;
		}
	this.doSubmit=function(){		
		if (this.checkRes){
			this.submiting.call(this.form)
			}
		else{
			return false;
			}
		}
	
	this.submiting=function(){		
		var submitRes,
			form=this.obj,
			formHTML=form.form,
			formFields=form.fields,
			res=false
			
		if (this.obj.params.formChecking){
			this.checkRes=$function(this.params.formChecking);
			}
		else{
			this.checkRes=form.checking();
			}
		
		
		if (!this.checkRes){
			return false;
			}
		form.getResult();
		var sFunc=form.userSubmit?form.userSubmit:formSubmiting;		
		submitRes=(typeof sFunc=='function')?sFunc.call(this.obj,form.result,this.obj):$function(sFunc,form.result);
		return false;
		}
	
	this.checkField=function(val,params){

		var fullData=this.getFieldData(params),
			res='';
		if (
				(fullData.f_max && val.length>fullData.f_max) ||
				(fullData.f_min && val.length<fullData.f_min) ||
				(fullData.f_minvalue && !isNaN(val) && val<fullData.f_minvalue ) ||
				(fullData.f_maxvalue && !isNaN(val) && val>fullData.f_maxvalue ) ||
				(fullData.f_rule && val.length>0 && val.search(fullData.f_rule)<0) ||
				(fullData.f_values && !in_array(fullData.f_values,val))
				
			){			
			res=fullData.f_error_msg||'некорректное значение';
			}
		
		if (fullData.f_addcheck && res==''){
			res=$function(fullData.f_addcheck,val);
			}
		return res;
		}
	
	this.getFieldData=function(params){		
		if (typeof params!='object'){
			params={
				'f_template':params
				};
			}
		
		var fieldData=params;	
		if (fieldData.f_template && FORM_TEMPLATES[fieldData.f_template]){
			fieldData=concatObjects(fieldData,FORM_TEMPLATES[fieldData.f_template]);
			}	
		fieldData=concatObjects(fieldData,DEFAULT_FIELD_PARAMS);
		return fieldData;
		}
	
	this.writeError=function(errBlock, errText,f){
		errBlock=$id(errBlock);
		if (errBlock) {
			errBlock.className='error showed';
			errBlock.innerHTML=errText;	
			}
		if (f=$id(f)){
			toggleClass(f,0,SWITCH_FIELD_ERROR_CLASSES);
			}		
		return true;
		}
	
	this.clearError=function (errBlock,f){
		if (errBlock=$id(errBlock)) {
			errBlock.innerHTML='';
			errBlock.className='error hidden';			
			}
		if (f=$id(f)){
			toggleClass(f,1,SWITCH_FIELD_ERROR_CLASSES);
			}		
		}
	
	this.clearErrorByClick=function (e){
		var form=this.obj,
		errBlock=$id(form.fields[this.id].errblock)||$id(this.id+'_error')
		form.clearError(errBlock,$id(this.id))
		if (this.onclick) this.onclick=null
		if (this.onkeypressed) this.onkeypressed=null
		}
	
	this.getResult=function(){
		var fields=this.fields;		
		for (fName in fields){
			var 
				fParam=fields[fName],
				fieldData=this.getFieldData(fParam),
				f=$id(fName)||this.form[fName];
			if (
					!f || 
					f.disabled || 
					( 
					 	!$displaying(f) && !in_array(['radio','hidden'],fieldData.f_type)
						) 
					){
				
				continue;
				}
				
			
			if (fieldData.f_type=='radio'){				
				this.result[fName]=getRadioValue(fName);
				}
			else if (fieldData.f_type=='textarea') {
				this.result[fName]=f.value;
				}
			else if (fieldData.f_type=='select' && f.multiple){
				this.result[fName]=(getSelectedOptions(f)).join(',');
				}
			else {
				this.result[fName]=fieldData['f_resSource']?f[fieldData['f_resSource']]:f.value;
				}				
			if (this.result[fName]==undefined || this.result[fName]==null){
				this.result[fName]=='';
				}
			}
		if (params.fieldsPrefix){
			var res={};
			for (fName in this.result){
				res[fName.replace(params.fieldsPrefix,'')]=this.result[fName];
				}
			this.result=res;
			}
		
		return this.result;
		}	
	
	this.isSuggesting=function(){
		var res=0
		for (var i=0;i<this.suggestedFields.length; i++){
			var f=this.suggestedFields[i];
			/* if (f.suggestData.block){
				res++;
				}	 */			
			}
		return res
		}		
	
	this.putEvents();
	
	this.form.onsubmit=this.form.submit=this.preSubmiting;
	return false;	
}


/* Base suggest
================================================*/
function Suggest(){
	
	}
	
	Suggest.prototype={
		}
		
/* Tags suggest
================================================*/
var SuggestTags=(
	function(field,data){
		this.initialize(field,data);
		}
	).inheritsFrom(Suggest);

	$.extend(
		SuggestTags.prototype,
		{
			initialize:function(field,data){				
				var $this=this;
				this.field=$(field);
				if (!this.field.length){
					return false;
					}
				this.data=$.extend(
					{
						valueSprt:','
						},
					data
					);
				var spfunc=function(e){
					e.stopPropagation();
					}
				this.area=$('#suggest-area').empty();
				if (!this.area.length){
					this.area=$('<div class="suggest" id="suggest-area"></div>').appendTo(document.body);
					}
				this.area					
					.hide()
					.css(
						{
							position:'absolute',
							zIndex:10010
							}
						)
					.click(spfunc);
				this.areaList=$('<ul></ul>').appendTo(this.area);
				
				this.valueParts=this.field.val().split(this.data.valueSprt);
				this._prevValue=this.field.val();
				this.changingValuePart=this.valueParts[this.valueParts.length-1];
				this.changingValuePartNum=this.valueParts.length-1;
				
				//this.source=this.prepareSource(ALL_TAGS);
				this.source=ALL_TAGS;
				this.actualize();
				var skipSuggest=false;
				var afunc=function(e){
					$this.fixStep();
					if (!skipSuggest){						
						$this.show();
						$this.actualize();
						}
					else{
						skipSuggest=false;
						}	
					}								
				
				
				this.field
					.keyup(afunc)
					//.focus(afunc)
					.click(spfunc)
					.keydown(
						function(e){
							switch(getKeyPressed(e)){
								case 'ARROWDOWN':
									$this.setNextActiveItem();
									break;
								case 'ARROWUP':
									$this.setPrevActiveItem();
									break;
								case 'ENTER':
									skipSuggest=true;
									if ($this.isShowing()){
										$this.selectItem();										
										return false;
										}
									else{
										return true;
										}
									break;
								case 'ESC':
									$this.hide();
									skipSuggest=true;
									return false;
								default:
									break;
								}
							}
						)
				$(document.body)
					.click(
						function(){
							$this.hide();
							}
						);
				this.items=[];
				this.activeItemNum=0;
				},
			actualize:function(){
				this.actualize_position();
				this.actualize_suggest();
				},			
			actualize_position:function(){
				var wh=$where(this.field);
				this.area.css(
					{
						left:wh.left,
						top:wh.bottom,
						width:$(this.field).width()
						}
					);
				},
			actualize_suggest:function(){
				var $this=this;
				var items=this.items=this.getApropriateItems();
				this.areaList.empty();
				$.each(
					items,
					function(num){
						var item=this;
						var li=$('<li'+(this.weight>=TAG_WEIGHT_MAIN?' class="accent"':'')+'></li>').appendTo($this.areaList);
						var a=$('<a href="javascript:;"'+(num==$this.activeItemNum?' class="active"':'')+'>'+this.caption+'</a>')
							.appendTo(li)
							.click(
								function(){
									$this.selectItem(num);
									}
								)
							.hover(
								function(){
									//$(this).addClass('active');
									$this.setActiveItem(num);
									},
								function(){
									//$(this).removeClass('active');
									}
								);
						}
					);
				
				},
			setActiveItem:function(num){
				if (num>(this.items.length-1)){
					num=num%this.items.length;
					}
				else if (num<0){
					num=this.items.length+num;
					}
				this.activeItemNum=num;
				$('li a',this.areaList).removeClass('active').eq(this.activeItemNum).addClass('active');
				},
			setNextActiveItem:function(){
				this.setActiveItem(this.activeItemNum+1);
				},
			setPrevActiveItem:function(){
				this.setActiveItem(this.activeItemNum-1);
				},
			selectItem:function(itemNum){
				if (!isset(itemNum)){
					itemNum=this.activeItemNum;
					}
				var item=this.items[itemNum];
				if (item && item.caption){
					//this.field.val(item.caption);
					var parts=this.field.val().split(this.data.valueSprt);
					parts[this.changingValuePartNum]=item.caption;
					this.field.val(parts.join(this.data.valueSprt));
					this.fixStep();
					this.hide();
					}				
				},
			getApropriateItems:function(){
				var val=this.changingValuePart.trim();
				var items=[];
				var denied='';
				$.each(
					this.source,
					function(num){
						if (this.caption.toLowerCase().search(val.toLowerCase())==0){
							items.push(this);
							}
						}
					);
				return items;
				},
			prepareSource:function(source){
				var res={};
				if (is_array(source)){
					$.each(
						source,
						function(){
							var item=this;
							res[this.caption]=this;
							}
						);
					}
				return res;
				},
			show:function(){
				this.area.show();
				},
			hide:function(){
				this.area.hide();
				},
			isShowing:function(){
				return $displaying(this.areaList[0]);
				},
			fixStep:function(){
				//this.valueParts_prev=this.valueParts.concat();
				var val=this.field.val();
				var parts=val.split(this.data.valueSprt);
				if (val.length!=this._prevValue.length){
					var prevParts=this._prevValue.split(this.data.valueSprt);
					this.changingValuePart='';
					this.changingValuePartNum=0;
					for (var i=0;i<parts.length;i++){
						var part=parts[i];
						if (part && part!=prevParts[i]){
							this.changingValuePart=part;
							this.changingValuePartNum=i;
							break;
							}
						}
					this._prevValue=String(val);
					}			
				}
			}
		);