function FormController() {

	var FC = {
        
		// модель
		Model: {
			data: {}, 		// данные формы
			metadata: {}, 	// их описания и параметры
			
			FillData : function () {
				for (var key in this.metadata) {
					var data = FC.Utils.ReturnObjVal(key);
			   		if (data !== 'undefined') {
			   			this.data[key] = data;
					}
				}
			},
			
			ValidateData : function () {	// проверяем обязательные поля на заполненность
				var invalidFields=[];
				for (var dataKey in FC.Model.metadata) {
					if (FC.Model.metadata[dataKey].required=='true'  
						 && (
								FC.Model.metadata[dataKey].type=='checkbox' 
									&& FC.Model.data[dataKey]==false
								|| (
									FC.Model.metadata[dataKey].type=='text' 
									|| FC.Model.metadata[dataKey].type=='textarea'
									) 
									&& 
									(
									FC.Model.data[dataKey]==="" 
									|| FC.Model.data[dataKey]===undefined
									|| FC.Model.data[dataKey]===FC.Model.metadata[dataKey].title + ":"
									)
							)
						)
						invalidFields[invalidFields.length] = 
						{
							field: dataKey, 
							message: 'поле "' + FC.Model.metadata[dataKey].title + '" обязательно для заполнения.'
						};
				}
				return invalidFields;
			},
		},

		// вид
		View: {

			msgContainerName : '', // тут будет выводиться сообщение
			
			ShowMessage : function (messageData, type){
				var msgContainer = FC.Utils.ReturnObjById(FC.View.msgContainerName);
				type = FC.Utils.SetArgDefVal(type,'note');
				msgContainer.className = type;
				var message='';
				if (type=='error') 
					var message='<b>Внимание</b>';
				if (FC.Utils.IsArray(messageData)) {
					message += '<ul class="error">';
					for (var i=0; i<messageData.length; i++)
						message += '<li>'+messageData[i].message+'</li>';
					
					message+='</ul>';
				} else
					message+=messageData;
				
				msgContainer.innerHTML = message;
			},
			
		},

		
		// контроллер	
		Controller: { 
			
			AddEvent: function (obj, name, func){
				if (obj.addEventListener) {
					obj.addEventListener(name, func, false);
				} else if(obj.attachEvent) {
					obj.attachEvent('on'+name, func);
				} else {
					throw 'Error';
				}
			},

			RemoveEvent: function (obj, name, fn){
			    if (obj.removeEventListener){
		    		obj.removeEventListener(name, fn, false );
			    } else {
			        obj.detachEvent( "on"+name, obj[name+fn] );
			        obj[name+fn] = null;
			        obj["e"+name+fn] = null;
			    }
			},
			
			ValidateForm : function (event) {
				FC.Model.FillData();
				var filled = false;
				var invalidFields = FC.Model.ValidateData();
				if (invalidFields.length) { 
					event.preventDefault();
					var message = new FC.View.ShowMessage(invalidFields, 'error');
				} else {
					var sendMailArgs = {};
					for (var dataKey in  FC.Model.data) {
					    dataVal = FC.Utils.ReturnObjVal(dataKey);
					    dataVal = FC.Utils.SetArgDefVal(dataVal, '');
					    dataVal = FC.Utils.NormalizeControlOutput(dataVal);
				  		
				  		if (FC.Model.metadata[dataKey] != undefined) {
							keyName = FC.Model.metadata[dataKey]['title'];
				  			keyName = FC.Utils.NotmalizeStr(keyName);
				  			keyName = FC.Utils.Trim(keyName);
				  		} else
				  			keyName = dataKey;
				  		
						if (keyName + ':'==dataVal)
				    		dataVal='';				  	
				  		if (dataVal!=='')
				  			filled = true;
				  		
				  		sendMailArgs[dataKey] = [keyName, dataVal];
					}
					if (filled)
						FC.Controller.SendMail(sendMailArgs);
					else
						FC.View.ShowMessage('форма не заполнена', 'error');
					
					return;
				}
			},
			
			SendMail: function (argsArr) {
				
				FC.View.ShowMessage(FC.Settings.proc_msg, 'note');
				var xmlHttp = new FC.Controller.XmlHttp();
				xmlHttp.open(FC.Settings.method, FC.Settings.ajaxPagePath, true);
				xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=");
				xmlHttp.onreadystatechange = function() {
					if (xmlHttp.readyState==4 && xmlHttp.status==200)
				  		FC.View.ShowMessage(xmlHttp.responseText,'ok');
				};

				var queryStr = "";
				for (var key in argsArr)
					queryStr += "&" + argsArr[key][0] + "=" + argsArr[key][1];
				queryStr = FC.Utils.CropTale(queryStr, 1, 'start');
				xmlHttp.send(queryStr);
			},
			
			XmlHttp: function () {
				try {xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");}
				catch(e) {
					try {xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");}
					catch(E) {xmlHttp=false;}
				}
				if (!xmlHttp && typeof XMLHttpRequest!='undefined') {
					xmlHttp=new XMLHttpRequest();
				}
				return xmlHttp;
			},
		
		},

		DomHandling: {  /*	Манипуляции с формой	*/ 

			EnableDisableControls: function () {
				var control;
				for (var ctrInd=0; ctrInd<arguments.length; ctrInd++) {
					control = FC.Utils.ReturnObjById(arguments[ctrInd]); 	
					if (control.disabled) {
						control.disabled = false;
						control.className = '';
					} else {
						control.disabled = true;
						control.className = 'disabled';
					}
				}
			},
			
			SetControlsByNumber: function (options) {
				// удаление всех, кроме первых старых контролов с именами, 
				// начинающимися с префиксов имен из массива аргументов
				for (var idInd=0; idInd<options.length; idInd++) {
					// выбираем первый эл-т из набора контролов
					var control = FC.Utils.ReturnObjById(this.GetFirstName(options[idInd])); 
					var controlRow = control.parentNode.parentNode;  // выделяем его контейнер
					var namePrefix = options[idInd]['namePrefix']; 	// префикс имени 
					// пока его следующие соседи содержат контролы, с таким-же префиксом имени, удаляем
					var nextControl = controlRow.nextSibling.childNodes[1];
					if (nextControl != undefined)
						while (nextControl.childNodes[0].getAttribute('name').indexOf(namePrefix)!=-1) {
							controlRow.parentNode.removeChild(controlRow.nextSibling);
							nextControl = controlRow.nextSibling.childNodes[1];
							if (nextControl == undefined)
								break; 
						}
				}
				
				// рисуем новые контролы
				for (var optInd in options)  // перебираем эл-ты массива
					this.CloneControls(options[optInd]);
			},

			CloneControls: function (options) {
				// выбираем первый эл-т из набора контролов — прототип клонов
				var control = FC.Utils.ReturnObjById(this.GetFirstName(options)); 
				
				var i=2;
				var controllerSel = FC.Utils.ReturnObjById(options['selId']); // выбираем управляющий контрол
				var selectsTotal = controllerSel.options.selectedIndex + 1; // определяем его значение
				while (i <= selectsTotal) {
					
					////////////////////////// строка функции onChange  //////////////////////////
					var onChangeString = options['onChange']==undefined ? '' : options['onChange'];
					// строка аргументов для функции onChange создаваемого контрола 
					var onChgArgsArr = options['onChangeArgs'];
					// строка массивов значений аргументов для функции onChange создаваемого контрола
					var onChgValsArrs = options['onChangeVals'];
					var onChangeArgsString = '';
					if (onChgArgsArr!=undefined && onChgValsArrs!=undefined) {
						onChgArgsArr = onChgArgsArr.split(','); // массив аргументов для функции onChange 
						onChgValsArrs = onChgValsArrs.split(';'); // массив массивов значений аргументов для функции onChange 
						if (onChangeString=='FC.DomHandling.SetControlsByNumber') {
		 					onChangeArgsString = "[";
							for (var onChgValsArrInd in onChgValsArrs) {
								onChangeArgsString += "{";
								onChgValsArr = onChgValsArrs[onChgValsArrInd].split(',');
								for (var onChArgInd in onChgArgsArr) {
									onChangeArgsString += onChgArgsArr[onChArgInd] + ':"' + onChgValsArr[onChArgInd];
									if (onChgArgsArr[onChArgInd]=='namePrefix' || onChgArgsArr[onChArgInd]=='selId') 	
										onChangeArgsString += '[' + i + ']';
									onChangeArgsString += '",';
								}
								onChangeArgsString = FC.Utils.CropTale(onChangeArgsString); // отрезаем запятую
								onChangeArgsString += "},";
							}
							onChangeArgsString = FC.Utils.CropTale(onChangeArgsString); // отрезаем запятую
							onChangeArgsString += ']';
						} else {
							for (var onChArgInd in onChgArgsArr)
								onChangeArgsString += onChgArgsArr[onChArgInd] + ',';
							
							onChangeArgsString = FC.Utils.CropTale(onChangeArgsString); // отрезаем запятую
						}
					}
					onChangeString += onChangeArgsString!='' ? "(" + onChangeArgsString + ")" : '';
					//////////////////////////
					
					// вычисляем номер первого уровня (номер номера гостиницы)
					controlNum = options['namePrefix'];
					numPos = controlNum.indexOf("[") + 1;
					controlNum = controlNum.slice(numPos,numPos+1);
					controlNum = parseInt(controlNum);
					controlNum = isNaN(controlNum) ? i : controlNum;
					control = this.CloneControl(control, i, options['control'], '№' + controlNum + ': ' + options['capt'], options['namePrefix'], options['treeLevel'], onChangeString);
					i++;
				}
			},
	            
			CloneControl: function (elderSel, roomSelCount, eltType, capt, namePrefix, treeLevel, onChangeString) { 
				var tagName = FC.Settings.form_envs[1];
				
				var newSelCapt = document.createElement(tagName);
				newSelCapt.setAttribute('class', 'capt');
				var elderSelCapt = elderSel.parentNode.previousSibling;
				newSelCapt.setAttribute('style', elderSelCapt.getAttribute('style'));
				newSelCapt.innerHTML = capt;
				
				var newSelect = document.createElement(eltType);
				var newSelectName = namePrefix + '[' + roomSelCount + ']';
				if (treeLevel>1) newSelectName += '[1]';
				newSelect.setAttribute('name', newSelectName);
				newSelect.setAttribute('id', newSelectName);
				if (onChangeString!='') newSelect.setAttribute('onChange', onChangeString);
				
				var newSelDiv = document.createElement(tagName);
				newSelDiv.setAttribute('class', 'cont');
				newSelDiv.appendChild(newSelect);
				newSelDiv.setAttribute('style', elderSel.parentNode.getAttribute('style'));
				
				var newSelRow = document.createElement(tagName);
				newSelRow.setAttribute('class', 'row');
				newSelRow.appendChild(newSelCapt);
				newSelRow.appendChild(newSelDiv);
				newSelect.innerHTML = elderSel.innerHTML;
				oldSelRow = elderSel.parentNode.parentNode;
				newSelRow = oldSelRow.parentNode.insertBefore(newSelRow, oldSelRow.nextSibling);
				
				FC.Model.metadata[newSelectName] = {
					required : false,
					title : capt,
					type : elderSel.type
				};
				
				return newSelRow.childNodes[1].childNodes[0];
			},

			GetFirstName: function (options) {
				var name = options['namePrefix'] + '[1]';
				if (options['treeLevel']>1) name += '[1]';
				return 	name;
			},
			
			//////////////////////////////////////////////////////////////////////////
			SetSelect: function(ctrlSelID, selName, addSelects) {
			    var sel = ReturnObjById(ctrlSelID);
			    var tagName = FC.Settings['form_envs'][1];
			    listLen = addSelects.length;
			    sel.innerHTML += "<" + tagName + " class='row'>" +
			    "<span class='label'></span><span class='formw'><select id='" + id + "' name='" + id + "' style='width:9em'>";
//			    alert( addSelects.toSource );
			    for (var i=0; i<listLen;) {
			        sel.innerHTML += addSelects;
			    }
			    sel.innerHTML += "</select></span>" + "</" + tagName + ">";
			    var addSel=returnObjById("sel" + i);
			    var selVal=returnObjById(selName).value;
			    if (listLen = addSelects[selVal][0].length) {
			        var id = addSelects[selVal][0][1];
			        addSel.innerHTML = "<" + tagName + " class='row'><span class='label'></span><span class='formw'><select id='" + id + "' name='" + id + "' style='width:9em'></select></span></" + tagName + ">";
			        addSel.childNodes[0].childNodes[0].innerHTML = addSelects[selVal][0][0] + ":"; 
			        var sel2 = addSel.childNodes[0].childNodes[1].childNodes[0];
			        for (var j=0; j<addSelects[selVal][1].length;) {
			            tmpStr=addSelects[selVal][1][j]; 
			            tmpArr=tmpStr.split(":");
			            if (tmpArr.length==2) { 
			                sel2.options[j++] = new Option(tmpArr[0], tmpArr[1]);
			            } else { 
			                sel2.options[j++] = new Option(tmpStr, tmpStr);
			            }
			        }
			    } else {
			        addSel.innerHTML = "";
			    }
			},

			SetAddSelect: function(selName, addSelects) {
			    var addSel=returnObjById("sel" + i);
			    var selVal=returnObjById(selName).value;
			    if (listLen = addSelects[selVal][0].length) {
			        var id = addSelects[selVal][0][1];
			        addSel.innerHTML = "";
			        addSel.innerHTML = "<div class='row'><span class='label'></span><span class='formw'><select id='" + id + "' name='" + id + "' style='width:9em'></select></span></div>";
			        addSel.childNodes[0].childNodes[0].innerHTML = addSelects[selVal][0][0] + ":";
			        var sel2 = addSel.childNodes[0].childNodes[1].childNodes[0];
			        for (var j=0; j<addSelects[selVal][1].length;) {
			            tmpStr=addSelects[selVal][1][j]; 
			            tmpArr=tmpStr.split(":");
			            if (tmpArr.length==2) { 
			                sel2.options[j++] = new Option(tmpArr[0], tmpArr[1]);
			            } else { 
			                sel2.options[j++] = new Option(tmpStr, tmpStr);
			            }
			        }
			    } else {
			        addSel.innerHTML = "";
			    }
			},
			
			DestroyElement : function(eltID) {
			    var elt=FC.Utils.ReturnObjById(eltID);
			    elt.innerHTML = "";
			},
			
		},

		Utils: {
		    // DOM
			ReturnObjVal:function(str) {
				tmpObj=this.ReturnObj(str);
				tmpObjType = tmpObj.type;
				if (typeof tmpObj==='undefined')
					return "";
				else if (tmpObjType==='checkbox')
					return tmpObj.checked;  
				else if (tmpObj.value) {
					if (tmpObjType==="text" 
						|| tmpObjType==="hidden" 
						|| tmpObjType==="textarea"
						|| tmpObjType==="select-one"
						) 
						return tmpObj.value;
				} else if (tmpObj.innerHTML)
					return tmpObj.innerHTML;
			},
			ReturnObj:function (str) {return this.ReturnObjById(str)||this.ReturnObjByName(str);},
			ReturnObjById:function(id) {return document.getElementById?document.getElementById(id):document.all?document.all[id]:document.layers[id];},
			ReturnObjByName:function(name) {return document.getElementsByName?document.getElementsByName(name)[0]:document.all?document.all[name]:document.layers[name];},
	        
	        // Strings
			CropTale: function(str, cnt, direction) {
				if(typeof cnt=='undefined') cnt=1;
				if(typeof direction=='undefined') direction='end';
				if(direction=='end')
					return str.substr(0,str.length-cnt);
				else
					return str.substr(cnt,str.length);
				
			},
			
			Trim: function(string) {return string.replace(/(^\s+)|(\s+$)/g, "");},
			
			NotmalizeStr: function (str) { 
				str = str.replace(/<.*>/, ""); // вырезаем теги
				str = str.replace("&nbsp;", " "); // заменяем пробел
				return str;
			},
			IsArray: function(obj) {return obj.constructor == Array;},
			SetArgDefVal: function(arg, defVal){return typeof(arg) != 'undefined' ? arg : defVal;},
			
			NormalizeControlOutput: function(ctrl_output){
				if (ctrl_output=='true' || ctrl_output===true) 
					ctrl_output = 'да';
				else if (ctrl_output=='false' || ctrl_output===false) 
					ctrl_output = 'нет';
				
				return ctrl_output;
			},
		},
		
		Settings : {  
			form_envs: [],
			proc_msg: '',
		},
		
	};
	
	return FC;
};
