//{{{ sdfArrayAddElementIfNotExists
/**
 * Добавляет елемент в массив, если этого элемента еще нет в массиве.
 * @since  18.09.2007 11:57:54
 * @author jikk
 * @param array arr массив
 * @param mixed el что-то
 */
function sdfArrayAddElementIfNotExists( arr, el )
{
	if ( typeof el != 'undefined' && el )
	{
		for ( var i in arr )
		{
			if ( arr[i] === el ) {
				return;
			}
		}
		arr.push( el );
	}
}
//===========================================================================}}}
function url_decode(utftext) {
	var string = "";
	var i = 0;
	var c = c1 = c2 = 0;
	utftext = unescape( utftext );

	while ( i < utftext.length ) {

		c = utftext.charCodeAt(i);

		if (c < 128) {
			string += String.fromCharCode(c);
			i++;
		}
		else if((c > 191) && (c < 224)) {
			c2 = utftext.charCodeAt(i+1);
			string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
			i += 2;
		}
		else {
			c2 = utftext.charCodeAt(i+1);
			c3 = utftext.charCodeAt(i+2);
			string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
			i += 3;
		}

	}

	return string;
}
//===========================================================================}}}
//{{{ sdfValidatorNotEmpty
/**
 * Валидирует значение поля на непустоту
 * @since  26.10.2007 15:13:58
 * @author jikk
 */
function sdfValidatorNotEmpty( Field )
{
	var val = Field.val();
	if ( typeof val != 'string' || ! val || ! $.trim( val ) ) {
		Field.addError( 'field is required' );
	}
}
//===========================================================================}}}
//{{{ sdfEmailValidator
/**
 * Валидирует поле с электропочтой
 * @since  18.09.2007 18:15:20
 * @author jikk
 * @param SdfField Field
 */
function sdfEmailValidator( Field )
{
	var val = Field.val();
	if ( typeof val == 'string' && val )
	{
		if ( ! /^ *([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,}) *$/i.test(val) ) {
			Field.addError( 'invalid email' );
		}
		if ( val.length > 320 ) {
			Field.addError( 'email is too long' );
		}
	}
}
//===========================================================================}}}
//{{{ sdfJabberValidator
/**
 * Валидирует поле с jabber-аккаунтом
 * @since  24.12.2008 12:06:18
 * @author eugene
 * @param SdfField Field
 */
function sdfJabberValidator( Field )
{
	var val = Field.val();
	if ( typeof val == 'string' && val )
	{
		if ( ! /^ *([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,}) *$/i.test(val) ) {
			Field.addError( 'invalid jabber' );
		}
	}
}
//===========================================================================}}}
//{{{ sdfRegexpValidator
/**
 * Метод проверяет, что поле состоит из латинских букв
 * @since  30.11.2007 14:02:17
 * @author jikk
 */
function sdfLatinSmallLettersValidator( Field )
{
	var val = Field.val();
	if ( typeof val == 'string' && val )
	{
		if ( ! /^([a-z]+)$/.test(val) ) {
			Field.addError( 'field should contain only small english letters' );
		}
	}
}
//===========================================================================}}}

//{{{ SdfField
/**
 * Класс, отвечающий за логику работы поля формы.
 * Валидация поля, обработка ошибок, связанных с полем - основные обязанности класса
 * @package    sdf
 * @since      18.09.2007 11:51:12
 * @author     jikk
 * @param Form SdfForm форма, к которой относится поле
 * @param JEl jQueryObject объект, который соотвествует полю (input, textarea, select, etc)
 * @param Validator function функция-валидатор для поля
 * @param Handler function функция-обработчик для поля
 */
