/**
	Cобытия на контейнере:

	События отправляются на контейнер:

	CtBlockScroller:scrollStart - индикация начала скроллирования
	CtBlockScroller:scrollFinish - индикация окончания скроллирования
	CtBlockScroller:scroll - т.к. процесс скроллирования дискретный, то при каждом шаге скроллирования отправляется данное событие
	NOTE: в event.memo.position помещен объект с процентной позицией (0...1) скроллера по вертикали и горизонтали: {top: [0...1], left: [0...1]}

	События ловятся на контейнере:
	CtBlockScroller:scrollTo - по этому событию скроллер перемещается в позицию, указанную в event.memo.position. А также к позиции контента после расчета добавляется коррекция в пикселях, указанная в event.memo.correction


*/


/**
	Общий конфиг (со значениями по-умолчанию)
	Может переопределяться частными конфигами (для каждой отдельной галереи)
	Частный конфиг может переопределять любые параметры общего конфига
	Частный конфиг задается по идентификатору контейнера в нотации CamelCase. Т.е. для блока id="some-block" - ctBlockScrollerConfig.someBlock = { ... }
*/

var ctBlockScrollerConfig = {

	buttonHoldTimeout: 0.3, // время в секундах. Таймаут, после которого начинается скроллирование (по нажатию на кнопку скроллирования)
	buttonHoldScrollStep: 2, // шаг в пикселях для непрерывного скроллирования по нажатию на кнопку скроллирования
	buttonHoldScrollTimeout: 0.01, // время в секундах. Задержка перед каждым шагом непрерывного скролла

	mouseScrollingTimeout: 0.6, // время в секундах. Задержка перед активацией мышиного скролла

	scrollerJump: { // шаг скроллирования по клику на кнопку скроллирования [0.05 / '5%' / '100px']
		horizontal: '5%',
		vertical: '5%'
	},

	navigationEffectAppear: { // настройки эффекта появления скроллбаров
		to: 0.7,
		delay: 0.3,
		duration: 0.5
	},

	navigationEffectFade: { // настройки эффекта исчезания скроллбаров
		delay: 0,
		duration: 0.5
	},

	horizontal: { // настройки для горизонтального скролла
		manualScroll: false,
		isPersistent: false, // скрывать/не скрывать при наведении на контейнер
		resizeScroller: true, // изменять ли размер скроллера пропорционально скроллируемому контенту
		element: null, // ИД элемента, который будет скроллбаром
		back: null, // ИД элемента, который будет кнопкой назад. Если false - то вообще без кнопки. Если не указывать вообще, то кнопку скрипт попытается выудить по имени класса. Если и так не получится - то создаст свою
		forward: null // ИД элемента, который будет кнопкой вперед
	},

	vertical: { // настройки для вертикального скролла
		manualScroll: false,
		isPersistent: false,
		resizeScroller: true,
		element: null,
		back: null,
		forward: null
	}

};


/*
	Класс для кнопок навигации

	Выбрасывает три типа ивентов на свой element:
	CtBlockScroller_Navigation_Button:press - Кнопку нажали
	CtBlockScroller_Navigation_Button:release - Кнопку отпустили
	CtBlockScroller_Navigation_Button:click - Если кнопку отпустили до срабатывания задержки buttonHoldTimeout, кидается событие клика

*/
var CtBlockScroller_Navigation_Button = Class.create({

	element: null, // DOM-элемент кнопки
	timer: null,

	config: null,

	layout: null,

	initialize: function(htmlElement, config) {
		this.config = config;

		if (htmlElement) {
			this.element = $(htmlElement);
		} else {
			this.element = new Element('div', {className: 'ctBlockScroller-navigation-button'});
			this.element.setStyle({position: 'absolute', zIndex: 20, overflow: 'hidden'});
		}
		this.timer = null;
		this.layout = null;


		Event.observe(this.element, 'mouseover', this.mouseoverHandler.bindAsEventListener(this));
		Event.observe(this.element, 'mouseout', this.mouseoutHandler.bindAsEventListener(this));
		Event.observe(this.element, 'mousedown', this.mousedownHandler.bindAsEventListener(this));
		Event.observe(this.element, 'click', Event.stop);
	},


	/*
		Обработчик нажатия на кнопку
	*/
	mousedownHandler: function(event) {
		Event.stop(event);
		Event.observe(document, 'mouseup', this.mouseupHandler.bindAsEventListener(this));
		this.timer = setTimeout(this.mousedownDelayed.bind(this), this.config.buttonHoldTimeout * 1000);

		this.element.addClassName('CtBlockScroller-navigation-button-pressed');
	},


	/*
		Обработчик нажатия c задержкой buttonHoldTimeout
	*/
	mousedownDelayed: function() {
		this.timer = null;
		this.element.fire('CtBlockScroller_Navigation_Button:press');
	},


	/*
		Обработчик отпускания кнопки
	*/
	mouseupHandler: function(event) {

		// Если таймер еще не сработал, то кидаемся событием Клика
		if (this.timer) {
			clearTimeout(this.timer);
			this.timer = null;
			this.element.fire('CtBlockScroller_Navigation_Button:click');
		} else {
			this.element.fire('CtBlockScroller_Navigation_Button:release');
		}


		Event.stopObserving(document, 'mouseup');

		this.element.removeClassName('ctBlockScroller-navigation-button-pressed');
	},


	mouseoverHandler: function(event) {
		this.element.addClassName('ctBlockScroller-navigation-button-hovered');
	},


	mouseoutHandler: function(event) {
		this.element.removeClassName('ctBlockScroller-navigation-button-hovered');
	},


	/*
		Эффект скрытия
	*/
	hide: function() {
		var effectConfig = Object.clone(this.config.navigationEffectFade);
		effectConfig.sync = true;
		return new Effect.Fade(this.element, effectConfig);
	},


	/*
		Эффект отображения
	*/
	show: function() {
		var effectConfig = Object.clone(this.config.navigationEffectAppear);
		effectConfig.sync = true;
		return new Effect.Appear(this.element, effectConfig);
	}



});


