/**
 * @author		Konstantin Vyalukhin <kst404@gmail.com>
 */
	function gcFormValidator(){
	}

	gcFormValidator.prototype = {
		validateElements: [],
		requiredPasswordPairs: [],
		
		dataTypes: {
			string: {
				required: function( value ) {
					return /\w+/.test( value );
				},
				format: function( value ) {
					return /\w+/.test( value );
				}	
			},
			email: {
				required: function( value ) {
					return /^\S+$/.test( value );
				},
				format: function( value ) {
					return /^[^@]+@([\d\w-]+\.){1,}[\d\w-]+$/.test( value );
				}
			},
			date: {
				required: function( value ) {
					return /^\S+$/.test( value );
				},
				format: function( value ) {
// MM/DD/YYYY
					return /^(0[1-9]|1[0-2])\/([0-2][0-9]|3[01])\/\d{4}$/.test( value );
				}
			}
			
		},
		
		validate: function() {
			var isPassed = true;
			var isElementPassed = false;
			
			for( var i in this.validateElements ) {
				switch( this.validateElements[i].element.type ) {
					case 'textarea':
					case 'text':
					case 'password':
						isElementPassed = this.dataTypes[ this.validateElements[i].type ][ this.validateElements[i].required ? 'required' : 'format' ]( this.validateElements[i].element.value );
						break;

					case 'checkbox':
						isElementPassed = this.checkRequiredCheckbox(this.validateElements[i].element);
						break;

					case 'radio':
						isElementPassed = this.checkRequiredRadio(this.validateElements[i].element.form[this.validateElements[i].element.name]);
						break;
				}
				if( !isElementPassed ) {
					if( isPassed )
						isPassed = false;
					this.errorDecorate( this.validateElements[i].element, this.validateElements[i].message );
				}
				else {
					this.errorUndecorate( this.validateElements[i].element );
				}
			}
			
			for( var i in this.requiredPasswordPairs ) {
				isElementPassed = this.dataTypes[ 'string' ][ 'required' ](this.requiredPasswordPairs[i][0]) && this.dataTypes[ 'string' ][ 'required' ](this.requiredPasswordPairs[i][1]) && this.requiredPasswordPairs[i][0].value == this.requiredPasswordPairs[i][1].value;

				if( !isElementPassed ) {
					if( isPassed )
						isPassed = false;
					this.errorDecorate( this.requiredPasswordPairs[i][1], this.requiredPasswordPairs[i][2] );
				}
				else {
					this.errorUndecorate( this.requiredPasswordPairs[i][1] );
				}
			}
			
			return isPassed;
		},
		
		addRequired: function( element, type, message ) {
			if( !message ) {
				message = type;
				type = 'string';
			}
			this.validateElements.push({element: element, type: type ? type : 'string', required: true, message: message });
		},
		
		addFormatCheck: function( element, type, message ) {
			this.validateElements.push({element: element, type: type, required: false, message: message });
		},
		
		addRequiredPasswordPair: function( element1, element2, message ) {
			this.requiredPasswordPairs.push([element1, element2, message]);
		},
		
		errorDecorateFunctions: function( fd, fu ) {
			this.errorDecorate = fd;
			this.errorUndecorate = fu;
		},

		errorDecorate: function( element ) {
			$( element ).addClass( 'form-required-error' );
		},

		errorUndecorate: function( element ) {
			$( element ).removeClass( 'form-required-error' );
		},
		
		checkRequiredRadio: function( element ) {
			var isSelected = false;
			
			for( var i=0; i<element.length; i++) {
				isSelected |= element[i].checked;
			}
			
			return isSelected;
		},
		
		checkRequiredCheckbox: function( element ) {
			return element.checked;
		}
	}
	
	function gcFormHelper(){
		
	}
	
	gcFormHelper.prototype = {
		cloneItem: function( itemId ){
			
		}
	}

	function gcAsyncFileUpload( field, url, userCallbackFunction ) {
		this.fieldFileUploadId = field;
		this.urlForSubmit = url;
		this.userCallbackFunction = userCallbackFunction;
	}
	
	gcAsyncFileUpload.prototype = {
		fieldFileUploadId: {},
		urlForSubmit: '',
		asyncIframe: {},
		tempForm: {},
		userCallbackFunction: null,
		
		run: function( asyncId ) {

			this.createTempForm( asyncId );
			this.createTempFileUploadField( asyncId );
			this.addLoader( asyncId );
			this.tempForm[asyncId].submit();
		},
		
		createAsyncId: function() {
			return Math.ceil(1000*Math.random());
		},
		
		createIframe: function( asyncId ) {
			this.asyncIframe[asyncId] = $('<iframe src="javascript:false" id="asyncIframe'+asyncId+'" name="asyncIframe'+asyncId+'" style="top: -1000px; left: -1000px; width: 1px; height: 1px; position: absolute;"></iframe>').appendTo(document.body).get(0);
		},
		
		createTempForm: function( asyncId ) {
			this.tempForm[asyncId] = $('<form method="post" enctype="multipart/form-data" action="'+this.urlForSubmit+'" target="asyncIframe'+asyncId+'"></form>').get(0);
			$('<div style="top: -1000px; left: -1000px; width: 1px; height: 1px; position: absolute;"></div>').append(this.tempForm[asyncId]).appendTo(document.body);
		},
		
		createTempFileUploadField: function( asyncId ) {
			newId = this.fieldFileUploadId+'async'+asyncId;
			newField = $('#'+this.fieldFileUploadId).attr('id', newId).clone().attr('id', this.fieldFileUploadId);
			$('#'+newId).after( newField );
			$('#'+newId).appendTo( this.tempForm[asyncId] );
		},
		
		listenEvent: function( objectId, eventName ) {
			var t = this;
			$('#'+objectId).bind( eventName, function( event ){
				var asyncId = t.createAsyncId();
				t.createIframe( asyncId );
				t.setCallbackFunction( asyncId );
				setTimeout( function( ) { t.run( asyncId ); }, 5 );
				
				return false;
			});
		},
		
		setCallbackFunction: function( asyncId ){
			var t = this;
			$(this.asyncIframe[asyncId]).load(function(event){
				
				if( 'javascript:false' != (this.contentWindow ? this.contentWindow : this.contentDocument).location.href ) {
					var jsonData = $( this.contentWindow ? this.contentWindow.document.body : this.contentDocument.document.body ).text();
					var data = eval("(" + jsonData + ")");
					
					t.removeLoader( asyncId );
					t.userCallbackFunction( asyncId, data );
				}
			});
		},
		
		addLoader: function( asyncId ){
			$('#'+this.fieldFileUploadId).after('<img src="/i/loadspinner.gif" id="asyncLoaderImage'+asyncId+'" style="padding-lrgt: 5px;"/>');
		},
		
		removeLoader: function( asyncId ){
			$('#asyncLoaderImage'+asyncId).remove();
		},
		
		replaceInputWithHidden: function( value ) {
			$('#'+this.fieldFileUploadId).after('<input type="hidden" name="'+$('#'+this.fieldFileUploadId).attr('name')+'" value="'+value+'">').remove();
		},
		
		addHidden: function( value ) {
			$('#'+this.fieldFileUploadId).after('<input type="hidden" name="'+$('#'+this.fieldFileUploadId).attr('name')+'" value="'+value+'">');
		},
		
		destroyFormAndIFrame: function( asyncId ) {
			$(this.tempForm[asyncId]).parent().remove();
			$(this.asyncIframe[asyncId]).remove();
		}
	}
	
	function gcFormSuggest( url, main, suggestions ) {
		this.bindTo( main, suggestions );
		this.suggestionsUrl( url );
		this.createSuggestionsBox();
	}
	
	gcFormSuggest.prototype = {
		mainField: {},
		suggestionsField: {},
		suggestionsUrl: '',
		suggestionsData: {},
		suggestionsBox: null,
		suggestionsItemRenderer: {},
		suggestionsItemClick: {},
		suggestionsAddForm: {},
		suggestionsAddFormCallback: {},
		
		bindTo: function( main, suggestions ) {
			this.mainField = main;
			this.suggestionsField = suggestions;

			var t = this;
			$(this.suggestionsField).keyup( function( e ){
				e.preventDefault();
				t.getSuggestions();
			});

			$("body").click(function(){
				t.hideSuggestions();
			});
		},
		
		getSuggestions: function() {
			var t =this;
			$.getJSON(this.suggestionsUrl,
				{
					q: $(this.suggestionsField).val()
				},
				function( data ) {
					t.showSuggestions( data );
				}
			);
		},
		
		suggestionsUrl: function( url ) {
			this.suggestionsUrl = url;
		},
		
		setSuggestionsItemRenderer: function( renderer ) {
			this.suggestionsItemRenderer = renderer;
		},
		
		setDefaultSuggestionsItemRenderer: function( label ) {
			this.itemLabel = label;
			this.suggestionsItemRenderer = function( item ){
				return item[ label ];
			};
		},
		
		setSuggestionsItemClick: function( itemClick ) {
			this.suggestionsItemClick = itemClick;
		},
		
		setDefaultSuggestionsItemClick: function( value ) {
			this.suggestionsItemClick = function( item ){
				return {value: item[ value ], text: item[ this.itemLabel ]};
			};
		},
		
		setDefaultSuggestionsItemClickMultiple: function( value ) {
			$('a.temp-form-multiple-values-item-delete-control').click(function( event ){
				event.preventDefault();
				event.stopPropagation();
				
				$(this).parents('li.temp-form-multiple-values-item-box').remove();
			})

			this.suggestionsItemClick = function( item ){
				if (!this.suggestionsMultipleValuesBox) {
					this.suggestionsMultipleValuesBox = $('<ul class="clearfix"/>');
					$(this.mainField).parents('div.temp-form-field').children('*:first-child').before(this.suggestionsMultipleValuesBox);
				}
				$('<li class="temp-form-multiple-values-item-box"><input type="hidden" name="'+this.mainField.name+'" value="'+item[ value ]+'">'+item[ this.itemLabel ]+'&nbsp;<sup><a href="" title="delete" class="temp-form-multiple-values-item-delete-control">x</a></sup></li>')
					.find('a.temp-form-multiple-values-item-delete-control').click(function( event ){
						event.preventDefault();
						event.stopPropagation();
						
						$(this).parents('li.temp-form-multiple-values-item-box').remove();
					})
					.end()
					.appendTo( this.suggestionsMultipleValuesBox );
				return { value: '', text: '' };
			};
		},
		
		setSuggestionsItemAddForm: function( itemAddForm ) {
			this.suggestionsAddForm = $( itemAddForm ).get(0);
			$(this.suggestionsBox).append( this.suggestionsAddForm );
			
			this.addFormInit();
		},
		
		showSuggestions: function( data ) {
			this.setSuggestionsBoxBounds();

			this.suggestionsData = data;
			var t = this;

			$(this.suggestionsBox).children('ul').empty();

			var suggestionsUl = $(this.suggestionsBox).children('ul');
			for(var i in this.suggestionsData) {
				if( this.suggestionsItemRenderer(this.suggestionsData[i]) )
					$('<li>'+this.suggestionsItemRenderer(this.suggestionsData[i])+'</li>').attr('suggestindex', i).appendTo(suggestionsUl);
			}
			$(suggestionsUl).children('li').click(function( e ){
				e.preventDefault();
				t.fillFields($(this).attr('suggestindex'));
				t.hideSuggestions();
			});
			$(this.suggestionsBox).show();
		},
		
		hideSuggestions: function( data ) {
			$(this.suggestionsBox).hide();
		},
		
		createSuggestionsBox: function() {
			var formParent = this.suggestionsField.form.parentNode
			
			this.suggestionsBox = $("<div style='display: none; position: absolute;' class='form-suggestions-box'><ul></ul></div>").appendTo(formParent).get(0);

			$(this.suggestionsBox).click(function( e ){
				e.stopPropagation();
			});
		},
		
		setSuggestionsBoxBounds: function() {
			var leftcorner = $(this.suggestionsField).offset({ scroll: false });
			$(this.suggestionsBox).css({
				'top': leftcorner.top + $(this.suggestionsField).height() + 5 + 'px',
				'left': leftcorner.left + 'px'
			});
			$(this.suggestionsBox).width($(this.suggestionsField).width());
		},
		
		fillFields: function( index ) {
			var itemClickData = this.suggestionsItemClick(this.suggestionsData[index]);
			if( typeof itemClickData.value == 'undefined' && typeof itemClickData.text == 'undefined' ){
				this.mainField.value = itemClickData;
				this.suggestionsField.value = this.suggestionsItemRenderer(this.suggestionsData[index]); 
			}
			else {
				this.mainField.value = itemClickData.value;
				this.suggestionsField.value = itemClickData.text; 
			}
		},
	
		addFormInit: function(){
			var t = this;
			$( this.suggestionsAddForm ).addClass( 'temp-form-suggestions-add-form' );
			this.setDefaultAddFormCallback();
			var jsonSubmit = new gcFormJsonSubmit( this.suggestionsAddForm, function( data ){ t.suggestionsAddFormCallback( data ); } );
		},
		
		setAddFormCallback: function( callback ) {
			this.suggestionsAddFormCallback = callback;
		},
		
		setDefaultAddFormCallback: function( ) {
			this.suggestionsAddFormCallback = function( data ){
				this.suggestionsData = data;
				this.fillFields(0);
				this.hideSuggestions();
			};
		}
	}
	
	function gcFormJsonSubmit( form, callback ) {
		this.form = $(form);
		this.callback = callback;
		this.bindEvents();
	}
	
	gcFormJsonSubmit.prototype = {
		form: {},
		callback: {},
		
		submit: function(){
			var sendData = {};
			var t = this;
			for(var i in this.form.get(0).elements) {
				// FIXME: editorial/add --> new tag
				sendData[this.form.get(0).elements[i].name]= this.elementValue(this.form.get(0).elements[i]);
			}

			$.post(this.form.attr('action'), sendData, function(data){
				t.callback(data);
			}, "json" )
		},
		
		elementValue: function( element ) {
			return element.value;
		},
		
		bindEvents: function(){
			var t = this;
			this.form.submit(function(e){
				e.preventDefault();
				e.stopPropagation();
				
				t.submit();
				
				return false;
			});
		}
	}