function SdfField( Form, JEl, Validator, Handler )
{
	this.form = Form;		// TODO: а зачем это?
	this.jEl = JEl;
	this.validators = [];	// список функций-валидаторов, присоединенных к полю
	this.handlers = [];		// список функций-обработчиков, присоединенных к полю
	this.inputName = JEl.attr( 'name' );  // имя поля
	this.addValidator( Validator );
	this.addHandler( Handler );
	this.errors = [];			// ошибки, выявленные при проверке поля
	this.notice = [];	   	// сообщение о нормальном завершении проверки
	this.data = {};			// объект, в который валидаторы могу складывать данные для хэндлеров
}
//-----------------------------------------------------------------------------
SdfField.prototype = {
//{{{ addValidator
/**
 * Присоединяет к полю валидатор, если он не был присоединен ранее
 * @since  18.09.2007 11:42:11
 * @author jikk
 * @param function v может быть null или undefined
 * @return void
 */
	addValidator : function ( v )
	{
		sdfArrayAddElementIfNotExists( this.validators, v );
	},
//===========================================================================}}}
//{{{ addHandler
/**
 * Присоединяет к полю обработчик, если он не был присоединен ранее
 * @since  18.09.2007 11:46:11
 * @author jikk
 * @param function h может быть null или undefined
 */
	addHandler : function ( h )
	{
		sdfArrayAddElementIfNotExists( this.handlers, h );
	},
//===========================================================================}}}
//{{{ val
/**
 * Возвращает текущее значение поля
 * @since  18.09.2007 11:47:17
 * @author jikk
 * @return mixed, обычно string или null
 */
	val : function ()
	{
		return this.jEl.val();
	},
//===========================================================================}}}
//{{{ check
/**
 * Запускает проверку корректности значения поля
 * @since  18.09.2007 11:48:12
 * @author jikk
 */
	check : function ()
	{
	// перед очередной проверкой очищаем старые ошибки и сообщения
		this.errors = []; this.notice = [];
		if ( ! this.form.preValidatorCheck || this.form.preValidatorCheck( this ) )
		{
			for ( var i in this.validators ) {
				this.validators[i]( this );
			}
			this.handleErrors();
		}
	},
//===========================================================================}}}
//{{{ handleErrors
/**
 * Запускает хендлеры ошибок. Выделено в отдельный метод, т.к. иногда ошибки могут прийти от сервера
 * и тогда надо запустить хендлеры, не вызывая валидаторы
 * @since  30.10.2007 12:58:32
 * @author jikk
 */
	handleErrors : function ()
	{
		for ( var i in this.handlers ) {
			this.handlers[i]( this );
		}
	},
//===========================================================================}}}
//{{{ addError
/**
 * Добавляет ошибку типа error
 * @since  18.09.2007 12:14:59
 * @author jikk
 * @param string EType тип ошибки (error, notice, autofix)
 * @param string LMessage сообщение об ошибке, ожидающее, что его переведут на нужный язык
 * @param string MParams параметры, которыми надо заменить спец-символы в сообщении
 */
	addError : function ( LMessage, MParams )
	{
		this.errors.push( new SdfError( 'error', LMessage, MParams ) );
	},
//===========================================================================}}}
//{{{ addNotice
/**
 * Добавляет ошибку типа notice
 * @since  18.09.2007 12:36:27
 * @author jikk
 * @param string EType тип ошибки (error, notice, autofix)
 * @param string LMessage сообщение об ошибке, ожидающее, что его переведут на нужный язык
 * @param string MParams параметры, которыми надо заменить спец-символы в сообщении
 */
	addNotice : function ( LMessage, MParams )
	{
		this.notice.push( new SdfError( 'notice', LMessage, MParams ) );
	},
//===========================================================================}}}
//{{{ focus
/**
 * Устанавливает фокус (если это возможно) на элемент, соответствующий объекту.
 * @since  18.09.2007 15:40:58
 * @author jikk
 */
	focus : function ()
	{
		if ( this.jEl.is( 'input|textarea' ) && this.jEl.css( 'display' ) != 'none' && this.jEl.css( 'visibility' ) != 'hidden' ) {
			this.jEl.focus();
		}
	},
//===========================================================================}}}
//{{{ getInputName
/**
 * Возвращает название инпута, в котором лежит значение
 * @since  09.07.2009 12:12:18
 * @author eugene
 */
	getInputName : function ()
	{
		return this.jEl.attr( 'name' );
	}
//===========================================================================}}}

};
/*============================================================================*
 *  END OF SdfField                                                           *
 *=========================================================================}}}*/

