/**
 * Скрипт для работы с клиентской частью CMS. 
 * Содержит управление версткой, Ajax-запросами и загрузкой файлов.
 * 
 * Используются следующие библиотеки:
 * icms - текстовые сообщения на разных языках. /js/язык/interface.js
 * jQuery (v1.6.3) - фреймворк JavaScript. http://jquery.com/
 * jQuery Form Plugin (v2.85) - отправка данных формы с помощью Ajax. http://jquery.malsup.com/form/
 * TinyMCE (v3.4.2) - визуальный редактор. http://tinymce.moxiecode.com/
 * 
 * Copyright 2010-2011
 */
var cms = new CMS();
var a = new Ajax();

/**
 * Общий объект.
 * 
 * Используется для верстки всего и вся.
 */
function CMS() {
	// Массив соответствий кодов символов UTF-8 кодам Windows-1251.
	this.win = [];
	
	// окно аутентификации и верификации на стороннем сервисе
	this.provider_window = null,
	
	// объект таймера
	this.timer = null,
	
	// флаг включения таймера
	this.timer_is_on = false,
	
	// конструктор объекта. рисование и навешивание всякой верстки
	this.constructor = function() {
		/*
		 * у юзера есть js. необходимо для определения сабмита формы через
		 * ajax  на стороне сервера, если в форме присутствует поле типа file. 
		 */
		$('input[name="is_js"]').val('on');
		
		// получение атрибутов элемента
		this.setAttrHandler();
		// установка редактора
		this.setEditor();
	},
	
	/*
	 * чтение всех атирибутов элемента http://code.google.com/p/jquery-list-attributes/
	 */
	this.setAttrHandler = function() {
		(function($){
			$.fn.listAttributes = function(prefix) {
				var list = [];
				
				$(this).each(function() {
					var attributes = [];
					
					for (var key in this.attributes) {
						if (!isNaN(key)) {
							if (!prefix || this.attributes[key].name.substr(0, prefix.length) == prefix) {
								attributes.push(this.attributes[key].name);
							}
						}
					}
					
					list.push(attributes);
				});
				
				return (list.length > 1 ? list : list[0]);
			};
		})(jQuery);
	},
	
	// определение браузера. для удобного разнесения стилей, если в одном браузере че-то не так как во остальных
	this.browser = function() {
		var ua = navigator.userAgent.toLowerCase();
		var html = $('html');
		var browser, version = '';
		$.browser.mobile = /mobile|opera mini/.test(ua);
		$.browser.chrome = /chrome/.test(ua);
		
		if ($.browser.mobile) {
			$(html).addClass('mobile');
			
			if (ua.indexOf('opera mini') != -1) {
				$(html).addClass('opera-mini');
			}
			else if (ua.indexOf('ipod') != -1) {
				$(html).addClass('ipod');
			}
			else if (ua.indexOf('iphone') != -1) {
				$(html).addClass('iphone');
			}
			else if (ua.indexOf('ipad') != -1) {
				$(html).addClass('ipad');
			}
		}
		
		if ($.browser.msie) {
			browser = 'msie';
			version = $.browser.version.substring(0, 1);
		}
		else if ($.browser.chrome) {
			browser = 'chrome';
			version = browser + ua.substring(ua.indexOf('chrome/') + 7).substring(0, 2);
		}
		else if ($.browser.safari) {
			browser = 'safari';
			version = browser + ua.substring(ua.indexOf('version/') + 8).substring(0, 1);
		}
		else if ($.browser.mozilla) {
			if (navigator.userAgent.toLowerCase().indexOf('firefox') !=- 1) {
				browser = 'firefox';
				version = browser + ua.substring(ua.indexOf('firefox/') + 8).substring(0, 1);
			}
			else {
				browser = 'mozilla';
			}
		}
		else if ($.browser.opera) {
			browser = 'opera';
			version = browser + ua.substring(ua.indexOf('version/') + 8).substring(0, 2);
		}
		
		if (browser) {
			$(html).addClass(browser + ' ' + version);
		}
	},
	
	/*
	 * установка визуального редактора для некоторых текстовых полей
	 */
	this.setEditor = function() {
		if ($.isFunction($.fn.tinymce)) {
			// редактор на все поля
			$('.editor').tinymce({
				script_url: "/js/tiny_mce/tiny_mce.js",
				theme: "simple",
				theme_advanced_toolbar_location: "top",
				theme_advanced_toolbar_align: "left",
				theme_advanced_resizing: true,
				skin: "default",
				skin_variant: "silver",
				mode: "textareas",
				plugins: "fullscreen",
				theme_advanced_buttons3_add: "fullscreen",
				fullscreen_new_window: true,
				fullscreen_settings: {
					theme_advanced_path_location: "top"
				},
				valid_elements: "*[*]",
				verify_css_classes: false,
				language: icms.lang
			});
			// редактор для с возможностью выделения "подката"
			$('#readmore').tinymce({
				script_url: "/js/tiny_mce/tiny_mce.js",
				theme: "advanced",
				mode : "none",
				theme_advanced_buttons1 : "bold,italic,underline,undo,redo,link,unlink,forecolor,styleselect,removeformat,cleanup,code,|,readmore",
	        	theme_advanced_buttons2 : "",
	        	theme_advanced_buttons3 : "",
				theme_advanced_toolbar_location: "top",
				theme_advanced_toolbar_align: "center",
				theme_advanced_resizing: true,
				theme_advanced_styles : "Code=codeStyle;Quote=quoteStyle",
				skin: "default",
				skin_variant: "silver",
				plugins: "bbcode,readmore",
				valid_elements: "*[*]",
				verify_css_classes: false,
				entity_encoding : "raw",
		        add_unload_trigger : false,
		        remove_linebreaks : false,
		        inline_styles : false,
		        convert_fonts_to_spans : false,
				language: icms.lang
			});
		}
	},
	
	/*
	 * отображение сервисного окна. вывод информационных сообщений, сообщений
	 * об ошибках, далогов.
	 * 
	 * - response. ответ сервера в формате JSON. атрибуты объекта:
	 * 'type' - тип ответа('message' - информационное сообщение, 'question' -
	 * диалоговое сообщение, 'error' - общая ошибка, 'slave' - получены
	 * зависимые поля), 'content' - содержание ответа, 'redirect' - ссылка для
	 * перенаправления;
	 * - overlayed. будет ли на странице оверлей или нет;
	 * - timeout. время задержки окон в милисекундах;
	 * - form. форма (объект jQuery), в которой произошел вызов.
	 */
	this.showModal = function(response, overlayed, timeout, form) {
		/*
		 * когда загружается файл через флеш, то всегда на сервер уходит на
		 * один запрос больше. сначала сами файлы, потом (последний) сабмит формы.
		 * запросы уходят асинхронно. сабмит формы отрабабывает быстрее запросов
		 * загрузки файлов и выдает свое сообщение. возникает ситуация, когда
		 * одно еще не исчезло, а следующее уже появилось, причем с тем же
		 * айдишником. что показывалось всегда актуальное сообщение (последнее),
		 * удаляем имеющееся окно
		 */
		var modal = $('#the-modal');
		
		if (modal.length) {
			modal.remove();
		}
		
		$('body').append('<div id="the-modal" class="' + response.type + '"><div class="header"></div>' + response.content + '</div>');
		
		if (overlayed) {
			this.showOverlay();
		}
		
		switch (response.type) {
			case 'error': $('#the-modal .header').text(icms.error_h);   break;
			default:      $('#the-modal .header').text(icms.message_h); break;
		}
		
		$('#the-modal').animate({'top': '0'}, 700);
		
		if (timeout) {
			$('#the-modal').animate({'marginBottom': '0'}, timeout, function() {
				if (overlayed) {
					$('#the-overlay').click();
				}
				else {
					$('#the-modal').animate({'top': '-1000px'}, 700, function() {
						$(this).remove();
					});
				};
			});
			
		}

		if ((
				form != undefined                               &&
				$(form).attr('name') != 'files_search'          &&
				!($(form).attr('name') == 'form_confirm_delete' &&
				$('input[name="itype"]', form).val()            &&
				$('input[name="field_id"]', form).val())        &&
				response.type != 'question'                     &&
				response.type != 'error'                        &&
				response.redirect
			) ||
			(
				form == undefined        &&
				response.type != 'error' &&
				response.redirect
			)) {			
			window.location.href = response.redirect;
		}
	},
	
	/*
	 * показ оверлея
	 */
	this.showOverlay = function() {
		// см. примечание в this.showModal
		var overlay = $('#the-overlay');
		
		if (overlay.length) {
			overlay.remove();
		}

		$('body').append('<div id="the-overlay"></div>');
		$('#the-overlay').css({'height': $('body').height()}).animate({'opacity': '0.3'}, 600);
		$('*[rel=close-over], #the-overlay, #the-modal button[name="cancel"]').live('click', function() {
			$('#the-overlay').animate({'opacity': '0'}, 500, function() {
				$(this).remove();
			});
			$('#the-modal').animate({'top': '-1000px'}, 700, function() {
				$(this).remove();
			});
		});
	},
	
	/*
	 * Перекодировка символов UTF-8 в Windows-1251.
	 * Символы с кодом больше 255 удаляются.
	 * 
	 * Возвращет перекодированную строку в escape-последовательности.
	 */
	this.escapeWin = function(text) {
		if (!this.win.length) {
			for (var i = 0x410; i <= 0x44F; i++) {
				win[i] = i - 0x350;
			}
			
			win[0x401] = 0xA8;
			win[0x451] = 0xB8;
		}
		
		var ret = [];

		for (var i = 0; i < text.length; i++) {
			var n = text.charCodeAt(i);
	
			if (typeof win[n] != 'undefined') {
				n = win[n];
			}
	
			if (n <= 0xFF) {
				ret.push(n);
			}
	    }
	
	    return escape(String.fromCharCode.apply(null, ret));
	},
	
	/*
	 * ждем, когда закроется окно верификации
	 */
	this.waitVerification = function(provider) {
		if (!this.timer_is_on) {
  			this.timer_is_on = true;
  			this.timedCount(provider);
  		}
	},
	
	/*
	 * таймер ожидания закрытия окна аутентификации и обработка события закрытия
	 */
	this.timedCount = function(provider) {
		// окно закрылось
		if (cms.provider_window.closed) {
			clearTimeout(cms.timer);
			cms.timer_is_on = false;
			// обновляем кнопку соединения/разъединения с провайдером
			a.main(
				undefined,
				{
					module: 'social',
					method: 'site_add_account',
					provider: provider,
					add: 0
				},
				{ url: '/index.php', replace: provider + '_connect' }
			);
		}
		// ждем закрытия
		else {
			cms.timer = setTimeout(function() { cms.timedCount(provider); }, 1000);
		}
	}
}