/*
	Класс скроллера. Скроллер содержится в навигационной полосе,
	но визуально может быть вынесен за ее пределы

	Суперкласс, его расширяют CtBlockScroller_Navigation_Scroller_Vertical и CtBlockScroller_Navigation_Scroller_Horizontal,
	реализуя некоторую специфическую функциональность


	Выбрасывает три типа ивентов на свой element:
	CtBlockScroller_Navigation_Scroller:jump - выбрасывается при любом "резком" изменении положения скроллера (любые не "move" движения скроллера: клик на полосе, клик на кнопках, скролл мышью)
	CtBlockScroller_Navigation_Scroller:move - выбрасывается на каждом шаге при скроллировании навигационной клавишей или скроллером
	CtBlockScroller_Navigation_Scroller:moveFinish - выбрасывается по завершению скроллирования навигационной клавишей или скроллером
	CtBlockScroller_Navigation_Scroller:moveStart - выбрасывается с началом скроллирования навигационной клавишей или скроллером

*/
var CtBlockScroller_Navigation_Scroller = Class.create({

	element: null, // DOM-элемент скроллера
	offsetMin: 0, // минимальный отступ от начала навигационной полосы до скроллера (обычно - размер навигационной кнопки "назад")
	offsetMax: 0, // максимальный отступ от начала навигационной полосы до скроллера (общая длина полосы за вычетом размеров скроллера и навигационных клавишь)
	offsetLength: 0, // длина пути скроллера
	offsetJump: 0, // длина прыжка скроллера по клику на навигационную клавишу. Рассчитывается исходя из параметра scrollerJump

	offsetCurrent: 0, // текущий отступ скроллера от начала навигационной полосы
	position: 0, // текущий отступ скроллера от начала навигационной полосы в процентном отношении [0..1]

	config: null,

	layout: null,

	initialize: function(htmlElement, config) {
		this.config = config;

		this.offsetMin = 0;
		this.offsetMax = 0;
		this.offsetCurrent = 0;
		this.offsetLength = 0;
		this.offsetJump = 0;
		this.position = 0;

		if (htmlElement) {
			this.element = $(htmlElement);
		}
		if (!this.element) {
			this.element = new Element('div', {className: 'ctBlockScroller-navigation-scroller'});
		}

		this.element.setStyle({position: 'absolute', zIndex: 20});

		this.layout = null;

		Event.observe(this.element, 'mouseover', this.mouseoverHandler.bindAsEventListener(this));
		Event.observe(this.element, 'mouseout', this.mouseoutHandler.bindAsEventListener(this));
		Event.observe(this.element, 'mousedown', this.mousedownHandler.bindAsEventListener(this));

		Event.observe(this.element, 'click', Event.stop);
	},

	mouseoverHandler: function(event) {
		this.element.addClassName('ctBlockScroller-navigation-scroller-hovered');
	},


	mouseoutHandler: function(event) {
		this.element.removeClassName('ctBlockScroller-navigation-scroller-hovered');
	},


	/*
		Обработчик нажатия на скроллер
	*/
	mousedownHandler: function(event) {
		Event.stop(event);
		Event.observe(document, 'mousemove', this.documentMousemoveHandler.bindAsEventListener(this));
		Event.observe(document, 'mouseup', this.documentMouseupHandler.bindAsEventListener(this));
		this.element.addClassName('ctBlockScroller-navigation-scroller-pressed');

		this.element.fire('CtBlockScroller_Navigation_Scroller:moveStart', {position: this.position});
	},


	/*
		Обработчик отпускания скроллера
	*/
	documentMouseupHandler: function(event) {
		Event.stop(event);
		Event.stopObserving(document, 'mousemove');
		Event.stopObserving(document, 'mouseup');
		this.element.removeClassName('ctBlockScroller-navigation-scroller-pressed');

		this.element.fire('CtBlockScroller_Navigation_Scroller:moveFinish', {position: this.position});
	},


	/*
		Обработчик "протягивания" мышью скроллера.
	*/
	documentMousemoveHandler: function(event) {
		throw 'CtBlockScroller_Navigation_Scroller:documentMousemoveHandler must be redefined';
	},


	/*
		Рассчет текущей позиции скроллера
	*/
	preparePosition: function(newOffset) {
		if (newOffset > this.offsetMax) {
			newOffset = this.offsetMax;
		}
		if (newOffset < this.offsetMin) {
			newOffset = this.offsetMin;
		}

		this.offsetCurrent = newOffset;
		this.position = (this.offsetCurrent - this.offsetMin) / this.offsetLength;
	},


	/*
		Обновление позиции скроллера
	*/
	updatePosition: function() {
		throw 'CtBlockScroller_Navigation_Scroller:updatePosition must be redefined';
	},


	/*
		Движение назад при зажатой кнопке (влево или вверх)
	*/
	moveBack: function() {
		this.scroll(-this.config.buttonHoldScrollStep);
	},


	/*
		Движение вперед при зажатой кнопке (вправо или вниз)
	*/
	moveForward: function() {
		this.scroll(this.config.buttonHoldScrollStep);
	},


	/*
		Движение назад при зажатой кнопке (влево или вверх)
	*/
	jumpBack: function() {
		this.jumpToOffset(this.offsetCurrent - this.offsetJump);
	},


	/*
		Движение вперед по клику на кнопку (вправо или вниз)
	*/
	jumpForward: function() {
		this.jumpToOffset(this.offsetCurrent + this.offsetJump);
	},


	/*
		Реализация move
	*/
	scroll: function(delta) {

		if ((delta < 0 && this.offsetCurrent <= this.offsetMin) ||
			(delta > 0 && this.offsetCurrent >= this.offsetMax)
		) {
			return;
		}

		this.mouseStart += delta;

		var newOffset = this.offsetCurrent + delta;
		this.moveToOffset(newOffset);

//		if (Math.abs(delta) < 100) {
//			this.moveToOffset(newOffset);
//		} else {
//			this.jumpToOffset(newOffset);
//		}

	},


	/*
		Реализация jump
	*/
	scrollTo: function(position) {
		var newOffset = position * this.offsetLength + this.offsetMin;
		this.jumpToOffset(newOffset);
	},


	/*
		Движение на указанную позицию
	*/
	moveToOffset: function(newOffset) {
		this.preparePosition(newOffset);
		this.updatePosition();
		this.element.fire('CtBlockScroller_Navigation_Scroller:move', {position: this.position});
	},


	/*
		Прыжок на указанную позицию
	*/
	jumpToOffset: function(newOffset) {
		this.preparePosition(newOffset);
		this.updatePosition();
		this.element.fire('CtBlockScroller_Navigation_Scroller:jump', {position: this.position});
	}


});



