﻿/**
 * @fileOverview 특정 리스트의 일련의 아이템들을 부드러운 움직임으로 이동시켜 볼 수 있도록하는 컴포넌트 
 * @author hooriza, modified by senxation
 * @version 0.3.2
 */

nhn.Rolling = jindo.$Class({
	/** @lends nhn.Rolling */
	_oTransition : null,

	/**
	 * Rolling 컴포넌트를 생성한다.
	 * @constructs
	 * @requires nhn.Effect
	 * @requires nhn.Transition
	 * @param {String | HTMLElement} el 리스트를 감싸고 있는 엘리먼트의 id 혹은 엘리먼트 자체  
	 * @param {Object} oOptions 옵션객체
	 * @example
<xmp>
<div id="horz_wrap">
	<ul>
		<li>첫번째</li>
		<li>두번째</li>
		<li>세번째</li>
		<li>네번째</li>
		<li>다섯번째</li>
		<li>여섯번째</li>
		<li>일곱번째</li>
		<li>여덟번째</li>
		<li>아홉번째</li>
		<li>마지막</li>
	</ul>
</div>
<script>
	new nhn.Rolling($('horz_wrap'), {
		duration : 400, // (ms) nhn.Effect, nhn.Transtition 참고
		direction : 'horizontal', // || 'vertical'
		effect : nhn.Effect.bounce //nhn.Effect 참고
	}).attach({
		beforemove : function(e) {
			//e.element 어느 엘리먼트의 scrollLeft 가 바뀌는지
			//e.index 몇번째 항목으로 이동하는지
			//e.scroll 이동할 포지션
		}
	});
</script>
</xmp>
	 */
	$init : function(el, oOptions) {
		
		this._oTransition = new nhn.Transition().fps(50);

		this._el = jindo.$(el);
		this._elList = cssquery.test(this._el, 'ul, ol') ? this._el : cssquery.getSingle('> ul, > ol', el);
		
		this.option({
			duration : 800,
			direction : 'horizontal',
			effect : nhn.Effect.linear
		});
		
		this.option(oOptions || {});
		
		this._oKeys = this.option('direction') == 'horizontal' ? {
			offsetWidth : 'offsetWidth',
			marginLeft : 'marginLeft',
			marginRight : 'marginRight',
			clientWidth : 'clientWidth',
			scrollLeft : 'scrollLeft'
		} : {
			offsetWidth : 'offsetHeight',
			marginLeft : 'marginTop',
			marginRight : 'marginBottom',
			clientWidth : 'clientHeight',
			scrollLeft : 'scrollTop'
		};

		var self = this;
		setTimeout(function() { self.moveTo(0); }, 0);
		
	},
	
	/**
	 * nhn.Transition 컴포넌트의 인스턴스를 가져온다.
	 * @return {nhn.Transition}
	 */
	getTransition : function() {
		return this._oTransition;
	},
	
	/**
	 * 리스트의 아이템(LI, 즉 자식 엘리먼트)들을 구한다.
	 * @return {Array} LI 엘리먼트들의 배열
	 */
	getItems : function() {
		return cssquery('> li', this._elList);
	},
	
	_offsetSize : function(el) {
		var eEl = jindo.$Element(el);
		var oKeys = this._oKeys;
		var nMarginLeft = parseInt(eEl.css(oKeys.marginLeft)) || 0;
		var nMarginRight = parseInt(eEl.css(oKeys.marginRight)) || 0;
		return el[oKeys.offsetWidth] + nMarginLeft + nMarginRight;
	},
	
	/**
	 * 현재 표시되고있는 LI의 인덱스를 구한다.
	 * @return {Number} 현재 표시되고있는 LI의 인덱스
	 */
	getIndex : function() {
	
		var el = this._el;
		var oKeys = this._oKeys;
		
		var nScroll = el[oKeys.scrollLeft];

		var aItems = this.getItems();
		var nSize = 0;
		
		var n = 0;
		var nMinDistance = 99999999;

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

			var nDistance = Math.abs(nScroll - nSize);
			
			if (nDistance < nMinDistance) {
				nMinDistance = nDistance;
				n = i;
			}
			
			nSize += this._offsetSize(aItems[i]);
			
		}
		
		return n;
	
	},
	
	_move : function(n) {
		
		var el = this._el;
		var oKeys = this._oKeys;
		
		var aItems = this.getItems();
		var nPos = 0, nSize = 0;
		var self = this;
		
		if (n > 0) {

			for (var i = 0; i < n; i++) {
				nPos += this._offsetSize(aItems[i]);
			}
		} else {
			
			for (var i = aItems.length + n; i < aItems.length; i++)
				nPos -= this._offsetSize(aItems[i]);
			
		}
		
		for (var i = 0; i < aItems.length; i++)
			nSize += this._offsetSize(aItems[i]);
			
		if (nPos + el[oKeys.clientWidth] > nSize) {
			nPos = nSize - el[oKeys.clientWidth];
		}
			
		this._size = nSize;

		var oParam = {
			element : el, // 어느 엘리먼트의 scrollLeft 가 바뀌는지
			index : n, // 몇번째 항목으로 이동하는지
			
			scroll : nPos
		};
	
		this.fireEvent('beforemove', oParam);
		
		if (el[oKeys.scrollLeft] == oParam.scroll) return false;
		var oDest = {};
		oDest[oKeys.scrollLeft] = this.option('effect')(oParam.scroll);
		
		this._oTransition.start(this.option('duration'),
			oParam.element, oDest
		);
			
		return true;
		
	},
	
	/**
	 * n번째 아이템으로 이동한다.
	 * @param {Number} n
	 * @return {Boolean} 실제로 이동했는지 여부 
	 */
	moveTo : function(n) {
	
		var aItems = this.getItems();
		
		if (n < 0) {
			n = 0;
		} else if (n >= aItems.length) {
			n = aItems.length - 1;
		}
		if (!this._move(n)) return false;
		
		return true;
		
	},

	/**
	 * 뒤에서부터 n번째 아이템으로 이동한다.
	 * @param {Number} n
	 * @return {Boolean} 실제로 이동했는지 여부
	 */
	moveLastTo : function(n) {
		
		var aItems = this.getItems();
		return this.moveTo(aItems.length - 1 - n);
		
	},

	/**
	 * 현재 위치와 n만큼 떨어진 아이템으로 이동한다.
	 * @param {Number} n
	 * @return {Boolean} 실제로 이동했는지 여부
	 */
	moveBy : function(n) {
	
		var nIndex = this.getIndex();
		return this.moveTo(nIndex + n);
		
	},
	
	/**
	 * 리스트의 아이템들이 가려있는지 여부를 가져온다.
	 * @return {Boolean} 리스트의 아이템들이 가려있는지 여부
	 */
	isOverflowed : function() {
		
		var el = this._el;
		var oKeys = this._oKeys;
		
		var nSize = 0;
		var aItems = this.getItems();
		for (var i = 0; i < aItems.length; i++)
			nSize += this._offsetSize(aItems[i]);
			
		return nSize > el[oKeys.clientWidth];
		
	}

}).extend(nhn.Component);