/**
 * Объект для работы с Ajax.
 */
function Ajax()  {
	var a = this;
	
	/*
	 * конструктор объекта. навешивает ajax куда надо.
	 * 
	 * setHandlers - установка ajax обрвботчиков.
	 */
	a.constructor = function(setHandlers) {
		// установки по умолчанию
		$.ajaxSetup({
			dataType: 'json',
			error: a.errorHandler
		});
		
		if (setHandlers) {
			a.formHandler();
			a.click();
			a.pagination();
		}
	},
	
	/*
	 * обработчик ответов сервера в результате ajax запроса.
	 * 
	 * - response. ответ сервера в формате JSON. атрибуты объекта:
	 * 'type' - тип ответа: 'message' - информационное сообщение, 'question' -
	 * диалоговое сообщение, 'interface' - интерфейс какого модуля, 'error' -
	 * общая ошибка, 'validate_error' - ошибка валидации поля, 'slave' - получены
	 * зависимые поля), 'content' - содержание ответа, 'redirect' - ссылка для
	 * перенаправления;
	 * - status. статус ajax запроса ('success', 'notmodified', 'error', 'timeout', 'parsererror');
	 * - xhr. объект XMLHttpRequest;
	 * - form. форма. объект jQuery.
	 */
	a.responseHandler = function(response, status, xhr, form) {
		switch (response.type) {
			case 'content':
				if (response.redirect) {
					window.location.href = data.redirect;
				}
				else {
					var element = 'replace' in response
						? $('#' + response.replace)
						: $('#' + $(':hidden[name="result"]', form).val());
					$(element).replaceWith(response.content);
					a.click();
					a.pagination();
				}
			
				break;
			// залипень, пока контакты не переделаны под oauth
			case 'vk':
				if ($('input[name="upload_to_vk"]', form).is(':checked')) {
					vk_api.uploadPhoto2Server(response);
				}
				else {
					console.log('Upload to vk doesn\'t checked');
					response.content = response.message;
					cms.showModal(response, true, 2000, form);
				}
				
				break;
			case 'provider':
				// аутентифицируемся у провайдера
				cms.provider_window = window.open(response.content, 'Service Provider', 'width=790,height=580');
				// ждем верификации
				cms.waitVerification(response.provider);
				
				break;
			case 'question':
				/*cms.showModal(response, true, 0, form);
				a.formHandler('the-modal');*/
				
				break;
			default:
				cms.showModal(response, true, 2000, form);
			
				break;
		}
		
		$('.form-ajax .finish').removeClass('spinner');
		$('.form-ajax .finish label').animate({'marginRight': '0'}, 400);
	},
	
	/*
	 * общий обработчик ошибок
	 * 
	 * - xhr. объект XMLHttpRequest;
	 * - status. статус ajax запроса ('success', 'notmodified', 'error', 'timeout', 'parsererror');
	 * - error. текст ошибки. при наличии первых двух параметров, наличие данного
	 * необязательно. но может быть наоборот. если присутсвует данный параметр,
	 * первые два могут отсутствовать.
	 */
	a.errorHandler = function(xhr, status, error) {
		if (text = xhr ? xhr.responseText : (error ? error : '')) {
			alert('STATUS:\n' + (status ? status : '') + ':\n\nTEXT:\n' + text + '\n\nOPTIONAL:\n' + error);
		}
	},

	/*
	 * вешаем Ajax для отправки форм.
	 */
	a.formHandler = function() {
		var forms = $('form');
		
		$(forms).each(function() {
			var form = $(this);
			
			// вешаем валидацию обязательных полей
			a.requireFieldsHandler(form);
			
			if ($(form).hasClass('form-ajax')) {
				$(form).ajaxForm({
					beforeSubmit: function(arr, form, options) {
						// красивости всякие
						$('.finish', form).addClass('spinner').find('button').attr('readonly', 'readonly').animate({'opacity': '0.5'}, 400);
						$('.finish label', form).animate({'marginRight': '30px'}, 400);
					},
					success: a.responseHandler,
					complete: function(xhr, status) {
						// это для всех форм
						$('.finish', form).find('button').removeAttr('readonly').animate({'opacity': '1'}, 400);
					}
				});
			}
		});
		$('.form-ajax:not([name="form_login"]) input, .form-ajax textarea, .form-ajax select').bind('change keypress', function() {
			$(this).parents('form').find('button[type="reset"]').fadeIn(400);
		});
		$('.form-ajax button[type="reset"]').live('click', function(){
			$(this).fadeOut(400);
		});
	},
	
	/*
	 * список полей формы, которые необходимо отправить вместе с обязательным
	 * или ведущим полем во время валидации.
	 * 
	 * - form. форма. объект jQuery.
	 */
	a.getSendFields = function(form) {
		var data = {};
		
		$('*[send]', form).each(function() {
			data[$(this).attr('name')] = $(this).val();
		});
		
		return data;
	},
	
	/*
	 * валидация обязательных полей.
	 * 
	 * - form. форма. объект JQuery.
	 */
	a.requireFieldsHandler = function(form) {
		$('*[required]', form).bind('blur', function() {
			var data = a.getSendFields(form);
			var field = $(this);
			data['module']   = $(':hidden[name="module"]', form).val();
			data['method']   = $(':hidden[name="method"]', form).val();
			data['save']     = 1;
			data['validate'] = 1;
			data['name']     = $(field).attr('name');
			data['value']    = $(field).val();
			a.main(form, data, {'field': field});
		});
	},
	
	/*
	 * отправка Ajax запросов основным методом jQuery.ajax().
	 * 
	 * - form. форма. объект jQuery;
	 * - data. дополнительные данные для отправки на сервер;
	 * - options. параметры, необходимые после успешного выполнения запроса.
	 * в самом запросе отсутсвуют.
	 */
	a.main = function(form, data, options) {
		if (!data) {
			data = {};
		}
		
		if (!options) {
			options = {};
		}
		
		if (url = (form != undefined) ? $(form).attr('action') : options.url) {
			$.ajax({
				url: url,
				data: data,
				success: function(response) {
					switch (response.type) {
						// все хорошо
						case 'message':
							/*
							 * если была валидация поля и при предыдущей проверке
							 * был касяк, снимаем, что поле касяковое
							 */
							if (field = options.field) {
								$(field).next().remove();
							}
						
							break;
						// ошибка валидации поля
						case 'validate_error':
							var error = $(options.field).next();
							
							if (false == $(error).hasClass('validate-error')) {
								error = $('<div class="validate-error"></div>').insertAfter(options.field);
							}
	
							$(error).html(response.content);
	
							break;
						default:
							if ('replace' in options) {
								response.replace = options.replace;
							}

							a.responseHandler(response);
						
							break;
					}
				}
			});
		}
	},
	
	/*
	 * навешивание кликов.
	 * 
	 * vk - запрос после ответа ВКонтакте API. может принимать значение 'albums'
	 * (работа с альбомами) или 'photos' (работа с фотками альбома).
	 * 
	 * @todo хрень с контактами не оптимизирована
	 */
	a.click = function(vk) {
		$('.ajax_click').css({'cursor': 'pointer'}).bind('click', function() {
			var element = this;
			/*
			 * первым элементом в контейнере рисуем мозговую деятельность (если
			 * мозговой деятельности еще нет). она исчезнет при получении нового
			 * контента или ошибки
			 */
			if (!$('#loading').length) {
				$($('#' + $(element).attr('replace')).children().get(0)).before('<img id="loading" src="/i/spinner.gif" alt="loading" />');
			}

			// параметры куда отправлять запрос (метод, модуль...)
			var params = a.getParams(element);

			/*
			 * работаем с контактами. в объекте vk_api хранятся все запрошенные альбомы.
			 * навешиваем клик на альбом. если неизвестно количество фоток на
			 * странице, тогда запрашиваем сервер (свой).
			 */
			if (vk && vk == 'albums' && vk_api.albumPhotosPerPage) {
				// запрашиваем фотки альбома
				vk_api.getVkAlbumPhotos(undefined, params['album_id'], params['album_page'], 1);
			}
			else {
				/*
				 * работаем с контактами. в объекте vk_api хранятся все запрошенные альбомы.
				 * навешиваем клик на возврат к альбомам
				 */
				if (vk && vk == 'photos') {
					/*
					 * чтобы не обращаться к апи каждый раз, проверяем объект
					 * vk_api на наличие альбомов. если в объекте альбомов нет,
					 * значит апи еще не спрашивали (на данном этапе это очень-очень
					 * мало вероятно, практически невозможно, ибо сначала получаются
					 * альбомы (обработка 'albums'), потом запросы на фотки). в 
					 * этом случае будет запрос к апи. выбираем альбомы, в 
					 * зависимости от номера страницы
					 */
					params['vk_albums'] = JSON.stringify(vk_api.albums.array.slice(
						(params['page'] - 1) * vk_api.albumsPerPage,
						(params['page'] - 1) * vk_api.albumsPerPage + vk_api.albumsPerPage
					));
					params['count'] = vk_api.albums.count;
					
					/*
					 * после вставки альбомов, работаем уже с ними, поэтому
					 * меняем vk, чтобы верно навесились пагинация и клики
					 */
					vk = 'albums';
				}

				$.post('/index.php', params, function(response) {
					if (response.type == 'error') {
						$('#loading').remove();
						a.errorHandler(undefined, undefined, response.content + '\nSOURCE:\n' + response);
					}
					else {
						if (provider = $(element).attr('provider')) {
							response.provider = provider;
						}
						
						if (replace = $(element).attr('replace')) {
							response.replace = replace;
						}
						
						a.responseHandler(response);
					}
				});
			}
		});
	},

	/*
	 * постраничная навигация
	 * 
	 * vk - запрос после ответа ВКонтакте API. может принимать значение 'albums'
	 * (работа с альбомами) или 'photos' (работа с фотками альбома).
	 * 
	 * @todo хрень с контактами не оптимизирована
	 */
	a.pagination = function(vk) {
		$('.pagination[module!=""][method!=""]').each(function() {
			var element = this;
			// параметры куда отправлять запрос (метод, модуль...)
			var params = a.getParams(element, vk);

			$('span', element).css({'cursor': 'pointer'}).bind('click', function() {
				/*
				 * первым элементом в контейнере рисуем мозговую деятельность
				 * (если мозговой деятельности еще нет). она исчезнет при 
				 * получении нового контента или ошибки
				 */
				if (!$('#loading').length) {
					$($('#' + $(element).parent().attr('id')).children().get(0)).before('<img id="loading" src="/i/spinner.gif" alt="loading" />');
				}
				
				params['page'] = $(this).attr('page');
				
				/*
				 * работаем с контактами. в объекте vk_api хранятся все запрошенные 
				 * альбомы. фотки же этих альбомов запрашиваем у апи. выбираем 
				 * фотки, в зависимости от номера страницы
				 */
				if (vk && vk == 'photos') {
					vk_api.getVkAlbumPhotos(undefined, params['album_id'], params['album_page'], params['page']);
				}
				else {
					/*
					 * навешиваем пагинацию на альбомы. в объекте vk_api хранятся все 
					 * запрошенные альбомы
					 */
					if (vk && vk == 'albums') {
						// выбираем альбомы, в зависимости от номера страницы
						params['vk_albums'] = JSON.stringify(vk_api.albums.array.slice(
							(params['page'] - 1) * vk_api.albumsPerPage,
							(params['page'] - 1) * vk_api.albumsPerPage + vk_api.albumsPerPage
						));
						params['count'] = vk_api.albums.count;
					}

					$.post('/index.php', params, function(response) {
						if (response.type == 'content') {
							// родительский элемент постраничной навигации, заменяем новым контентом 
							$('#' + $(element).parent().attr('id')).replaceWith(response.content);
							a.pagination(vk);
							a.click(vk);
						}
						else if (response.type == 'error') {
							$('#loading').remove();
							a.errorHandler(undefined, undefined, response.content + '\nSOURCE:\n' + response);
						}
					});
				}
			});
		});
	},

	/*
	 * получение параметров
	 */
	a.getParams = function(element) {
		var params = {};
		
		$.map($(element).listAttributes(), function(name) {
			if ($.inArray(name, ['class', 'id', 'style', 'name', 'value', 'src', 'alt', 'title', 'width', 'height', 'replace']) == -1) {
				params[name] = $(element).attr(name);
			}
		});
		
		return params;
	},
	
	/*
	 * выполнение POST-запросов
	 */
	a.post = function(params, callback) {
		$.post('/index.php', params, function(response) { callback(response, a); });
	}
}

/**
 * Инициализация.
 */
function initialize() {
	// закрываем окно аутентификации с опен ид
	if ('Service Provider' == window.name) {
		window.close();
	}
	
	cms.constructor();	
	a.constructor(true);
}

$(document).ready(function() { initialize(); });