/*
	Класс горизонтального скроллера
	Расширяет CtBlockScroller_Navigation_Scroller, реализуя некоторую специфическую для горизонтального скрола функциональность
*/
var CtBlockScroller_Navigation_Scroller_Horizontal = Class.create(CtBlockScroller_Navigation_Scroller, {


	mousedownHandler: function($super, event) {
		$super(event),
		this.mouseStart = Event.pointerX(event);
	},


	documentMousemoveHandler: function(event) {
		Event.stop(event);
		this.scroll(Event.pointerX(event) - this.mouseStart);
	},


	updatePosition: function() {
		this.element.setStyle({left: Math.round(this.offsetCurrent) + 'px'});
	}


});



/*
	Класс вертикального скроллера
	Расширяет CtBlockScroller_Navigation_Scroller, реализуя некоторую специфическую для вертикального скрола функциональность
*/
var CtBlockScroller_Navigation_Scroller_Vertical = Class.create(CtBlockScroller_Navigation_Scroller, {

	mousedownHandler: function($super, event) {
		$super(event),
		this.mouseStart = Event.pointerY(event);
	},


	documentMousemoveHandler: function(event) {
		Event.stop(event);
		this.scroll(Event.pointerY(event) - this.mouseStart);
	},

	updatePosition: function() {
		this.element.setStyle({top: Math.round(this.offsetCurrent) + 'px'});
	}


});



