/**
 * @projectDescription
 * Слайдер изображений. Поддерживает кэширование.
 * @author Vlad Yakovlev
 * @version 0.2
 * @requires jQuery 1.2
 * @requires jTweener
 */
var slider = (function() {

	/**
	 * Объекты jQuery html-элементов.
	 * @type {Object}
	 */
	var blocks;

	/**
	 * Пути к изображениям. Массив объектов, каждый из которых имеет свойства:
	 * <ul>
	 *  <li><code>high</code> - путь к изображению высокого разрешения,</li>
	 *  <li><code>low</code> - путь к изображению низкого разрешения.</li>
	 * </ul>
	 * @type {Array[Object]}
	 */
	var imagePaths;

	/**
	 * Массив объектов, хранящих информацию об изображениях:
	 * <ul>
	 *  <li><code>lowImage</code> - объект изображения низкого разрешения (<code>null</code>,
	 *    если загружено изображение высокого разрешения),</li>
	 *  <li><code>highImage</code> - объект изображения высокого разрешения (<code>null</code>,
	 *    если загружено изображение низкого разрешения),</li>
	 *  <li><code>isHigh</code> - если <code>true</code>, то загружено изображение высокого разрешения.</li>
	 * </ul>
	 */
	var images = [];

	/**
	 * Ширина изображения в пикселях.
	 * @type {Number}
	 */
	var imageWidth;

	/**
	 * Флаг анимации.
	 */
	var isAnimate = false;

	/**
	 * Флаг перемещения изображения.
	 */
	var isMove = false;

	/**
	 * Последняя координата по горизонтали для слайдера при перемещении, в пикселях.
	 * @type {Number}
	 */
	var lastLeft;

	/**
	 * Последняя учтенная координата мыши по горизонтали при перемещении, в пикселях.
	 * @type {Number}
	 */
	var lastPageX;

	/**
	 * Максимальное время анимации, в секундах.
	 */
	var maxTime = 1.5;

	/**
	 * Шаги перемещений. Массив объектов, содержащих свойства:
	 * <ul>
	 *  <li><code>time</code> - момент времени, в милисекундах,</li>
	 *  <li><code>step</code> - шаг, в пикселях.</li>
	 * </ul>
	 *
	 * @type {Array[Object]}
	 */
	var moveSteps;

	/**
	 * Индекс из массива <code>images</code>, которому соответствует крайнее левое изображение в html.
	 * @type {Number}
	 */
	var startIndex;

	/**
	 * Стандартное время навигации, в секундах.
	 */
	var time = 1;


	$(function() {

		blocks = {
			root: $('#slider .wrap'),
			container: $('#slider .container'),
			arrows: $('#slider .arrow'),
			slogan: $('#slider .slogan')
		};

		initStructure();

		blocks.root.mousedown(startDnd).mousemove(dnd).mouseup(finishDnd).mouseout(finishDnd).hover(hoverOn, hoverOut);

		blocks.arrows.mousedown(function(e)
		{
			e.stopPropagation();

			if (!isAnimate) {
				fastMove($(this).hasClass('l'));
			}
		});

		$(window).resize(windowResize);

		$(window).load(windowResize);

		// Для IE создаем костыли, чтоб слоган фейдился реалистично.
		if ($.browser.msie) {
			blocks.slogan.append('<div class="crutch"><img alt="" /></div>');
			blocks.crutch = blocks.slogan.find('.crutch');
		}
	});

	/**
	 * Загружает в память изображения.
	 */
	function loadImages() {
		for (var i = 0; i < imagePaths.length; i++) {
			images[i] = {
				lowImage: null,
				highImage: null,
				isHigh: false
			};

			if (2 > i || i == imagePaths.length - 1 || null !== $.cookie(imagePaths[i].high)) {
				// Если изображение в кэше, видимое или около видимого, грузим в хорошем качестве.
				images[i].isHigh = true;
				images[i].highImage = new Image();
				images[i].highImage.src = imagePaths[i].high;
			} else {
				images[i].lowImage = new Image();
				images[i].lowImage.src = imagePaths[i].low;
			}
		}
	}

	/**
	 * Создает html-блоки для анимации.
	 */
	function initStructure() {
		// Создаем два дополнительных блока - предыдущее и следующее изображение.
		var htmlString = '<div class="image"><img alt="" /></div>';
		blocks.container.prepend(htmlString).append(htmlString).css('left', 0);
		blocks.images = blocks.container.find('.image');
		blocks.images.eq(0).find('img').attr('src', images[images.length - 1].highImage.src);
		blocks.images.eq(2).find('img').attr('src', images[1].highImage.src);
		startIndex = images.length - 1;
	}

	/**
	 * Перемещает блоки изображений для создания эффекта слайдера.
	 * @param {Boolean} before Заменяется изображение до или после.
	 */
	function updateStructure(before) {
		/** @type {Number} */
		var curLeft = lastLeft;
		/** @type {Number} */
		var curIndex;

		if (before) {
			curLeft += imageWidth;
			// Меняем startIndex на следующий.
			startIndex = startIndex == images.length - 1 ? 0 : startIndex + 1;

			// Вычисляем индекс последнего изображения.
			curIndex = startIndex + 2;

			if (curIndex == images.length) {
				curIndex = 0;
			} else if (curIndex == images.length + 1) {
				curIndex = 1;
			}


			// Убираем перемещаемый блок с экрана, меняем ему изображение,
			// перемещаем в конец и снова делаем видимым, изменяя при этом координату слайдера.

			/** @type {jQuery} */
			var curImageBlock = blocks.images.eq(0);
			curImageBlock.css('display', 'none');
			curImageBlock.find('img').attr('src', images[curIndex].isHigh ? images[curIndex].highImage.src : images[curIndex].lowImage.src);
			blocks.images.eq(2).after(curImageBlock);
			curImageBlock.css('display', '');
			blocks.container.css('left', curLeft);

			// Обновляем блок изображений.
			blocks.images = blocks.container.find('.image');
		} else {
			curLeft -= imageWidth;
			// Меняем startIndex на предыдущий.
			startIndex = 0 == startIndex ? images.length - 1 : startIndex - 1;
			curIndex = startIndex;


			// Убираем перемещаемый блок с экрана, изменяя при этом координату слайдера,
			// меняем ему изображение, перемещаем в начало и снова делаем видимым.

			/** @type {jQuery} */
			var curImageBlock = blocks.images.eq(2);
			blocks.container.css('left', curLeft);
			curImageBlock.css('display', 'none');
			curImageBlock.find('img').attr('src', images[curIndex].isHigh ? images[curIndex].highImage.src : images[curIndex].lowImage.src);
			blocks.images.eq(0).before(curImageBlock);
			curImageBlock.css('display', '');

			// Обновляем блок изображений.
			blocks.images = blocks.container.find('.image');
		}

		// Грузим изображение высокого разрешения, если оно еще не загружено.
		if (!images[curIndex].isHigh) {
			loadHighImage(curIndex);
		}
	}

	/**
	 * Событие при перемещении курсора мышки на слайдер.
	 */
	function hoverOn() {
		// Убираем слоган.
		if ($.browser.msie && !isAnimate) {
			// Для IE, когда нет анимации вместо фейда слогана, делаем обратный фейд
			// картинки, повторяющей изображение под слоганом.
			blocks.crutch.find('img').attr('src', blocks.images.eq(1).find('img').attr('src'));
			blocks.crutch.stop().css({
				opacity: 0,
				display: 'block'
			});
			blocks.crutch.animate({ opacity: 1 }, 300, function() {
				blocks.slogan.css('display', 'none');
				blocks.crutch.css({
					display: '',
					opacity: ''
				});
			});
		} else {
			// Для остальных браузерах и IE в случае анимации фейдим слоган
			// (баг IE почти не заметен).
			blocks.slogan.stop().animate({ opacity: 0 }, 300, function() {
				blocks.slogan.css({
					display: 'none',
					opacity: ''
				});
			});
		}

		// Показываем стрелки.
		blocks.arrows.stop().css({
			display: 'block',
			opacity: 0
		}).animate({ opacity: 0.6 }, 300, function() {
			blocks.arrows.css('opacity', '');
		});
	}

	/**
	 * Событие при перемещении курсора мышки на слайдер.
	 */
	function hoverOut() {
		// Показываем слоган.
		if ($.browser.msie && !isAnimate) {
			// Для IE, когда нет анимации вместо фейда слогана, делаем обратный фейд
			// картинки, повторяющей изображение под слоганом.
			blocks.crutch.find('img').attr('src', blocks.images.eq(1).find('img').attr('src'));
			blocks.crutch.css('display', 'block');
			blocks.slogan.css('display', '');
			blocks.crutch.stop().animate({ opacity: 0 }, 300, function() {
				blocks.crutch.css({
					display: '',
					opacity: ''
				});
			});
		} else {
			// Для остальных браузерах и IE в случае анимации фейдим слоган
			// (баг IE почти не заметен).
			blocks.slogan.stop().css({
				display: '',
				opacity: 0
			}).animate({ opacity: 0.6 }, 300, function() {
				blocks.slogan.css('opacity', '');
			});
		}

		// Убираем стрелки.
		blocks.arrows.stop().animate({ opacity: 0 }, 300, function() {
			blocks.arrows.css({
				display: '',
				opacity: ''
			});
		});
	}

	/**
	 * Стартует перемещение изображений.
	 * @param {Event} event Событие от <code>onmousedown</code>.
	 * @return {Boolean} Скажем нет событию.
	 */
	function startDnd(event) {
		// Обнуляем и инициализируем значения, которые будем использовать при перемещении.
		isMove = true;

		moveSteps = [];
		lastPageX = event.pageX;
		lastLeft = parseInt(blocks.container.css('left'));

		jTweener.removeTween(blocks.container);
		isAnimate = false;

		return false;
	}

	/**
	 * Перемещение изображений.
	 * @param {Event} event Событие от <code>onmousemove</code>.
	 * @return {Boolean} Скажем нет событию.
	 */
	function dnd(event) {
		if (!isMove) {
			return;
		}

		var timeCounter = new Date();
		var pageX = parseInt(event.pageX);
		var step = pageX - lastPageX;

		lastLeft += step;
		moveSteps.push({
			time: timeCounter.getTime(),
			step: step
		});
		lastPageX = pageX;

		blocks.container.css('left', lastLeft);

		return false;
	}

	/**
	 * Заканчивает перемещение, начинает анимацию.
	 * @param {Event} event Событие от <code>onmouseout</code> или <code>onmouseup</code>.
	 */
	function finishDnd(event) {
		if (!isMove) {
			return;
		}

		isMove = false;

		var timeCounter = new Date();

		// Вычисляем среднюю скорость
		var calcSteps = 5;
		/** @type {Number} */
		var start = calcSteps <= moveSteps.length ?  moveSteps.length - calcSteps : 0;
		var sum = 0;

		for (var i = start; i < moveSteps.length; i++) {
			sum += moveSteps[i].step;
		}

		/** @type {Number} */
		var averageSpeed = Math.abs(Math.round(sum / ((timeCounter.getTime() - moveSteps[start].time) / 1000)));


		var toLeft = 0;
		/** @type {Number} */
		var toTime = time;

		if (100 > averageSpeed) {
			if (lastLeft < -imageWidth / 2) {
				toLeft = -imageWidth;
			} else if (lastLeft > imageWidth / 2) {
				toLeft = imageWidth;
			}
		} else {
			toLeft = 0 < sum ? imageWidth : -imageWidth;
			toTime = maxTime;

			if (600 < averageSpeed) {
				// Анимируем по скорости передвижения.
				toTime = Math.abs(toLeft - lastLeft) / averageSpeed;
			}
		}

		isAnimate = true;

		if (0 != lastLeft && toLeft) {
			updateStructure(0 > lastLeft);
		}

		jTweener.addTween(blocks.container, {
			left: 0,
			time: toTime,
			transition: 'easeOutCubic',
			onComplete: function() {
				isAnimate = false;
			}
		});
	}

	/**
	 * Быстро перемещает изображения (при клике на стрелочках).
	 * @param {Boolean} Направление движения.
	 */
	function fastMove(before) {
		lastLeft = parseInt(blocks.container.css('left'));

		jTweener.removeTween(blocks.container);

		isAnimate = true;

		updateStructure(!before);
		jTweener.addTween(blocks.container, {
			left: 0,
			time: time,
			transition: 'easeInOutCubic',
			onComplete: function() {
				isAnimate = false;
			}
		});
	}

	/**
	 * Загружает изображение высокого разрешения.
	 * @param {Number} index Индекс массива <code>images</code>.
	 */
	function loadHighImage(index) {
		images[index].highImage = new Image();
		images[index].highImage.onload = function() {
			images[index].isHigh = true;

			// Меняем изображение у html-элемента.
			if (startIndex <= index && index <= startIndex + 2) {
				blocks.container.find('.image img').eq(index - startIndex).attr('src', images[index].highImage.src);
			}

			// Записываем в cookie, чтоб потом сразу загрузить изображение высокого разрешения.
			$.cookie(images[index].highImage.src, '1', { path: '/' });
			// Нам теперь не нужно изображение с низким разрешением.
			images[index].lowImage = null;
		};
		images[index].highImage.src = imagePaths[index].high;
	}

	/**
	 * Событие при ресайзе окна.
	 */
	function windowResize() {
		imageWidth = blocks.root.width();

		// Для IE нужно поменять размеры костылей.
		if ($.browser.msie) {
			blocks.crutch.find('img').css({
				height: blocks.images.eq(0).outerHeight(),
				width: imageWidth,
				left: blocks.root.offset().left - blocks.slogan.offset().left,
				top: blocks.root.offset().top - blocks.slogan.offset().top
			});
		}
	}

	/**
	 * Задает параметры для слайдера.
	 * @param {Object} inOptions - <code>images</code> - пути к изображениям. Массив объектов, каждый из которых имеет свойства:
	 * <ul>
	 *  <li><code>high</code> - путь к изображению высокого разрешения,</li>
	 *  <li><code>low</code> - путь к изображению низкого разрешения.</li>
	 * </ul>
	 */
	function setOptions(inOptions) {
		imagePaths = inOptions.images;

		// Загружаем изображения, не дожидаясь, пока произойдет загрузка всей страницы.
		loadImages();
	}

	return {
		setOptions: setOptions
	};
})();