SdfForm = $.sdf.extend({
//{{{ __construct
/**
 * Класс, отвечающий за логику работы формы.
 * Валидация формы, отображение ошибок и т.п. - обязанности этого класса
 * @package    sdf
 * @since      18.09.2007 12:00:17
 * @author     jikk
 */
	__construct: function( el )
	{
		if ( this.parentCall( 'constructor', arguments ) === false ) return false;
		this.action = '';
		this.method = 'POST';
		this.format = 'json';
		this.submitInProgress = false;
		this.fields = {};
		this.defaultErrorHandler = typeof DefaultErrorHandler != 'undefined' ? DefaultErrorHandler : null;
		this.onSuccess = null;
		this.$bigBaraBoom = this.$el.find( '#bigBaraBoom' );
		if ( ! this.$bigBaraBoom.length )
		{
			this.$bigBaraBoom = $(document).find( '#bigBaraBoom' );
			if ( ! this.$bigBaraBoom.length ) {
				this.$bigBaraBoom = $( '<div id="bigBaraBoom"></div>' ).prependTo( this.$el );
			}
		}
		this.redirectByResponse = true;
		var $buttons = $( el ).find( '.bums-btn' );
		$buttons.each(
			function() {
				var $btnObj = $sdf( $( this ) );
				if ( $btnObj && $btnObj.type == 'submit' ) {
					$btnObj.initOwnForm();
				}
			}
		);

	},
//===========================================================================}}}
//{{{ setAction
/**
 * Устанавливает экшн
 * @param Action string урл, короче
 */
	setAction: function( Action )
	{
		this.action = Action;
		var me = this;
		var options = {
			dataType: this.format,
			url: this.action,
			type: this.method,
			beforeSubmit: function() { return me.beforeSubmit(); },
			success: function( json ) { me.submitSuccess( json ); }
		};

		$(document).ajaxError( $.proxy(this.serverErrorHandler, this) );

		this.$el.ajaxForm( options );
		this.$el.bind( 'form-pre-serialize', function(){
			me.trigger( 'onBeforeSubmit' );
		});
	},
//===========================================================================}}}
//{{{ setFormat
/**
 * Устанавливает формат ответа
 * @param Format string формат ответа
 */
	setFormat: function( Format )
	{
		this.format = Format;
	},
//===========================================================================}}}

	/**
	 * Обработчик ошибки сервера, которая может произойти при обработке данных из формы.
	 * @author rnd
	 * @param evt AjaxEvent
	 * @param xhr соответствующий объект XMLHttpRequest
	 * @param options настройки Ajax запроса
	 * @param error ошибка
	 */
	serverErrorHandler: function( evt, xhr, options, error ) {
		// Если это userLive - не делаем ничего
		if ( options && options.url && options.url.match( /^\/SdfPerm\/User\/userLive\.json/ ) ) {
			return;
		}
		// Приводим кнопки в рабочее состояние
		this.$el.find( '.bums-btn' ).each(
			function() {
				var $btnObj = $sdf( $( this ) );
				if ( $btnObj && $btnObj.type == 'submit' ) {
					$btnObj.endProgress();
				}
			}
		);
		// Выводим окно с сообщением об ошибке
		this.submitInProgress = false;
		var ajaxErrorWindow = $sdf( $( '<div id="ajaxErrorWindow" class="sdf-wnd sdf-wnd-shadow"></div>' ).
				prependTo( document.body ).
				sdf( $.sdf.window ) );
		var errorWindowWidth = Math.round( $( window ).width() * 0.9);
		var errorWindowHeight = Math.round( $( window ).height() * 0.8);

		var errorMessage = '';

		// В некоторых случаях параметр error приходит пустым, тогда берем содержимое из xhr
		if( !error ) {
			error = xhr.statusText + xhr.responseText; 
		}

		// Формируем сообщение об ошибке. Тут есть два варианта:
		if( error.indexOf("<?xml") >= 0 ) {
			// У нас ошибка на сервере и мы получили в качестве ответа стандартную страницу ошибки
			var stacktrace = error.indexOf("<?xml") >= 0 ? error.substring(0, error.indexOf("<?xml")) : '';
			var style = error.substring(error.indexOf("<style"), error.indexOf("</style>") + 8);
			var serverError = error.substring(error.indexOf("<body>") + 6, error.indexOf("</body>"));
			errorMessage = stacktrace + style + serverError;
		} else {
			// Либо на сервере все хорошо, но нам по какой-то причине прилетел невалидный JSON,
			// тогда, если мы не в режиме отладки DEBUG_MODE_ENABLED его необходимо завернуть в наш дизайн
			if ( window.sdf.settings.get( 'DEBUG_MODE_ENABLED' ) === true ) {
				errorMessage = '<pre>' + error + '</pre>';
			} else {
				var supportEmail = window.sdf.settings.get( 'app.support_email' );
				var encodedMessage = $.base64Encode(
							'<table class="jsError"><tr><th>Сообщение об ошибке:</th><td>' + error +
							'</td></tr><tr><th>URL страницы:</th><td>' + document.location.href +
							'</td></tr><tr><th>URL AJAX-запроса:</th><td>' + ( options ? options.url : '?' ) +
							'</td></tr><tr><th>Данные запроса:</th><td>' + ( options ? url_decode( options.data ) : '?' ) +
							'</td></tr><tr><th>Время на клиенте:</th><td>' + (new Date()) +
							'</td></tr><tr><th>Заголовки ответа сервера:</th><td>' + ( xhr.getAllResponseHeaders ? xhr.getAllResponseHeaders() : '?' ) +
							'</td></tr><tr><th>UserAgent:</th><td>' + ( navigator ? navigator.userAgent : '?' ) ).
							replace( /[a-zA-Z0-9/]{20}/g, '$&<wbr/>') +
							'</tr></table>';
				errorMessage = '<style type="text/css">body {background-color:#F9F9F7;font-size:83%!important;' +
						'font-family:arial;margin:0;padding:0;color:#000000;}	a {color:#438EB3;white-space:nowrap;}' +
						'#content {margin:143px 0 0 93px;}	#content .inner {width:100%; overflow:hidden;}' +
						'.megaplan { background:url(/s/7/i/megaplan.gif) 0 65% no-repeat; padding-left:132px;' +
						'min-height:16px; } .megaplan { color:#616161;font-size:1.95em; margin:0; font-weight:normal; }' +
						'.inner .block {margin:32px 0 0 132px;} .debug { width:70%; font-family:monospace; } </style>' +
						'<div id="content"><div class="inner"><div class="megaplan">' + sdfGetText( 'JavaScript error' ) +
						'</div><div class="block">' + sdfGetText( 'We are greatly sorry but something goes wrong.' ) +
						'<br /><p style="font-size:1.4em;margin: 1em 0;">' + sdfGetText( 'Please' ) + ', <a href="mailto:' +
						supportEmail +	'" id="mailto">' + sdfGetText( 'inform us about this error' ) + '</a> (' +
						sdfGetText( 'e-mail client is required' ) + ').</p><p>' + sdfGetText( 'Please, send e-mail to' ) +
						' <b><a href="mailto:' + supportEmail + '">' + supportEmail + '</a></b> ' +
						sdfGetText( 'and insert the following text besides the error description' ) + ':</p>' +
						'<div class="debug">' + '[js]' + encodedMessage + '</div></div></div></div>';
			}
		}

		ajaxErrorWindow.showCloseIcon = true;
		ajaxErrorWindow.setHtml( '<style type="text/css">.sdf-wnd #content { margin-top: 70px !important; }</style>' +
				'<div style="width: ' + errorWindowWidth + 'px; height: ' + errorWindowHeight +
				'px; overflow: auto; margin-right: 10px;">' + errorMessage + '</div>');
		var errorWindowLeft = Math.round( ( $( window ).width() - ajaxErrorWindow.$el.width() ) / 2 );
		var errorWindowTop = Math.round( ( $( window ).height() - ajaxErrorWindow.$el.height() ) / 2 );
		ajaxErrorWindow.show( errorWindowLeft, errorWindowTop, 'px' );
		ajaxErrorWindow.setModal();
	},

//===========================================================================}}}
	beforeSubmit: function()
	{
		if ( this.submitInProgress ) {
			return false;
		}
		this.submitInProgress = true;
		var isValid = true;

		if ( this.beforeSubmitSpecial )
		{
			if ( ! this.beforeSubmitSpecial() ) {
				isValid = false;
			}
		}

		this.$el.find( '.help' ).text( '' );
		this.$bigBaraBoom.text( '' );
		this.$el.find( '.field' ).removeClass( 'error' ).removeClass( 'autofix' ).removeClass( 'notice' );
		var firstFieldWithError = null;
		for ( var inputName in this.fields )
		{
			var f = this.fields[inputName];
			if ( f instanceof SdfField )
			{
				f.check();
				if ( f.errors.length > 0 && null == firstFieldWithError )
				{
					isValid = false;
					firstFieldWithError = f;
				}
			}
		}
		if ( null != firstFieldWithError )
		{
			this.submitInProgress = false;
			this.trigger('fail');
			firstFieldWithError.focus();
			return false;
		}
		else if ( ! isValid )
		{
			this.submitInProgress = false;
			this.trigger('fail');
			return false;
		}
		this.submitInProgress = true;
		return true;
	},

	submitSuccess: function( json )
	{
		var focusSet = false;
		var i = 0;
		var failTriggered = false;
		with ( json )
		{
			if ( sdfHandleJsonException( json ) )
			{
				this.trigger('fail');
				this.submitInProgress = false;
				return;
			}
			if ( json.formErrors && json.formErrors.length )
			{
				this.trigger('fail');
				failTriggered = true;
				for ( i=0; i < formErrors.length; i++ )
				{
				// если поле зарегистрировано и это SdfField
					var inputName = formErrors[i].input;
					var field = this.fieldByName( inputName );
					if ( field && field.errors !== undefined )
					{
						field.errors.push( new SdfError( formErrors[i].type, formErrors[i].msg ) );
						if ( ! focusSet )
						{
							field.focus();
							focusSet = true;
						}
						field.handleErrors();
					}
				// TODO от этой ситуации надо избавиться. должны быть зарегены поля
					else
					{
						var jInput = this.$el.find( 'input[name="'+formErrors[i].input+'"]' );
						if ( !jInput.length ) {
							jInput = this.$el.find( 'textarea[name="'+formErrors[i].input+'"]' );
						}
						if ( ! jInput.length ) {
							json.errorSummary = (typeof(json.errorSummary) == 'undefined' ? '' : json.errorSummary + "\n<br/>" ) + formErrors[i].msg;
						}
					// Если можем навести фокус на первый элемент с ошибкой, делаем это
						if ( ! focusSet )
						{
							if ( jInput.css( 'display' ) != 'none' && jInput.css( 'visibility' ) != 'hidden' ) {
								jInput.focus();
							}
							focusSet = true;
						}
						var jField = jInput.parents( '.field' );
						if ( jField.length )
						{
							jField.addClass( formErrors[i].type );
							jField.find('.help').text( formErrors[i].msg );
						}
						else
						{
							var $errElem = this.$el.find( 'div[field="' + formErrors[i].input + '"]' );
							if ( $errElem )
							{
								$errElem.html( formErrors[i].msg );
							}
						}
						if ( formErrors[i].type == 'autofix' ) {
							this.setFieldValue( formErrors[i].input, formErrors[i].value );
						}
					}
				}
			}
			if ( json.errorSummary )
			{
				var $boom = this.$el.find( '.big-bara-boom' );
				if ( ! $boom.length ) $boom = $('#bigBaraBoom');
				$boom.html( json.errorSummary );
				var anchor = $boom.attr( 'name' );
				if ( anchor ) {
					document.location.href = "#" + anchor;
				}
				this.submitInProgress = false;
			}
			else if ( json.redirectTo && this.redirectByResponse )
			{
				var redirectTo = json.redirectTo;
				document.location.href = redirectTo;
				if ( redirectTo.charAt(0) == '#' ) {
					this.submitInProgress = false;
				}
				else
				{
					if ( this.onSuccess )
					{
						this.onSuccess( json );
						this.submitInProgress = false;
					}
					return;
				}
			}
			else if ( this.onSuccess )
			{
				this.onSuccess( json );
				this.submitInProgress = false;
				return;
			}
			if ( ! failTriggered )
			{
				this.trigger('fail');
				failTriggered = true;
			}
		}
		this.submitInProgress = false;
		this.trigger( 'onSubmitSuccess', json );
	},


	registerField: function( FieldObject )
	{
		this.fields[ FieldObject.getInputName() ] = FieldObject;
	},

	setFieldValue: function( InputName, Value )
	{
		if ( ! this.fields[InputName] ) {
			return false;
		}
		this.fields[InputName].setValue( Value );
	},

	disable: function()
	{
		this.$el.addClass( 'form-disabled' );
		this.trigger( 'formDisabled' );
	},

	enable: function()
	{
		this.$el.removeClass( 'form-disabled' );
		this.trigger( 'formEnabled' );
	},

	registerCheck: function ( jEl, Validators, Handlers )
	{
		var inputName = jEl.attr( 'name' );
		if ( ! this.fields[inputName] ) {
			this.fields[inputName] = new SdfField( this, jEl );
		}
		if ( this.fields[inputName] instanceof SdfField )
		{
			if ( typeof Validators == 'function' ) {
				Validators = [Validators];
			}
			for ( var i in Validators ) {
				this.fields[inputName].addValidator( Validators[i] );
			}
			if ( typeof Handlers == 'function' ) {
				Handlers = [Handlers];
			}
			for ( var i in Handlers ) {
				this.fields[inputName].addHandler( Handlers[i] );
			}
		}
		return this.fields[inputName];
	},

	fieldByName: function( InputName )
	{
		var res = null;
		var matchLength = 0;
	// Ищем поле с максимальным совпадением названия
		for ( var i in this.fields )
		{
			if ( InputName == i.substr( 0, InputName.length ) || i == InputName.substr( 0, i.length ) )
			{
				var l = Math.min( InputName.length, i.length );
				if ( l > matchLength )
				{
					matchLength = l;
					res = this.fields[i];
				}
			}
		}
		return res;
	}
},
{
	defaultErrorHandler: function( Field )
	{
		var ers = Field.errors, errCell;
	// ищем строку
		var jField = Field.jEl.parents( 'div.field' );
		if ( ers.length )
		{
		// возможно, элемент с ошибкой уже был создан
			errCell = jField.find( 'div.help' );
		// если нет, создаем его
			if ( 0 == errCell.length ) {
				errCell = $( '<div class="help">' + ers[0].message + '</div>' ).appendTo( jField );
			}
		// если уже был, надо удалить "ошибочный" css-класс у строки
			else {
				jField.removeClass( errCell.attr( 'type' ) );
			}
			errCell.html( ers[0].message );
			errCell.attr( 'type', ers[0].type );
			jField.addClass( ers[0].type );
		}
		else
		{
		// ищем ошибочный элемент
			errCell = jField.find( 'div.help' );
		// если нашли, удаляем его и удаляем "ошибочный" css-класс у строки
			if ( errCell.length )
			{
				jField.removeClass( errCell.attr( 'type' ) );
				errCell.remove();
			}
		}
		if ( Field.notice.length )
		{
			var $notice = jField.find( 'div.notice' );
			if ( ! $notice.length ) {
				$notice = $( '<div class="notice"></div>' ).appendTo( jField );
			}
			$notice.html( Field.notice[0].message ).show();
		}
		else {
			jField.find( 'div.notice' ).html( '' ).hide();
		}
	}
});
/*============================================================================*
 *  END OF SdfForm                                  	               			*
 *=========================================================================}}}*/

 	$(document).ready(function(){
		$('.sdf-form').sdf( SdfForm );
	});

//{{{ SdfError
/**
 * Класс инкапсулирует в себе информацию об ошибке - тип ошибки, сообщение.
 * @package    sdf
 * @since      18.09.2007 12:40:17
 * @author     jikk
 */
function SdfError( type, message, params )
{
	this.message = sdfGetText( message, params );
	this.type = type;
}
//-----------------------------------------------------------------------------
SdfError.prototype = {};
/*============================================================================*
 *  END OF SdfError                                                           *
 *=========================================================================}}}*/

 /*============================================================================*
 * vim: set expandtab tabstop=3 shiftwidth=3 foldmethod=marker:               *
 *   END OF FILE                                                              *
 *============================================================================*/