/*
	Класс навигационной панели. Обычно содержит кнопки скроллирования вперед/назад и скроллер

	Суперкласс, его расширяют CtBlockScroller_Navigation_Vertical и CtBlockScroller_Navigation_Horizontal,
	реализуя некоторую специфическую функциональность


	Выбрасывает три типа ивентов на свой element:
	CtBlockScroller_Navigation:jump - выбрасывается при любом "резком" изменении положения скроллера (любые не "move" движения скроллера: клик на панели, клик на кнопках, скролл мышью)
	CtBlockScroller_Navigation:scroll - выбрасывается на каждом шаге при скроллировании навигационной клавишей или скроллером
	CtBlockScroller_Navigation:scrollFinish - выбрасывается по завершению любого типа скроллирования
	CtBlockScroller_Navigation:scrollStart - выбрасывается с началом любого типа скроллирования

*/
var CtBlockScroller_Navigation = Class.create({

	element: null, // DOM-элемент навигационной панели

	back: null, // ссылка на объект CtBlockScroller_Navigation_Button
	forward: null, // ссылка на объект CtBlockScroller_Navigation_Button
	scroller: null, // ссылка на объект CtBlockScroller_Navigation_Scroller_Vertical или CtBlockScroller_Navigation_Scroller_Horizontal

	isPersistent: false, // скрывать или не скрывать навигационную панели (настройка isPersistent конфига)
	periodicalExecuter: null,
	timer: null,
	isWheelScrolling: false, // флаг отображающий, скроллируется ли сейчас мышью контейнер в соответствующем направлении (горизонтальном или вертикальном в соответствии с классом)

	config: null,

	layout: null,

	initialize: function() {
		// <--- тут происходит инициализация CtBlockScroller_Navigation_Vertical или CtBlockScroller_Navigation_Horizontal

		this.layout = null;

		var pos = Element.getStyle(this.element, 'position');
    	var style = {zIndex: 15};
		if (pos == 'static' || !pos) {
			style.position = 'absolute';
		}
		this.element.setStyle(style);

		// создаем кнопочку скроллирования назад
		if (this.config && this.config.back) {
			this.back = new CtBlockScroller_Navigation_Button(this.config.back, this.config);
		} else if (this.config && this.config.back === false) {
			this.back = null;
		} else {
			this.back = new CtBlockScroller_Navigation_Button(this.element.down('div.ctBlockScroller-navigation-button-back'), this.config);
		}

		if (this.back) {
			this.back.element.addClassName('ctBlockScroller-navigation-button-back');

			if (!this.back.element.up(0)) {
				this.element.insert(this.back.element);
			}

			Event.observe(this.back.element, 'CtBlockScroller_Navigation_Button:click', this.backClickHandler.bindAsEventListener(this));
			Event.observe(this.back.element, 'CtBlockScroller_Navigation_Button:press', this.backPressHandler.bindAsEventListener(this));
			Event.observe(this.back.element, 'CtBlockScroller_Navigation_Button:release', this.backReleaseHandler.bindAsEventListener(this));
		}


		// создаем кнопочку скроллирования вперед
		if (this.config && this.config.forward) {
			this.forward = new CtBlockScroller_Navigation_Button(this.config.forward, this.config);
		} else if (this.config && this.config.forward === false) {
			this.forward = null;
		} else {
			this.forward = new CtBlockScroller_Navigation_Button(this.element.down('div.ctBlockScroller-navigation-button-forward'), this.config);
		}

		if (this.forward) {
			this.forward.element.addClassName('ctBlockScroller-navigation-button-forward');
			if (!this.forward.element.up(0)) {
				this.element.insert(this.forward.element);
			}

			Event.observe(this.forward.element, 'CtBlockScroller_Navigation_Button:click', this.forwardClickHandler.bindAsEventListener(this));
			Event.observe(this.forward.element, 'CtBlockScroller_Navigation_Button:press', this.forwardPressHandler.bindAsEventListener(this));
			Event.observe(this.forward.element, 'CtBlockScroller_Navigation_Button:release', this.forwardReleaseHandler.bindAsEventListener(this));
		}

		if (!this.scroller.element.up(0)) {
			this.element.insert(this.scroller.element);

		}

		Event.observe(this.element, 'click', this.clickHandler.bindAsEventListener(this));

		Event.observe(this.scroller.element, 'CtBlockScroller_Navigation_Scroller:jump', this.scrollerJumpHandler.bindAsEventListener(this));
		Event.observe(this.scroller.element, 'CtBlockScroller_Navigation_Scroller:move', this.scrollerMoveHandler.bindAsEventListener(this));
		Event.observe(this.scroller.element, 'CtBlockScroller_Navigation_Scroller:moveStart', this.scrollerMoveStartHandler.bindAsEventListener(this));
		Event.observe(this.scroller.element, 'CtBlockScroller_Navigation_Scroller:moveFinish', this.scrollerMoveFinishHandler.bindAsEventListener(this));

		this.periodicalExecuter = null;
		this.timer = null;
		this.isWheelScrolling = false;
		this.isWheelScrollingPossible = false;

		this.isPersistent = (this.config && this.config.isPersistent) ? this.config.isPersistent : false;
	},


	/*
		Эффект скрытия панели
	*/
	hide: function() {
		var effectConfig = Object.clone(this.config.navigationEffectFade);
		effectConfig.sync = true;
		return new Effect.Fade(this.element, effectConfig);
	},


	/*
		Эффект отображения панели
	*/
	show: function() {
		var effectConfig = Object.clone(this.config.navigationEffectAppear);
		effectConfig.sync = true;
		return new Effect.Appear(this.element, effectConfig);
	},


	/*
		Метод необходим потому, что мышиный скрол работает дискретно.
		А интерфейсно хочется видеть, что скрол стартанул когда начинаешь
		крутить колесо и завершился, когда перестал крутить.
		Хотя технически при этом событие скроллирования постоянно вызывается

		Для реализации этого механизма добавлена задержка timer и дополнительный флаг isWheelScrolling,
		сбрасываемый по этой задержке.
		Задержка и флаг устанавливаются по каждому событию скроллирования.
		Сбрасываются по срабатыванию таймера.
	*/
	prepareMousewheelEvents: function() {
		if (!this.timer) {
			this.timer = setTimeout(this.mousewheelDelay.bind(this), 500);

			if (!this.isWheelScrolling) {
				this.isWheelScrolling = true;
				this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
			}

		} else if (this.isWheelScrolling) {
			clearTimeout(this.timer);
			this.timer = setTimeout(this.mousewheelDelay.bind(this), 500);
		} else {
			clearTimeout(this.timer);
			this.timer = null;
		}
	},


	/*
		Сброс таймера и флага скроллирования по таймеру
	*/
	mousewheelDelay: function() {
		this.timer = null;
		this.isWheelScrolling = false;
	},


	/*
		Обработка мышиного скролла для мозиллы
	*/
	mousewheelMozillaHandler: function(event) {
		Event.stop(event);

		this.prepareMousewheelEvents();

		if (event.detail > 0) {
			this.scroller.jumpForward();
		} else {
			this.scroller.jumpBack();
		}
	},


	/*
		Обработка мышиного скролла для всех браузеров кроме мозиллы
	*/
	mousewheelOtherHandler: function(event) {
		Event.stop(event);

		this.prepareMousewheelEvents();

		if (event.wheelDelta < 0) {
			this.scroller.jumpForward();
		} else {
			this.scroller.jumpBack();
		}
	},


	/*
		Обработка клика по панели (перемещает скроллер в точку нажатия)
	*/
	clickHandler: function(event) {
		throw 'CtBlockScroller_Navigation:clickHandler must be redefined';
	},


	/*
		Обработка клика по кнопке "назад"
	*/
	backClickHandler: function(event) {
		Event.stop(event);

		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
		this.scroller.jumpBack();
	},


	/*
		Обработка зажатой кнопки "назад"
	*/
	backPressHandler: function(event) {
		Event.stop(event);

		this.periodicalExecuter = new PeriodicalExecuter(this.scroller.moveBack.bind(this.scroller), this.config.buttonHoldScrollTimeout);
		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
	},


	/*
		Обработка отпущенной кнопки "назад"
	*/
	backReleaseHandler: function(event) {
		Event.stop(event);

		if (this.periodicalExecuter) {
			this.periodicalExecuter.stop();
			this.periodicalExecuter = null;
			this.element.fire('CtBlockScroller_Navigation:scrollFinish', {position: this.scroller.position});
		}
	},


	/*
		Обработка клика по кнопке "вперед"
	*/
	forwardClickHandler: function(event) {
		Event.stop(event);

		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
		this.scroller.jumpForward();
	},


	/*
		Обработка зажатой кнопки "вперед"
	*/
	forwardPressHandler: function(event) {
		Event.stop(event);

		this.periodicalExecuter = new PeriodicalExecuter(this.scroller.moveForward.bind(this.scroller), this.config.buttonHoldScrollTimeout);
		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
	},


	/*
		Обработка отпущенной кнопки "вперед"
	*/
	forwardReleaseHandler: function(event) {
		Event.stop(event);

		if (this.periodicalExecuter) {
			this.periodicalExecuter.stop();
			this.periodicalExecuter = null;
			this.element.fire('CtBlockScroller_Navigation:scrollFinish', {position: this.scroller.position});
		}
	},


	/*
		Обновление параметров скроллера. Метод вызывается из Gallery_Element после создания навигационных панелей
	*/
	updateScroller: function() {
		throw 'CtBlockScroller_Navigation:updateScroller must be redefined';
	},


	/*
		Обработчик начала движения скроллера
	*/
	scrollerMoveStartHandler: function(event) {
		Event.stop(event);
		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
	},


	/*
		Обработчик завершения движения скроллера
	*/
	scrollerMoveFinishHandler: function(event) {
		Event.stop(event);
		this.element.fire('CtBlockScroller_Navigation:scrollFinish', {position: this.scroller.position});
	},


	/*
		Обработчик каждого шага скроллера
	*/
	scrollerMoveHandler: function(event) {
		Event.stop(event);
		this.element.fire('CtBlockScroller_Navigation:scroll', {position: this.scroller.position});
	},


	/*
		Обработчик прыжка скроллера
	*/
	scrollerJumpHandler: function(event) {
		Event.stop(event);
		this.element.fire('CtBlockScroller_Navigation:jump', {position: this.scroller.position});
	},


	scrollTo: function(position) {
		this.scroller.scrollTo(position);
	}


});


