/*

スクロールボックスを動かしている部分

 * $.ui.touchscrollable
 *
 * version 0.1.0 (2010/07/24)
 * Copyright (c) 2010 Takeshi Takatsudo (takazudo[at]gmail.com)
 * MIT license
 *
=============================================================================
 depends on
-----------------------------------------------------------------------------
 * jQuery 1.4.2
 * jQuery UI 1.8.2
 * jQuery UI Widget 1.8.2
 *
 */
(function($){ // start $=jQuery encapsulation

	var EASING_EXTRASCROLL = 'cubic-bezier(0,0,0,1)';
	var EASING_RETURNOVER = 'ease-out';
	
	/**
	 * $.ui.touchscrollable
	 * outer.
	 */
	$.widget('ui.touchscrollable', {
		options:{
			selector_inner: '.widget-body',
			transitionDuration_over: 1000,
			transitionDuration_extraMove: 700,
			overEdgeSlowdownRate: 0.1,
			overScrollSlowDownRate: 0.5,
			extraScrollCancelDuration: 80,
			maxOverEdgeDistance: 20,
			xScrollable: false,
			yScrollable: true
		},
		_touchSupported: false,
		_cssTransitionsSupported: false,
		_startX: null,
		_startY: null,
		_currentX: null,
		_currentY: null,
		_lastTouchTime: null,
		_caliculator: null,
		_inner: null,
		_scrollbarX: null,
		_scrollbarY: null,
		_body: null, // $(body) for reuse
		_create: function(){
			this._body = $(document.body);
			this._caliculator = new SpeedCaliculator();
			this._detectTouchSupport();
			this._detectCssTransitionsSupport();
			this._prepareInner();
			this._prepareScrollbarX();
			this._prepareScrollbarY();
			this._eventify();
		},

		/* around initialize */

		_detectTouchSupport: function(){
			/*
				i dont think that this is not a good way but
				Chrome on PC says it supports touchstart even if there's no touch device
				so detecting agents via UA.
			*/
			var ua = navigator.userAgent;
			this._touchSupported = (
				ua.indexOf('iPad')>-1 ||
				ua.indexOf('iPhone')>-1
			);
			return this;
		},
		_detectCssTransitionsSupport: function(){
			this._cssTransitionsSupported = jQuery.browser.webkit;
			return this;
		},
		_prepareInner: function(){
			var fn = 'touchscrollable_inner';
			var element_container = this.element;
			var element_inner = $(this.options.selector_inner, element_container);
			var o = $.extend({}, this.options, {
				containerWidth: element_container.width(),
				containerHeight: element_container.height(),
				caliculator: this._caliculator
			});
			this._inner = element_inner[fn](o).data(fn);
			return this;
		},
		_prepareScrollbarX: function(){
			var outerHeight = this.element.height();
			var innerHeight = this._inner.getHeight();
			if(!this.options.xScrollable){
				return this;
			}
			if(this._scrollbarX){
				this._scrollbarX.update( outerHeight, innerHeight );
			}else{
				var fn = 'touchscrollable_scrollbarX';
				var e = $('.scrollbarX', this.element);
				if(!e.size()){
					return this;
				}
				var o = {
					containerOuterHeight: outerHeight,
					containerInnerHeight: innerHeight
				};
				this._scrollbarX = e[fn](o).data(fn);
				this._inner.attachScrollbarX(this._scrollbarX);
			}
			return this;
		},
		_prepareScrollbarY: function(){
			var outerHeight = this.element.height();
			var innerHeight = this._inner.getHeight();
			if(!this.options.yScrollable){
				return this;
			}
			if(this._scrollbarY){
				this._scrollbarY.update( outerHeight, innerHeight );
			}else{
				var fn = 'touchscrollable_scrollbarY';
				// *スクロールバーの要素を指定
				var e = $('.scrollbarY', this.element);
				if(!e.size()){
					return this;
				}
				var o = {
					containerOuterHeight: outerHeight,
					containerInnerHeight: innerHeight
				};
				this._scrollbarY = e[fn](o).data(fn);
				this._inner.attachScrollbarY(this._scrollbarY);
			}
			return this;
		},

		// *イベントを追加しているところ
		_eventify: function(){
			var e = this.element;
			var node = e.get(0);
			if(this._touchSupported){
				node.addEventListener('touchstart',  $.proxy(this._touchStartHandler, this), false);
				node.addEventListener('touchmove',   $.proxy(this._touchMoveHandler, this), false);
				node.addEventListener('touchend',    $.proxy(this._touchEndHandler, this), false);
			}else{
				e.bind('mousedown', $.proxy(this._dragStartHandler, this));
				e.bind('wheel', $.proxy(this._wheelHandler, this));
			}
			$(window).bind('resize',$.proxy(this._resizeHandler, this));
		},

		/* core */

		_setStartTouch: function(touch){
			this._caliculator.empty();
			this._startX = touch.pageX;
			this._startY = touch.pageY;
			this._caliculator.add(touch);
			this._lastTouchTime = null;
			return this;
		},
		_setCurrentTouch: function(touch){
			this._lastX = this._currentX;
			this._lastY = this._currentY;
			this._currentX = touch.pageX;
			this._currentY = touch.pageY;
			this._caliculator.add(touch);
			this._lastTouchTime = (new Date).getTime();
			return this;
		},
		_endTouch: function(){
			var now = (new Date).getTime();
			var o = this.options;
			if(now-this._lastTouchTime < o.extraScrollCancelDuration){
				this._inner.endScroll(
					this._caliculator.getExtraScroll() || null
				);
			}else{
				this._inner.endScroll();
			}
		},
		_slideInner: function(){
			var o = this.options;
			var distances = {};
			if(o.xScrollable){ distances.x = this._currentX - this._startX; }
			if(o.yScrollable){ distances.y = this._currentY - this._startY; }
			this._inner.slide(distances);
		},

		_mouseWheel: function(delta){
			var o = this.options;
			this._startY = null;
			this._currentY = delta;
			this._caliculator.add(delta);
			this._lastTouchTime = null;
			this._inner.endScroll(this._caliculator.getExtraScroll() || null);
		},

		/* misc */

		_resizeHandler: function(){
			this.update();
		},

		/* mouse event handlers */

		_dragStartHandler: function(event){
			this._setStartTouch(event);
			this._inner.startScroll();
			this._body
				.bind('mousemove', $.proxy(this._dragMoveHandler, this))
				.bind('mouseup',   $.proxy(this._dragEndHandler, this));
		},
		_dragMoveHandler: function(event){
			this._setCurrentTouch(event);
			this._slideInner();
			event.preventDefault();
		},
		_dragEndHandler: function(event){
			this._body
				.unbind('mousemove', $.proxy(this._dragMoveHandler, this))
				.unbind('mouseup',   $.proxy(this._dragEndHandler, this));
			this._endTouch();
		},

		/* mouse wheel event handlers */

		_wheelHandler: function(event, delta){
			this._mouseWheel(delta);
			//this._setCurrentTouch(event);
			this._slideInner();
			event.preventDefault();
		},


		/* touch event handlers */

		_touchStartHandler: function(event){
			var touches = event.touches;
			if(touches.length>1){
				return;
			}
			this._setStartTouch(touches[0]);
			this._inner.startScroll();
			// Default event をキャンセル
			//event.preventDefault();
		},
		_touchMoveHandler: function(event){
			var touches = event.touches;
			if(touches.length>1){
				return;
			}
			this._setCurrentTouch(touches[0]);
			this._slideInner();
			event.preventDefault();
		},
		_touchEndHandler: function(){
			this._endTouch();
		},
		
		/* when containers' size was changed, need updated */

		update: function(){
			this._inner.updateSize(
				this.element.width(),
				this.element.height()
			);
			this._prepareScrollbarX();
			this._prepareScrollbarY();
			this._inner.slide({x:0,y:0});
		}

	});

	/**
	 * $.ui.touchscrollable_inner
	 * inner.
	 */
	$.widget('ui.touchscrollable_inner', {
		options:{
			caliculator: null,
			xScrollable: null,
			yScrollable: null,
			transitionDuration_over: null,
			transitionDuration_extraMove: null,
			overEdgeSlowdownRate: null,
			maxOverEdgeDistance: null,
			containerWidth: null,
			containerHeight: null
		},
		_startX: 0,
		_startY: 0,
		_currentX: 0,
		_currentY: 0,
		_minX: 0,
		_maxX: 0,
		_minY: 0,
		_maxY: 0,
		_width: null,
		_height: null,
		_transitionEndHandler: null,
		_animator: null,
		_calicurator: null,
		_scrollbarX: null,
		_scrollbarY: null,
		_whileOverEdge: false,

		_create: function(){
			var o = this.options;
			this._calicurator = o.caliculator;
			this._setupAnimator();
			this.updateSize( o.containerWidth, o.containerHeight );
		},

		getWidth: function(){
			return this._width;
		},
		getHeight: function(){
			return this._height;
		},

		/* around initialize */

		_setupAnimator: function(){
			var fn = 'transitionChainable';
			this._animator = this.element[fn]().data(fn);
		},

		attachScrollbarX: function(scrollbarX){
			this._scrollbarX = scrollbarX;
		},
		attachScrollbarY: function(scrollbarY){
			this._scrollbarY = scrollbarY;
		},

		updateSize: function(containerWidth, containerHeight){
			this.options.containerWidth = containerWidth;
			this.options.containerHeight = containerHeight;
			this._prepareXScroll();
			this._prepareYScroll();
		},

		_prepareXScroll: function(){
			var o = this.options;
			this._width = this.element.innerWidth();
			this._maxX = 0;
			this._minX = o.containerWidth - this._width;
		},
		_prepareYScroll: function(){
			var o = this.options;
			this._height = this.element.innerHeight();
			this._maxY = 0;
			this._minY = o.containerHeight - this._height;
		},

		/*
			methods below fix the scrolling distance
			because the scrolling speed needs to be slow downed
			if it was over the container.
		*/

		_calcAdjustedDistance: function(plannedDistance, startCoord, promisedCoord){
			var rate = this.options.overScrollSlowDownRate;
			var promisedDistance = promisedCoord - startCoord;
			var overDistance = plannedDistance - promisedDistance;
			var adjustedOverDistance = Math.floor(overDistance*rate);
			return promisedDistance + adjustedOverDistance;
		},
		_fixOverDistanceX: function(distanceX){
			if(this._isTooRight()){
				return this._calcAdjustedDistance(distanceX, this._startX, this._maxX);
			}else if(this._isTooLeft()){
				return this._calcAdjustedDistance(distanceX, this._startX, this._minX);
			}else{
				return distanceX;
			}
		},
		_fixOverDistanceY: function(distanceY){
			if(this._isTooAbove()){
				return this._calcAdjustedDistance(distanceY, this._startY, this._minY);
			}else if(this._isTooBelow()){
				return this._calcAdjustedDistance(distanceY, this._startY, this._maxY);
			}else{
				return distanceY;
			}
		},

		/* transform wrapper */

		// アニメーションの命令
		_animateXYTo: function(x,y,duration,easing,before,after){
			this._animator.animate({
				val: 'translate3d('+x+'px,'+y+'px,0)',
				easing: easing,
				duration: duration,
				before: before || null,
				after: after || null
			});
			this._startX = this._currentX = x;
			this._startY = this._currentY = y;
		},

		_normalizeExtraScroll_positive: function(currentCoord, maxCoord, plannedCoord){
			var maxOverDistance = this.options.maxOverEdgeDistance;
			var promisedCoord = maxCoord;
			var promisedDistance = maxCoord - currentCoord;
			var overDistance = plannedCoord - promisedCoord;
			var lessenedDistance;
			overDistance = (overDistance > maxOverDistance) ? maxOverDistance : overDistance;
			lessenedDistance = promisedDistance + overDistance;
			return {
				distance: lessenedDistance,
				duration: SpeedCaliculator.calcOverScrollDurationFromDistance(lessenedDistance)
			};
		},
		_normalizeExtraScroll_negative: function(currentCoord, minCoord, plannedCoord){
			var maxOverDistance = this.options.maxOverEdgeDistance;
			var promisedCoord = minCoord;
			var promisedDistance = minCoord - currentCoord;
			var overDistance = plannedCoord - promisedCoord;
			var lessenedDistance;
			overDistance = (overDistance < maxOverDistance) ? -maxOverDistance : overDistance;
			lessenedDistance = promisedDistance + overDistance;
			return {
				distance: lessenedDistance,
				duration: SpeedCaliculator.calcOverScrollDurationFromDistance(lessenedDistance)
			};
		},

		_normalizeExtraScroll: function(extraScrollInfo){
			var self = this;
			var o = self.options;
			var maxOver = o.maxOverEdgeDistance;
			var info = extraScrollInfo;
			var plannedX = self._currentX + info.x;
			var plannedY = self._currentY + info.y;
			if(o.xScrollable){
				(function(){
					var res =
						(plannedX>self._maxX) ?  self._normalizeExtraScroll_positive(self._currentX, self._maxX, plannedX) :
						(plannedX<self._minX) ?  self._normalizeExtraScroll_negative(self._currentX, self._minX, plannedX) : null;
					if(res){
						info.x = res.distance;
						info.duration = res.duration;
					}
				})();
			}
			if(o.yScrollable){
				(function(){
					var res =
						(plannedY>self._maxY) ? self._normalizeExtraScroll_positive(self._currentY, self._maxY, plannedY) :
						(plannedY<self._minY) ? self._normalizeExtraScroll_negative(self._currentY, self._minY, plannedY) : null;
					if(res){
						info.y = res.distance;
						info.duration = res.duration;
					}
				})();
			}
			return info;
		},

		_getOverInfo: function(){
			var res = {};
			var x = this._currentX;
			var y = this._currentY;
			var o = this.options;
			res.left   = o.xScrollable ? this._isTooLeft()  : false;
			res.right  = o.xScrollable ? this._isTooRight() : false;
			res.top    = o.yScrollable ? this._isTooAbove() : false;
			res.bottom = o.yScrollable ? this._isTooBelow() : false;
			return res;
		},

		_isOverEdge: function(){
			var info = this._getOverInfo();
			return (info.left || info.right || info.top || info.bottom);
		},

		/* container over detect methods */

		_isTooAbove: function(){
			return (this._currentY < this._minY);
		},
		_isTooBelow: function(){
			return (this._currentY > this._maxY);
		},
		_isTooLeft: function(){
			return (this._currentX < this._minX);
		},
		_isTooRight: function(){
			return (this._currentX > this._maxX);
		},

		stop: function(){
			var scrollbarX = this._scrollbarX;
			var scrollbarY = this._scrollbarY;
			this._animator.stop();
			scrollbarX && scrollbarX.stop();
			scrollbarY && scrollbarY.stop();
		},

		startScroll: function(){
			this.stop();
			var xy = this._animator.getXY();
			var scrollbarX = this._scrollbarX;
			var scrollbarY = this._scrollbarY;
			scrollbarX && scrollbarX.show();
			scrollbarY && scrollbarY.show();
			this._startX = this._currentX = xy.x;
			this._startY = this._currentY = xy.y;
			return this;
		},
		slide: function(distances){
			var scrollbarX = this._scrollbarX;
			var scrollbarY = this._scrollbarY;
			var x = this._currentX = (distances.x===undefined) ? 0 : this._startX + this._fixOverDistanceX(distances.x);
			var y = this._currentY = (distances.y===undefined) ? 0 : this._startY + this._fixOverDistanceY(distances.y);
			this._animator.changeVal( 'translate3d('+x+'px,'+y+'px,0)' );
			scrollbarX && scrollbarX.handleInnerMove(
				this._currentX, this._currentY
			);
			scrollbarY && scrollbarY.handleInnerMove(
				this._currentX, this._currentY
			);
			return this;
		}, 
		endScroll: function(extraScrollInfo){
			var scrollbarX = this._scrollbarX;
			var scrollbarY = this._scrollbarY;
			var o = this.options;
			var x = this._currentX;
			var y = this._currentY;
			var duration = this.options.transitionDuration_over;
			if(this._isOverEdge()){
				this._scrollToEdge( duration, EASING_EXTRASCROLL);
			}else if(extraScrollInfo){
				this._invokeExtraScroll( extraScrollInfo );
			}else{
				scrollbarX && scrollbarX.hide();
				scrollbarY && scrollbarY.hide();
			}
		},
		_scrollToEdge: function(duration, easing, isAfterExtraScroll){
			var self = this;
			var scrollbarX = self._scrollbarX;
			var scrollbarY = self._scrollbarY;
			var over = self._getOverInfo();
			var x = self._currentX;
			var y = self._currentY;
			var o = self.options;
			x = over.left   ? self._minX : x;
			x = over.right  ? self._maxX : x;
			y = over.top    ? self._minY : y;
			y = over.bottom ? self._maxY : y;
			self._animateXYTo( x,y,duration,easing,function(){
				self._whileOverEdge = true;
			},function(){
				self._whileOverEdge = false;
				scrollbarX && scrollbarX.hide();
				scrollbarY && scrollbarY.hide();
			});
			scrollbarX && scrollbarX.handleScrollToEdge(duration, isAfterExtraScroll);
			scrollbarY && scrollbarY.handleScrollToEdge(duration, isAfterExtraScroll);
			return this;
		},
		_invokeExtraScroll: function(extraScrollInfo){
			var self = this;
			var info = extraScrollInfo;
			info = this._normalizeExtraScroll(info);
			var o = this.options;
			var scrollbarX = this._scrollbarX;
			var scrollbarY = this._scrollbarY;
			var x = o.xScrollable ? this._currentX + info.x : this._currentX;
			var y = o.yScrollable ? this._currentY + info.y : this._currentY;
			this._animateXYTo( x, y, info.duration, EASING_EXTRASCROLL );
			if(scrollbarX){
				if (info.x === 0){
					scrollbarX.hide();
				}else{
					scrollbarX.handleExtraScroll(x, info.duration);
				}
			}
			if(scrollbarY){
				if (info.y === 0){
					scrollbarY.hide();
				}else{
					scrollbarY.handleExtraScroll(y, info.duration);
				}
			}
			if(this._isOverEdge()){
				this._scrollToEdge( info.duration*0.9, EASING_RETURNOVER ,true);
			}
		}

	});

	/**
	 * SpeedCaliculator
	 * caliculate the distance for extra move animation.
	 */
	var SpeedCaliculator = function(options){
		this._touches = [];
		this.options = $.extend({},this.options, options);
	};
	SpeedCaliculator.prototype = {
		_touches: null,
		options: {
			size: 10
		},
		_removeUnnecessaryTouches: function(){
			var touches = this._touches;
			var size = this.options.size;
			if(touches.length>size){
				while(touches.length>=size){
					touches.shift();
				}
			}
		},
		add: function(touchEvent){
			var touches = this._touches;
			touches.push(
				$.extend({},{
					x: touchEvent.pageX,
					y: touchEvent.pageY,
					time: (new Date).getTime()
				})
			);
			this._removeUnnecessaryTouches();
		},
		empty: function(){
			this._touches.length = 0;
		},
		getExtraScroll: function(){
			function bigger(a,b){
				return (a>b) ? a: b;
			}
			var infoX = this._calcFinalMove('x');
			var infoY = this._calcFinalMove('y');
			return {
				duration: bigger(infoX.duration, infoY.duration),
				x: infoX.distance,
				y: infoY.distance
			};
		},
		_calcFinalMove: function(axis){
			var touches = this._touches.slice(0);
			var finalTouch = touches.pop();
			var endPos = finalTouch[axis];
			var startPos;
			var current = finalTouch;
			var next;
			var goingPlus;
			var currentGoingPlus;
			var distance;
			var endTime = finalTouch.time;
			var startTime;
			var duration;
			var speed;
			var i = 0;
			while((next = touches.pop())!==undefined){
				i++;
				currentGoingPlus = (current[axis]-next[axis]>0);
				if(goingPlus===undefined){
					goingPlus = currentGoingPlus;
				}else{
					if(goingPlus!==currentGoingPlus){
						break;
					}
				}
				startTime = current.time;
				startPos = next[axis];
				current = next;
			}
			// too few touches occurs too fast result so avoid this
			if(i<3){
				return {
					duration: 0,
					distance: 0
				};
			}
			distance = endPos - startPos;
			duration = endTime - startTime;
			return this._calcFinalSpeed(distance, duration, axis);
		},
		_calcFinalSpeed: function(distance, duration, axis){
			var res = {};
			var speed, speed_abs, plus;
			if(distance<=0){
				res.distance = 0;
				res.duration = 0;
			}
			speed = distance/duration;
			return SpeedCaliculator.calcExtraScrollInfoFromSpeed(speed);
		}
	};
	SpeedCaliculator.calcExtraScrollInfoFromSpeed = function(speed){
		var res;
		var speed_original = speed;
		speed = Math.abs(speed);
		res = (speed>2)    ? { duration:1700, distance:2000 } :
		      (speed>1)    ? { duration:1200, distance:500 } :
		      (speed>0.5)  ? { duration:1000, distance:100 } :
		      (speed>0.25) ? { duration:1000, distance:80 } :
		      (speed>0.15) ? { duration:800, distance:60 } :
		                     { duration:0, distance:0 };
		if(speed_original<0){
			res.distance = -res.distance;
		}
		return res;
	};
	SpeedCaliculator.calcOverScrollDurationFromDistance = function(distance){
		distance = Math.abs(distance);
		return (distance>=2000) ? 1700 :
		       (distance>=1000) ? 900 :
		       (distance>=700) ? 600 :
		       (distance>=500) ? 400 :
		       (distance>=300) ? 370 :
		       200;
	};

	/**
	 * $.ui.touchscrollable_scrollbarY
	 */
	$.widget('ui.touchscrollable_scrollbarY', {
		options: {
			alwaysVisible: true
		},
		_hidden: false,
		_positioner: null,
		_create: function(){
			this._preparePositioner();
			this.options.alwaysVisible && this._forceShow();
		},
		_preparePositioner: function(){
			var fn = 'touchscrollable_scrollbarY_positioner';
			// *ポインターの要素を指定
			var e = $('.positioner', this.element);
			var o = $.extend({}, this.options, {
				scrollbar: this
			});
			this._positioner = e[fn](o).data(fn);
		},
		_forceShow:function(){
			this.element.addClass('visible');
			this._hidden = false;
		},
		show: function(){
			if(this.options.alwaysVisible || !this._hidden){
				return this;
			}
			this._forceShow();
			return this;
		},
		hide: function(){
			if(this.options.alwaysVisible || this._hidden){
				return this;
			}
			this.element.removeClass('visible');
			this._hidden = true;
			return this;
		},
		startScroll: function(){
			this.show();
			return this;
		},
		stop: function(){
			this.hide();
			this._positioner.stop();
			return this;
		},
		update: function(containerOuterHeight, containerInnerHeight){
			this._positioner.update(containerOuterHeight, containerInnerHeight);
			return this;
		},
		handleInnerMove: function(movedX, movedY){
			this.show();
			this._positioner.handleInnerMove(movedX, movedY);
			return this;
		},
		handleScrollToEdge: function(duration, isAfterExtraScroll){
			this._positioner.handleScrollToEdge(duration, isAfterExtraScroll);
			return this;
		},
		handleExtraScroll: function(yInnerWillMoveTo, duration){
			this._positioner.handleExtraScroll(yInnerWillMoveTo, duration);
			return this;
		}
	});

	/**
	 * $.ui.touchscrollable_scrollbarY_positioner
	 */
	$.widget('ui.touchscrollable_scrollbarY_positioner', {
		options:{
			containerOuterHeight: null,
			containerInnerHeight: null,
			scrollbar: null
		},
		_minY: 0,
		_maxY: null,
		_currentY: null,
		_height: null,
		_maxHeight: null,
		_containerOuterHeight: null,
		_containerInnerHeight: null,
		_reservedReturnShrinkAnimationDuration: null,
		_animator: null,
		_scaleAgainstOuter: null,
		_watchShrinkTimer: null,
		_scrollbar: null,
		_shrinker: null,
		_create: function(){
			var o = this.options;
			this._scrollbar = o.scrollbar;
			this._containerOuterHeight = o.containerOuterHeight;
			this._containerInnerHeight = o.containerInnerHeight;
			this._setupAnimator();
			this._fitToTop();
			this._prepareShrinker();
			this.update( o.containerOuterHeight, o.containerInnerHeight );
			return this;
		},
		update: function(containerOuterHeight, containerInnerHeight){
			this._updateMaxHeight();
			var ratio = containerOuterHeight / containerInnerHeight;
			var maxHeight = this._maxHeight;
			var height = maxHeight * ratio;
			this.element.height(height);
			this._height = height;
			this._maxY = maxHeight - height;
			this._scaleAgainstOuter = height / containerOuterHeight;
			this._shrinker.update();
			return this;
		},
		_updateMaxHeight: function(){
			var e = this.element;
			var h = e.css('height');
			e.css('height','100%');
			this._maxHeight = e.height();
			e.height(h);
			return this;
		},
		_prepareShrinker: function(){
			// *シュリンカーの要素を指定
			var e = $('.shrinker', this.element);
			var fn = 'touchscrollable_scrollbarY_shrinker';
			var o = { scrollbar: this._scrollbar };
			this._shrinker = e[fn](o).data(fn);
			return this;
		},
		_setupAnimator: function(){
			var fn = 'transitionChainable';
			this._animator = this.element[fn]().data(fn);
			return this;
		},
		// アニメーションの命令
		_animateXYTo: function(x,y,duration,easing,after){
			this._animator.animate({
				val: 'translate3d('+x+'px,'+y+'px,0)',
				easing: easing,
				duration: duration,
				after: after || null
			});
			this._currentY = y;
			return this;
		},
		stop: function(){
			this._animator.stop();
			this._watchShrinkTimer && clearInterval(this._watchShrinkTimer);
			this._shrinker.stop();
			return this;
		},
		handleInnerMove: function(movedX, movedY){
			var x = 0;
			var y = -(this._scaleAgainstOuter * movedY);
			if(y < this._minY){
				this._shrinker.instantShrink('top', this._minY - y);
				y = this._minY;
			}else if(y > this._maxY){
				this._shrinker.instantShrink('bottom', y - this._maxY);
				y = this._maxY;
			}
			this._animator.changeVal( 'translate3d(0,'+y+'px,0)' );
			return this;
		},
		handleScrollToEdge: function(duration, isAfterExtraScroll){
			if(isAfterExtraScroll){
				this._reservedReturnShrinkAnimationDuration = duration;
			}else{
				this._shrinker.startReturnShrinkAnimation(duration);
			}
			return self;
		},
		_watchShrink_top: function(plannedY, duration){
			var self = this;
			var animator = self._animator;
			var passed = 0;
			var timer = self._watchShrinkTimer = setInterval(function(){
				var currentY = animator.getComputedXY().y;
				passed++;
				if(currentY<self._minY+3){
					var restDuration = duration - passed;
					var restDistance = - plannedY - self._minY;
					restDistance = restDistance*2;
					self._fitToTop();
					self._shrinker.startShrinkAnimation('top', restDuration, restDistance);
					self._shrinker.startReturnShrinkAnimation(self._reservedReturnShrinkAnimationDuration);
					self._reservedReturnShrinkAnimationDuration = null;
					clearInterval(timer);
					self._watchShrinkTimer = null;
				}
			},1);
			return self;
		},
		_watchShrink_bottom: function(plannedY, duration){
			var self = this;
			var animator = self._animator;
			var passed = 0;
			var timer = self._watchShrinkTimer = setInterval(function(){
				var currentY = animator.getComputedXY().y;
				passed++;
				if(currentY>self._maxY-3){
					var restDuration = duration - passed;
					var restDistance = plannedY - self._maxY;
					restDistance = restDistance*2;
					self._fitToBottom();
					self._shrinker.startShrinkAnimation('bottom', restDuration, restDistance);
					self._shrinker.startReturnShrinkAnimation(self._reservedReturnShrinkAnimationDuration);
					self._reservedReturnShrinkAnimationDuration = null;
					clearInterval(timer);
					self._watchShrinkTimer = null;
				}
			},1);
			return self;
		},
		handleExtraScroll: function(yInnerWillMoveTo, duration){
			var self = this;
			var plannedY = -(yInnerWillMoveTo * self._scaleAgainstOuter);
			var tooAbove = plannedY < this._minY;
			var tooBelow = plannedY > this._maxY;
			var willOver = tooAbove || tooBelow || false;
			self._animateXYTo(
				0, plannedY, duration, EASING_EXTRASCROLL,
				willOver ? null : function(){
					self._scrollbar.hide();
				}
			);
			if(tooAbove){
				this._watchShrink_top(plannedY, duration);
			}else if(tooBelow){
				this._watchShrink_bottom(plannedY, duration);
			}
			return self;
		},
		_fitToTop: function(){
			this._animator.stop().changeVal( 'translate3d(0,'+this._minY+'px,0)' );
			return this;
		},
		_fitToBottom: function(){
			this._animator.stop().changeVal( 'translate3d(0,'+this._maxY+'px,0)' );
			return this;
		}
	});

	/**
	 * $.ui.touchscrollable_scrollbarY_shrinker
	 */
	$.widget('ui.touchscrollable_scrollbarY_shrinker', {
		options: {
			minBarHeight: 5,
			scrollbar: null
		},
		_naturalHeight: null,
		_animator: null,
		_origin: 'top', // or 'bottom'
		_scrollbar: null,
		_reservedAfterShrinkFn: null,
		_create: function(){
			var o = this.options;
			this._scrollbar = o.scrollbar;
			this._setupAnimator();
			this.update();
		},
		_setupAnimator: function(){
			var fn = 'transitionChainable';
			var o = { property:'height' };
			this._animator = this.element[fn](o).data(fn);
		},
		_setOriginToTop: function(){
			if(this._origin === 'top'){
				return this;
			}
			this.element.css('bottom','auto').css('top',0);
			this._origin = 'top';
			return this;
		},
		_setOriginToBottom: function(){
			if(this._origin === 'bottom'){
				return this;
			}
			this.element.css('top','auto').css('bottom',0);
			this._origin = 'bottom';
			return this;
		},
		_changeOrigin: function(origin){
			(origin === 'top') ? this._setOriginToTop() : 
			(origin === 'bottom') ? this._setOriginToBottom() : null;
		},
		instantShrink: function(origin, cutDownHeight){
			this._animator.finish();
			this._changeOrigin(origin);
			this.element.height(this._calcShrinkedHeight(cutDownHeight));
			return this;
		},
		_calcShrinkedHeight: function(cutDownHeight){
			var newHeight = this._naturalHeight - cutDownHeight;
			var minHeight = this.options.minBarHeight;
			newHeight = (newHeight < minHeight) ? minHeight : newHeight;
			return newHeight;
		},
		update: function(){
			var e = this.element;
			var h;
			e.css('height','100%');
			h = this._naturalHeight = e.height();
			e.height(h);
		},
		stop: function(){
			this._animator.stop();
		},
		// アニメーションの命令
		startShrinkAnimation: function(origin, duration, cutDownHeight){
			this._changeOrigin(origin);
			this.element.height(this._naturalHeight);
			var self = this;
			this._animator.animate({
				val: this._calcShrinkedHeight(cutDownHeight),
				easing: EASING_RETURNOVER,
				duration: duration
			});
			return this;
		},
		// アニメーションの命令
		startReturnShrinkAnimation: function(duration){
			var self = this;
			self._animator.animate({
				val: self._naturalHeight,
				easing: EASING_RETURNOVER,
				duration: duration
			});
			return self;
		}
	});

	/**
	 * $.ui.touchscrollable_scrollbarX
	 */
	$.widget('ui.touchscrollable_scrollbarX', {
		options: {
			alwaysVisible: false
		},
		_hidden: false,
		_positioner: null,
		_create: function(){
			this._preparePositioner();
			this.options.alwaysVisible && this._forceShow();
		},
		_preparePositioner: function(){
			var fn = 'touchscrollable_scrollbarX_positioner';
			var e = $('.positioner', this.element);
			var o = $.extend({}, this.options, {
				scrollbar: this
			});
			this._positioner = e[fn](o).data(fn);
		},
		_forceShow:function(){
			this.element.addClass('visible');
			this._hidden = false;
		},
		show: function(){
			if(this.options.alwaysVisible || !this._hidden){
				return this;
			}
			this._forceShow();
			return this;
		},
		hide: function(){
			if(this.options.alwaysVisible || this._hidden){
				return this;
			}
			this.element.removeClass('visible');
			this._hidden = true;
			return this;
		},
		startScroll: function(){
			this.show();
			return this;
		},
		stop: function(){
			this.hide();
			this._positioner.stop();
			return this;
		},
		update: function(containerOuterWidth, containerInnerWidth){
			this._positioner.update(containerOuterWidth, containerInnerWidth);
			return this;
		},
		handleInnerMove: function(movedX, movedY){
			this.show();
			this._positioner.handleInnerMove(movedX, movedY);
			return this;
		},
		handleScrollToEdge: function(duration, isAfterExtraScroll){
			this._positioner.handleScrollToEdge(duration, isAfterExtraScroll);
			return this;
		},
		handleExtraScroll: function(xInnerWillMoveTo, duration){
			this._positioner.handleExtraScroll(xInnerWillMoveTo, duration);
			return this;
		}
	});

	/**
	 * $.ui.touchscrollable_scrollbarX_positioner
	 */
	$.widget('ui.touchscrollable_scrollbarX_positioner', {
		options:{
			containerOuterWidth: null,
			containerInnerWidth: null,
			scrollbar: null
		},
		_minX: 0,
		_maxX: null,
		_currentX: null,
		_width: null,
		_maxWidth: null,
		_containerOuterWidth: null,
		_containerInnerWidth: null,
		_reservedReturnShrinkAnimationDuration: null,
		_animator: null,
		_scaleAgainstOuter: null,
		_watchShrinkTimer: null,
		_scrollbar: null,
		_shrinker: null,
		_create: function(){
			var o = this.options;
			this._scrollbar = o.scrollbar;
			this._containerOuterWidth = o.containerOuterWidth;
			this._containerInnerWidth = o.containerInnerWidth;
			this._setupAnimator();
			this._fitToLeft();
			this._prepareShrinker();
			this.update( o.containerOuterWidth, o.containerInnerWidth );
			return this;
		},
		update: function(containerOuterWidth, containerInnerWidth){
			this._updateMaxWidth();
			var ratio = containerOuterWidth / containerInnerWidth;
			var maxWidth = this._maxWidth;
			var width = maxWidth * ratio;
			this.element.width(width);
			this._width = width;
			this._maxX = maxWidth - width;
			this._scaleAgainstOuter = width / containerOuterWidth;
			this._shrinker.update();
			return this;
		},
		_updateMaxWidth: function(){
			var e = this.element;
			var w = e.css('width');
			e.css('width','100%');
			this._maxWidth = e.width();
			e.width(w);
			return this;
		},
		_prepareShrinker: function(){
			var e = $('.shrinker', this.element);
			var fn = 'touchscrollable_scrollbarX_shrinker';
			var o = { scrollbar: this._scrollbar };
			this._shrinker = e[fn](o).data(fn);
			return this;
		},
		_setupAnimator: function(){
			var fn = 'transitionChainable';
			this._animator = this.element[fn]().data(fn);
			return this;
		},
		// アニメーションの命令
		_animateXYTo: function(x,y,duration,easing,after){
			this._animator.animate({
				val: 'translate3d('+x+'px,'+y+'px,0)',
				easing: easing,
				duration: duration,
				after: after || null
			});
			this._currentX = x;
			return this;
		},
		stop: function(){
			this._animator.stop();
			this._watchShrinkTimer && clearInterval(this._watchShrinkTimer);
			this._shrinker.stop();
			return this;
		},
		handleInnerMove: function(movedX, movedY){
			var y = 0;
			var x = -(this._scaleAgainstOuter * movedX);
			if(x < this._minX){
				this._shrinker.instantShrink('left', this._minX - x);
				x = this._minX;
			}else if(x > this._maxX){
				this._shrinker.instantShrink('right', x - this._maxX);
				x = this._maxX;
			}
			this._animator.changeVal( 'translate3d('+x+'px,0,0)' );
			return this;
		},
		handleScrollToEdge: function(duration, isAfterExtraScroll){
			if(isAfterExtraScroll){
				this._reservedReturnShrinkAnimationDuration = duration;
			}else{
				this._shrinker.startReturnShrinkAnimation(duration);
			}
			return self;
		},
		_watchShrink_left: function(plannedX, duration){
			var self = this;
			var animator = self._animator;
			var passed = 0;
			var timer = self._watchShrinkTimer = setInterval(function(){
				var currentX = animator.getComputedXY().x;
				passed++;
				if(currentX<self._minX+3){
					var restDuration = duration - passed;
					var restDistance = - plannedX - self._minX;
					restDistance = restDistance*2;
					self._fitToLeft();
					self._shrinker.startShrinkAnimation('left', restDuration, restDistance);
					self._shrinker.startReturnShrinkAnimation(self._reservedReturnShrinkAnimationDuration);
					self._reservedReturnShrinkAnimationDuration = null;
					clearInterval(timer);
					self._watchShrinkTimer = null;
				}
			},1);
			return self;
		},
		_watchShrink_right: function(plannedX, duration){
			var self = this;
			var animator = self._animator;
			var passed = 0;
			var timer = self._watchShrinkTimer = setInterval(function(){
				var currentX = animator.getComputedXY().x;
				passed++;
				if(currentX>self._maxX-3){
					var restDuration = duration - passed;
					var restDistance = plannedX - self._maxX;
					restDistance = restDistance*2;
					self._fitToRight();
					self._shrinker.startShrinkAnimation('right', restDuration, restDistance);
					self._shrinker.startReturnShrinkAnimation(self._reservedReturnShrinkAnimationDuration);
					self._reservedReturnShrinkAnimationDuration = null;
					clearInterval(timer);
					self._watchShrinkTimer = null;
				}
			},1);
			return self;
		},
		// アニメーションの命令
		handleExtraScroll: function(xInnerWillMoveTo, duration){
			var self = this;
			var plannedX = -(xInnerWillMoveTo * self._scaleAgainstOuter);
			var tooLeft = plannedX < this._minX;
			var tooRight = plannedX > this._maxX;
			var willOver = tooLeft || tooRight || false;
			self._animateXYTo(
				plannedX, 0, duration, EASING_EXTRASCROLL,
				willOver ? null : function(){
					self._scrollbar.hide();
				}
			);
			if(tooLeft){
				this._watchShrink_left(plannedX, duration);
			}else if(tooRight){
				this._watchShrink_right(plannedX, duration);
			}
			return self;
		},
		_fitToLeft: function(){
			this._animator.stop().changeVal( 'translate3d('+this._minX+'px,0,0)' );
			return this;
		},
		_fitToRight: function(){
			this._animator.stop().changeVal( 'translate3d('+this._maxX+'px,0,0)' );
			return this;
		}
	});

	/**
	 * $.ui.touchscrollable_scrollbarX_shrinker
	 */
	$.widget('ui.touchscrollable_scrollbarX_shrinker', {
		options: {
			minBarWidth: 5,
			scrollbar: null
		},
		_naturalWidth: null,
		_width: null,
		_animator: null,
		_origin: 'left', // or 'right'
		_scrollbar: null,
		_reservedAfterShrinkFn: null,
		_create: function(){
			var o = this.options;
			this._scrollbar = o.scrollbar;
			this._setupAnimator();
			this.update();
		},
		_setupAnimator: function(){
			var fn = 'transitionChainable';
			var o = { property:'width' };
			this._animator = this.element[fn](o).data(fn);
		},
		_setOriginToLeft: function(){
			if(this._origin === 'left'){
				return this;
			}
			this.element.css('right','auto').css('left',0);
			this._origin = 'left';
			return this;
		},
		_setOriginToRight: function(){
			if(this._origin === 'bottom'){
				return this;
			}
			this.element.css('left','auto').css('right',0);
			this._origin = 'right';
			return this;
		},
		_changeOrigin: function(origin){
			(origin === 'left') ? this._setOriginToLeft() : 
			(origin === 'right') ? this._setOriginToRight() : null;
		},
		instantShrink: function(origin, cutDownWidth){
			this._animator.finish();
			this._changeOrigin(origin);
			this.element.width(this._calcShrinkedWidth(cutDownWidth));
			return this;
		},
		_calcShrinkedWidth: function(cutDownWidth){
			var newWidth = this._naturalWidth - cutDownWidth;
			var minWidth = this.options.minBarWidth;
			newWidth = (newWidth < minWidth) ? minWidth : newWidth;
			return newWidth;
		},
		update:function(){
			var e = this.element;
			var w;
			e.css('width','100%');
			w = this._naturalWidth = this.element.width();
			e.width(w);
		},
		stop: function(){
			this._animator.stop();
		},
		// アニメーションの命令
		startShrinkAnimation: function(origin, duration, cutDownWidth){
			this._changeOrigin(origin);
			this.element.width(this._naturalWidth);
			var self = this;
			this._animator.animate({
				val: this._calcShrinkedWidth(cutDownWidth),
				easing: EASING_RETURNOVER,
				duration: duration
			});
			return this;
		},
		startReturnShrinkAnimation: function(duration){
			var self = this;
			self._animator.animate({
				val: self._naturalWidth,
				easing: EASING_RETURNOVER,
				duration: duration
			});
			return self;
		}
	});

	// マウスホイールのカスタムイベントを追加
	$.fn.mousewheel = function( fn ){
		return this[ fn ? "bind" : "trigger" ]( "wheel", fn );
	};

	// special event config
	$.event.special.wheel = {
		setup: function() {
			$.event.add( this, wheelEvents, wheelHandler, {} );
		},
		teardown: function(){
			$.event.remove( this, wheelEvents, wheelHandler );
		}
	};

	// events to bind ( browser sniffed... )
	var wheelEvents = !$.browser.mozilla ? "mousewheel" : // IE, opera, safari
		"DOMMouseScroll"+( $.browser.version<"1.9" ? " mousemove" : "" ); // firefox

	// shared event handler
	function wheelHandler( event ) {
		
		switch ( event.type ) {
			
			// FF2 has incorrect event positions
			case "mousemove": 
				return $.extend( event.data, { // store the correct properties
					clientX: event.clientX, clientY: event.clientY,
					pageX: event.pageX, pageY: event.pageY
				});
				
			// firefox	
			case "DOMMouseScroll": 
				$.extend( event, event.data ); // fix event properties in FF2
				event.delta = -event.detail / 3; // normalize delta
				break;
				
			// IE, opera, safari	
			case "mousewheel":				
				event.delta = event.wheelDelta / 120;
				break;
		}
		
		event.type = "wheel"; // hijack the event	
		return $.event.handle.call( this, event, event.delta );
	}

})(jQuery); // end $=jQuery encapsulation