/*
	Класс горизонтального скроллбара
	Расширяет CtBlockScroller_Navigation, реализуя некоторую специфическую для горизонтального скроллбара функциональность
*/
var CtBlockScroller_Navigation_Horizontal = Class.create(CtBlockScroller_Navigation, {


	initialize: function($super, config) {
//		this.config = config;
//
//		if (this.config.element) {
//			this.element = $(this.config.element);
//		} else {
//			this.element = new Element('div', {className: 'ctBlockScroller-navigation ctBlockScroller-navigation-horizontal'});
//		}
//		if (this.config.scroller) {
//			this.scroller = new CtBlockScroller_Navigation_Scroller_Horizontal(this.config.scroller, this.config);
//		} else {
//			this.scroller = new CtBlockScroller_Navigation_Scroller_Horizontal(this.element.down('div.ctBlockScroller-navigation-scroller'), this.config);
//		}
//
//		$super();
	},


	updateScroller: function(containerSize, wrapperSize) {


//		var backWidth = (this.back && !this.config.back) ? this.back.element.getWidth() : 0;
//		var forwardWidth = (this.forward && !this.config.forward) ? this.forward.element.getWidth() : 0;
//
//		var scrollerWidth = 0;
//		if (this.config && this.config.resizeScroller) {
//			var scrollerWidthMax = this.scroller.element.up(0).getWidth() - backWidth - forwardWidth;
//			var scrollerWidthMin = this.scroller.element.getWidth();
//
//			scrollerWidth = Math.round(containerSize / wrapperSize * scrollerWidthMax);
//			if (scrollerWidth < scrollerWidthMin) {
//				scrollerWidth = scrollerWidthMin;
//			}
//		} else {
//			scrollerWidth = this.scroller.element.getWidth();
//		}
//		this.scroller.offsetMin = backWidth;
//		this.scroller.offsetMax = this.scroller.element.up(0).getWidth() - forwardWidth - scrollerWidth;
//		this.scroller.offsetLength = this.scroller.offsetMax - this.scroller.offsetMin;
//		this.scroller.offsetCurrent = this.scroller.offsetMin;
//
//		var offsetJump = this.config.scrollerJump.horizontal;
//		if (typeof(offsetJump) == 'number') {
//			this.scroller.offsetJump = this.scroller.offsetLength * offsetJump;
//		} else if (offsetJump.search('px') != -1) {
//			this.scroller.offsetJump = (offsetJump.replace('px', '') * 1) / (wrapperSize - containerSize) * this.scroller.offsetLength;
//		} else if (offsetJump.search('%') != -1) {
//			this.scroller.offsetJump = this.scroller.offsetLength * (offsetJump.replace('%', '') * 0.01);
//		}
//
//		this.scroller.element.setStyle({width: scrollerWidth + 'px', left: Math.round(this.scroller.offsetCurrent) + 'px'});
	},



	clickHandler: function(event) {
		Event.stop(event);

//		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});
//
//		var elementWidth = this.element.getWidth();
//		var backWidth = (this.back && !this.config.back) ? this.back.element.getWidth() : 0;
//		var forwardWidth = (this.forward && !this.config.forward) ? this.forward.element.getWidth() : 0;
//
//		var position = (Event.pointerX(event) - this.element.cumulativeOffsetFixed().left - backWidth) / (elementWidth - backWidth - forwardWidth);
//		this.scroller.scrollTo(position);
	}

});


/*
	Класс вертикального скроллбара
	Расширяет CtBlockScroller_Navigation, реализуя некоторую специфическую для вертикального скроллбара функциональность
*/
var CtBlockScroller_Navigation_Vertical = Class.create(CtBlockScroller_Navigation, {



	initialize: function($super, config) {
		this.config = config;

		if (this.config.element) {
			this.element = $(this.config.element);
		} else {
			this.element = new Element('div', {className: 'ctBlockScroller-navigation ctBlockScroller-navigation-vertical'});
		}

		if (this.config.scroller) {
			this.scroller = new CtBlockScroller_Navigation_Scroller_Vertical(this.config.scroller, this.config);
		} else {
			this.scroller = new CtBlockScroller_Navigation_Scroller_Vertical(this.element.down('div.ctBlockScroller-navigation-scroller'), this.config);
		}

		$super();
	},


	updateScroller: function(containerSize, wrapperSize) {
		this.layout = new Element.Layout(this.element);

		var backHeight = 0;
		if (this.back) {
			this.back.element.setStyle({
				top: this.layout.get('padding-top') + 'px',
				right: this.layout.get('padding-right') + 'px'
			});
			this.back.layout = new Element.Layout(this.back.element);
			backHeight = this.back.layout.get('margin-box-height') + this.back.layout.get('top');
		}
		var forwardHeight = 0;
		if (this.forward) {
			this.forward.element.setStyle({
				bottom: this.layout.get('padding-bottom') + 'px',
				right: this.layout.get('padding-right') + 'px'
			});
			this.forward.layout = new Element.Layout(this.forward.element);
			forwardHeight = this.forward.layout.get('margin-box-height') + this.forward.layout.get('bottom');
		}


		this.scroller.layout = new Element.Layout(this.scroller.element);
		if (this.config.resizeScroller) {
			var scrollerHeightMax = this.layout.get('height') - backHeight - forwardHeight;
			var scrollerHeightMin = this.scroller.layout.get('height');

			var scrollerHeight = Math.round(containerSize / wrapperSize * scrollerHeightMax);
			if (scrollerHeight < scrollerHeightMin) {
				scrollerHeight = scrollerHeightMin;
			}
			this.scroller.element.setStyle({height: scrollerHeight + 'px'});
			this.scroller.layout = new Element.Layout(this.scroller.element);
		}

		this.scroller.offsetMin = backHeight;
		this.scroller.offsetMax = this.layout.get('height') - forwardHeight - scrollerHeight - (this.scroller.layout.get('margin-box-height') - this.scroller.layout.get('border-box-height'));
		this.scroller.offsetLength = this.scroller.offsetMax - this.scroller.offsetMin;
		this.scroller.offsetCurrent = this.scroller.offsetMin;

		var offsetJump = this.config.scrollerJump.vertical;
		if (typeof(offsetJump) == 'number') {
			this.scroller.offsetJump = this.scroller.offsetLength * offsetJump;
		} else if (offsetJump.search('px') != -1) {
			this.scroller.offsetJump = (offsetJump.replace('px', '') * 1) / (wrapperSize - containerSize) * this.scroller.offsetLength;
		} else if (offsetJump.search('%') != -1) {
			this.scroller.offsetJump = this.scroller.offsetLength * (offsetJump.replace('%', '') * 0.01);
		}

		this.scroller.element.setStyle({top: Math.round(this.scroller.offsetCurrent) + 'px'});
	},

	clickHandler: function(event) {
		Event.stop(event);
		this.element.fire('CtBlockScroller_Navigation:scrollStart', {position: this.scroller.position});

		var elementHeight = this.layout.get('height');
		var backHeight = this.back ? this.back.layout.get('margin-box-height') + this.back.layout.get('top') : 0;
		var forwardHeight = this.forward ? this.forward.layout.get('margin-box-height') + this.forward.layout.get('bottom') : 0;

		var position = (Event.pointerY(event) - this.element.cumulativeOffsetFixed().top - backHeight) / (elementHeight - backHeight - forwardHeight);
		this.scroller.scrollTo(position);
	}


});





/*
	Класс блокскроллера. Создается динамически фабрикой CtBlockScroller
*/
var CtBlockScroller_Element = Class.create({

	element: null, // DOM-элемент, контейнер, скроллируемый блок
	scrollType: null, // тип скролла: вертикальный, горизонтальный, оба варианта ['v' / 'h' / 'b']
	wrapper: null, // DOM-элемент, создается динамически. В него переносится весь контент из контейнера. Скроллирование происходит путем движения этого враппера с контентом внутри зафиксированного контейнера
	horizontal: null, // ссылка на объект CtBlockScroller_Navigation_Horizontal, если есть
	vertical: null, // ссылка на объект CtBlockScroller_Navigation_Vertical, если есть
	id: null, // ID DOM-элемента/контейнера

	timer: null,

	config: null,

	layout: null,
	wrapperLayout: null,



	initialize: function(htmlElement, config) {
		this.config = config;
		this.element = $(htmlElement);
		this.element.setStyle({position: 'relative', overflow: 'hidden'});
		this.id = this.element.identify();
		this.layout = new Element.Layout(this.element);

		this.scrollType = null;
		this.wrapper = null;
		this.horizontal = null;
		this.vertical = null;
		this.timer = null;


		if (this.element.hasClassName('ctBlockScroller-horizontal')) {
			this.scrollType = 'h';
		} else if (this.element.hasClassName('ctBlockScroller-vertical')) {
			this.scrollType = 'v';
		} else {
			this.scrollType = 'b';
		}



		this.wrapper = this.element.down('.ctBlockScroller-wrapper');
		if (this.wrapper == null) {
			this.wrapper = new Element('div', {className: 'ctBlockScroller-wrapper'});
			this.element.insert(this.wrapper);
			this.element.immediateDescendants().each(function(descendant) {
				this.wrapper.insert(descendant);
			}.bind(this));
		}
		this.wrapperLayout = new Element.Layout(this.wrapper);




		switch (this.scrollType) {
			case 'v':
				if (this.wrapperLayout.get('margin-box-height') <= this.layout.get('height')) {
					this.scrollType = null;
				}
				break;

			case 'h':
				if (this.wrapperLayout.get('margin-box-width') <= this.layout.get('width')) {
					this.scrollType = null;
				}
				break;

			case 'b':
				if (this.wrapperLayout.get('margin-box-height') <= this.layout.get('height') && this.wrapperLayout.get('margin-box-width') <= this.layout.get('width')) {
					this.scrollType = null;
				} else if (this.wrapperLayout.get('margin-box-height') <= this.layout.get('height')) {
					this.scrollType = 'h';
				} else if (this.wrapperLayout.get('margin-box-width') <= this.layout.get('width')) {
					this.scrollType = 'v';
				}
				break;

			default:
				break;
		}


		// Если скроллбар не создается динамически (указан в конфиге), но скролла такого типа быть не должно, то добавляется соответствующий класс
		if ((this.scrollType == 'v' || !this.scrollType) && this.config && this.config.horizontal && this.config.horizontal.element) {
			var el = $(this.config.horizontal.element);
			if (el) {
				el.addClassName('ctBlockScroller-navigation-disabled');
			}
		}
		if ((this.scrollType == 'h' || !this.scrollType) && this.config && this.config.vertical && this.config.vertical.element) {
			var el = $(this.config.vertical.element);
			if (el) {
				el.addClassName('ctBlockScroller-navigation-disabled');
			}
		}

		// Если скроллы не нужны (контент меньше контейнера), то не создавая скроллбаров просто выходим.
		if (!this.scrollType) {
			return;
		}

		this.createNavigation();

		Event.observe(this.element, 'CtBlockScroller:scrollTo', this.scrollTo.bindAsEventListener(this));
	},


	/*
		Метод реализует скроллирование контента по внешнему событию CtBlockScroller:scrollTo
		Скроллер перемещается в позицию, указанную в event.memo.position.
		Также к позиции контента после расчета добавляется коррекция в пикселях, указанная в event.memo.correction
	*/
	scrollTo: function(event) {

		var verticalPosition = this.vertical ? this.vertical.scroller.position : 0;
		var horizontalPosition = this.horizontal ? this.horizontal.scroller.position : 0;
		this.element.fire('CtBlockScroller:scrollStart', {position: {top: verticalPosition, left: horizontalPosition}});

		if (this.vertical) {
 			var offsetV = this.wrapperLayout.get('margin-box-height') * event.memo.position.top + event.memo.correction.top;

			var maxOffsetV = (this.wrapperLayout.get('margin-box-height') - this.layout.get('height'));
			if (offsetV > maxOffsetV) {
				offsetV = maxOffsetV;
			} else if (offsetV < 0) {
				offsetV = 0;
			}

			this.vertical.scrollTo(offsetV / maxOffsetV);
		}
		if (this.horizontal) {
			var offsetH = this.wrapperLayout.get('margin-box-width') * event.memo.position.left + event.memo.correction.left;
			var maxOffsetH = (this.wrapperLayout.get('margin-box-width') - this.layout.get('width'));
			if (offsetH > maxOffsetH) {
				offsetH = maxOffsetH;
			} else if (offsetH < 0) {
				offsetH = 0;
			}
			this.horizontal.scrollTo(offsetH / maxOffsetH);
		}
	},


	/*
		Обработка движения скроллера, ретрансляция события наружу
	*/
	scrollRetranslate: function(event) {
		Event.stop(event);
		var verticalPosition = this.vertical ? this.vertical.scroller.position : 0;
		var horizontalPosition = this.horizontal ? this.horizontal.scroller.position : 0;

		var top = (this.wrapperLayout.get('margin-box-height') - this.layout.get('height')) * verticalPosition;
		var left = (this.wrapperLayout.get('margin-box-width') - this.layout.get('width')) * horizontalPosition;
		this.element.fire('CtBlockScroller:scroll', {position: {top: verticalPosition, left: horizontalPosition}});

		this.wrapper.setStyle({left: -Math.round(left) + 'px', top: -Math.round(top) + 'px'});

	},


	/*
		Обработка прыжка скроллера, ретрансляция события наружу
	*/
	jumpRetranslate: function(event) {
		Event.stop(event);
		Effect.Queues.get(this.id + '-smoothSlide').each(function(effect) { effect.cancel(); });

		var verticalPosition = this.vertical ? this.vertical.scroller.position : 0;
		var horizontalPosition = this.horizontal ? this.horizontal.scroller.position : 0;

		var top = (this.wrapperLayout.get('margin-box-height') - this.layout.get('height')) * verticalPosition;
		var left = (this.wrapperLayout.get('margin-box-width') - this.layout.get('width')) * horizontalPosition;

		new Effect.Move(this.wrapper, {
			x: -Math.round(left),
			y: -Math.round(top),
			duration: 0.5,

			mode: 'absolute',
			afterFinish: function() {
				this.element.fire('CtBlockScroller:scroll', {position: {top: verticalPosition, left: horizontalPosition}});
				this.element.fire('CtBlockScroller:scrollFinish', {position: {top: verticalPosition, left: horizontalPosition}});
			}.bind(this),
			queue: {scope: this.id + '-smoothSlide', position: 'end'}

		});

	},


	/*
		Обработка начала движения скроллера, ретрансляция события наружу
	*/
	scrollStartRetranslate: function(event) {
		Event.stop(event);

		var verticalPosition = this.vertical ? this.vertical.scroller.position : 0;
		var horizontalPosition = this.horizontal ? this.horizontal.scroller.position : 0;
		this.element.fire('CtBlockScroller:scrollStart', {position: {top: verticalPosition, left: horizontalPosition}});
	},


	/*
		Обработка окончания движения скроллера, ретрансляция события наружу
	*/
	scrollFinishRetranslate: function(event) {
		Event.stop(event);

		var verticalPosition = this.vertical ? this.vertical.scroller.position : 0;
		var horizontalPosition = this.horizontal ? this.horizontal.scroller.position : 0;
		this.element.fire('CtBlockScroller:scrollFinish', {position: {top: verticalPosition, left: horizontalPosition}});
	},


	/*
		Создание скроллбаров
	*/
	createNavigation: function() {

		var configSection = this.id.camelize();

		if (this.scrollType == 'v' || this.scrollType == 'b') {
			var verticalConfig = Object.extend(this.config, this.config.vertical ? this.config.vertical : {});
			this.vertical = new CtBlockScroller_Navigation_Vertical(verticalConfig);
			if (!this.vertical.element.up(0)) {
				this.element.insert(this.vertical.element);
			}
			Event.observe(this.vertical.element, 'CtBlockScroller_Navigation:scrollStart', this.scrollStartRetranslate.bindAsEventListener(this));
			Event.observe(this.vertical.element, 'CtBlockScroller_Navigation:scrollFinish', this.scrollFinishRetranslate.bindAsEventListener(this));
			Event.observe(this.vertical.element, 'CtBlockScroller_Navigation:scroll', this.scrollRetranslate.bindAsEventListener(this));
			Event.observe(this.vertical.element, 'CtBlockScroller_Navigation:jump', this.jumpRetranslate.bindAsEventListener(this));
		}

		if (this.scrollType == 'h' || this.scrollType == 'b') {
			var horizontalConfig = Object.extend(this.config, this.config.horizontal ? this.config.horizontal : {});
			this.horizontal = new CtBlockScroller_Navigation_Horizontal(horizontalConfig);
			if (!this.horizontal.element.up(0)) {
				this.element.insert(this.horizontal.element);
			}
			Event.observe(this.horizontal.element, 'CtBlockScroller_Navigation:scrollStart', this.scrollStartRetranslate.bindAsEventListener(this));
			Event.observe(this.horizontal.element, 'CtBlockScroller_Navigation:scrollFinish', this.scrollFinishRetranslate.bindAsEventListener(this));
			Event.observe(this.horizontal.element, 'CtBlockScroller_Navigation:scroll', this.scrollRetranslate.bindAsEventListener(this));
			Event.observe(this.horizontal.element, 'CtBlockScroller_Navigation:jump', this.jumpRetranslate.bindAsEventListener(this));
		}

		// Рассчеты всех размеров делаем только после того как скроллбары созданы и находятся в DOM-дереве документа
		if (this.scrollType == 'v') {
			this.vertical.layout = new Element.Layout(this.vertical.element);
			this.vertical.element.setStyle({height: (this.layout.get('height') - this.vertical.layout.get('margin-box-height')) + 'px'});
			this.vertical.updateScroller(this.layout.get('height'), this.wrapperLayout.get('margin-box-height'));
		} else if (this.scrollType == 'h') {
			this.vertical.layout = new Element.Layout(this.vertical.element);
			this.horizontal.element.setStyle({width: (this.layout.get('width') - this.horizontal.layout.get('margin-box-width')) + 'px'});
			this.horizontal.updateScroller(this.layout.get('width'), this.wrapperLayout.get('margin-box-width'));
		} else if (this.scrollType == 'b') {
			this.vertical.layout = new Element.Layout(this.vertical.element);
			this.horizontal.layout = new Element.Layout(this.horizontal.element);

			this.vertical.element.setStyle({height: (this.layout.get('height') - this.horizontal.layout.get('height') - this.vertical.layout.get('margin-box-height')) + 'px'});
			this.horizontal.element.setStyle({width: (this.layout.get('width') - this.vertical.layout.get('width') - this.horizontal.layout.get('margin-box-width')) + 'px'});

			this.vertical.updateScroller(this.layout.get('height'), this.wrapperLayout.get('margin-box-height'));
			this.horizontal.updateScroller(this.layout.get('height'), this.wrapperLayout.get('margin-box-height'));
		}

		// Только после рассчетов всех размеров можем скрыть скроллбары, если необходимо
		if ((this.scrollType == 'v' || this.scrollType == 'b') && !this.vertical.isPersistent) {
			this.vertical.element.hide();
		}
		if ((this.scrollType == 'h' || this.scrollType == 'b') && !this.horizontal.isPersistent) {
			this.horizontal.element.hide();
		}

		Event.observe(this.element, 'mouseover', this.mouseoverHandler.bindAsEventListener(this));
		Event.observe(this.element, 'mouseout', this.mouseoutHandler.bindAsEventListener(this));
	},


	/*
		Начинаем слушать мышиный скролл (только после срабатывания задержки mouseScrollingTimeout)
		Если есть оба скрола или только вертикальный, то скроллируем мышью по-вертикали.
		Если же есть только горизонтальный скролл, то скроллируем по-горизонтали.
		Соответственно отменяем стандартный скролл документа.
	*/
	startMouseListening: function() {
		if (this.vertical && !this.config.vertical.manualScroll) {
			Event.observe(this.element, 'DOMMouseScroll', this.vertical.mousewheelMozillaHandler.bindAsEventListener(this.vertical));
			Event.observe(this.element, 'mousewheel', this.vertical.mousewheelOtherHandler.bindAsEventListener(this.vertical));
		} else if (this.horizontal && !this.config.horizontal.manualScroll) {
			Event.observe(this.element, 'DOMMouseScroll', this.horizontal.mousewheelMozillaHandler.bindAsEventListener(this.horizontal));
			Event.observe(this.element, 'mousewheel', this.horizontal.mousewheelOtherHandler.bindAsEventListener(this.horizontal));
		}
	},


	/*
		Прекращаем слушать мышиный скролл
	*/
	stopMouseListening: function() {
		Event.stopObserving(this.element, 'DOMMouseScroll');
		Event.stopObserving(this.element, 'mousewheel');
	},


	/*
		Если есть необходимость, то отображаем скроллбары
		Также стартуем таймер mouseScrollingTimeout, после которого уже можно скроллировать мышью
	*/
	mouseoverHandler: function(event) {
		if (Event.findElement(event) != this.element && !Event.findElement(event).descendantOf(this.element)) {
			return;
		}

		Effect.Queues.get(this.id).each(function(effect) { effect.cancel(); });
		var effects = [];
		if ((this.scrollType == 'v' || this.scrollType == 'b') && !this.vertical.isPersistent && !this.config.vertical.manualScroll) {

			if (this.config.vertical.back && this.config.vertical.forward) {
				effects.push(this.vertical.back.show());
				effects.push(this.vertical.forward.show());
			}

			effects.push(this.vertical.show());
		}
		if ((this.scrollType == 'h' || this.scrollType == 'b') && !this.horizontal.isPersistent && !this.config.horizontal.manualScroll) {
			if (this.config.horizontal.back && this.config.horizontal.forward) {
				effects.push(this.horizontal.back.show());
				effects.push(this.horizontal.forward.show());
			}

			effects.push(this.horizontal.show());
		}
		if (effects.length > 0) {
			new Effect.Parallel(effects, {
				delay: this.config.navigationEffectAppear.delay,
				duration: this.config.navigationEffectAppear.duration,
				queue: {scope: this.id, position: 'end', limit: 1}
			});
		}

		this.timer = setTimeout(this.startMouseListening.bind(this), this.config.mouseScrollingTimeout * 1000);
	},


	/*
		Скрываем скроллбары, если необходимо. Сбрасываем таймер mouseScrollingTimeout
	*/
	mouseoutHandler: function(event) {
		if (Event.findElement(event) == this.element || Event.findElement(event).descendantOf(this.element)) {
			return;
		}

		clearTimeout(this.timer);
		this.stopMouseListening();
		Effect.Queues.get(this.id).each(function(effect) { effect.cancel(); });


		var effects = [];
		if ((this.scrollType == 'v' || this.scrollType == 'b') && !this.vertical.isPersistent && !this.vertical.manualScroll) {
			if (this.config.vertical.back && this.config.vertical.forward && event.relatedTarget != this.vertical.back.element && !Element.descendantOf(event.relatedTarget, this.vertical.back.element) && event.relatedTarget != this.vertical.forward.element && !Element.descendantOf(event.relatedTarget, this.vertical.forward.element)) {
				effects.push(this.vertical.back.hide());
				effects.push(this.vertical.forward.hide());
			}

			effects.push(this.vertical.hide());
		}
		if ((this.scrollType == 'h' || this.scrollType == 'b') && !this.horizontal.isPersistent && !this.horizontal.manualScroll) {
			if (this.config.horizontal.back && this.config.horizontal.forward && event.relatedTarget != this.horizontal.back.element && !Element.descendantOf(event.relatedTarget, this.horizontal.back.element) && event.relatedTarget != this.horizontal.forward.element && !Element.descendantOf(event.relatedTarget, this.horizontal.forward.element)) {
				effects.push(this.horizontal.back.hide());
				effects.push(this.horizontal.forward.hide());
			}

			effects.push(this.horizontal.hide());
		}
		if (effects.length > 0) {
			new Effect.Parallel(effects, {
				delay: this.config.navigationEffectFade.delay,
				duration: this.config.navigationEffectFade.duration,
				queue: {scope: this.id, position: 'end', limit: 1}
			});
		}
	}




});





/*
	Фабрика
	Помимо нумерованного массива блокСкроллеров, содержит хэш блокСкроллеров
	Доступ к каждому блокСкроллеру осуществляется по ID блокСкроллера внутри объекта фабрики.

	Например, для id="someContainer" доступ к этой галерее осуществляется через ссылку ctBlockScroller.someContainer или ctBlockScroller.elements[_index_].
	Однако извне _index_ неизвестен

*/
var CtBlockScroller = Class.create({

	elements: [], // нумерованный массив объектов блокСкроллеров

	initialize: function() {
		var elements = $$('.ctBlockScroller');

		for (var i = 0; i < elements.length; i++) {


			var id = elements[i].identify().camelize();
			var config = Object.clone(Object.extend(ctBlockScrollerConfig, (ctBlockScrollerConfig[id]) ? ctBlockScrollerConfig[id] : {}));
			var ctBlockScrollerElement = new CtBlockScroller_Element(elements[i], config);

			this[id] = ctBlockScrollerElement;
			this.elements.push(ctBlockScrollerElement);
		}

	}

});

CtPage.registerScript("CtBlockScroller");


