/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Smooth Scroller
 *
 *    @version 2.2.20100317
 *    @requires jquery.js
 *    @requires jquery.easing.js
 *    @requires jquery.mousewheel.js
 *    @requires bajl.js
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* -------------------- Settings for BAJL.PageScroller -------------------- */
/** 
 * settings for {@link BAJL.PageScroller}
 * @namespace settings for {@link BAJL.PageScroller}
 * @fieldOf BAJL.settings
 * @property {Boolean} enabled     flag for enable auto setup BAJL.PageScroller.
 * @property {Number}  offsetX     X-distance from original scroll destination position (px)
 * @property {Number}  offsetY     Y-distance from original scroll destination position (px)
 * @property {Number}  duration    animation duration (ms)
 * @property {String}  easing      easing function name existing in jQuery.easing
 * @property {String}  ignore      className of an clicked element which doesn't start scrolling
 * @property {BAJL.PageScroller.callback.onStart}    onStart       a callback for when scrolling starts
 * @property {BAJL.PageScroller.callback.onScroll}   onScroll      a callback for during scrolling continues
 * @property {BAJL.PageScroller.callback.onAbort}    onAbort       a callback for when scrolling is aborted
 * @property {BAJL.PageScroller.callback.onComplete} onComplete    a callback for when scrolling is completed
 */
BAJL.settings.PageScroller = {
	  'enabled'    : true
	, 'offsetX'    : 0
	, 'offsetY'    : 0
	, 'duration'   : 1000
	, 'easing'     : 'easeInOutCubic'
	, 'ignore'     : 'noSmoothScroll'
	, 'onStart'    : function(x, y, lastAnchor) {  }
	, 'onScroll'   : function(x, y, lastAnchor) {  }
	, 'onAbort'    : function(x, y, lastAnchor) {  }
	, 'onComplete' : function(x, y, lastAnchor) {
		var B = BAJL.ua;
		if (lastAnchor && (B.isGecko || B.isIE || (B.isSafari && B.version > 522))) {
			location.href = lastAnchor.href;
		}
	}
};



/* -------------------- Class : BAJL.Scroller -------------------- */
/**
 * provide smooth scroll behavior to the scrollable block elements.
 * @class smooth scroller
 * @extends BAJL.Observable
 * @param {Element|jQuery|String} node                         element to apply behavior
 * @param {Number}                [offsetX=0]                  X-distance from original scroll destination position (px)
 * @param {Number}                [offsetY=0]                  Y-distance from original scroll destination position (px)
 * @param {Number}                [duration=1000]              animation duration (ms)
 * @param {String}                [easing="easeInOutCubic"]    easing function name existing in jQuery.easing
 * @constructor
 */
BAJL.Scroller = function(node, offsetX, offsetY, duration, easing) {
	/** element node to apply behavior
	    @type Element
	    @private */
	this.node     = $(node).get(0);
	/** X-distance from original scroll destination position (px)
	    @type Number
	    @private */
	this.offsetX  = Number(offsetX) || 0;
	/** Y-distance from original scroll destination position (px)
	    @type Number
	    @private */
	this.offsetY  = Number(offsetY) || 0;
	/** animation duration time (ms).
	    @type Number
	    @private */
	this.duration = (Number(duration) >= 0) ? Number(duration) : 1000;
	/** easing function name existing in jQuery.easing
	    @type String
	    @private */
	this.easing   = ($.easing[easing]) ? easing : 'easeInOutCubic';
	
	if (BAJL.env.isDOMReady) {
		this.init();
	}
}

BAJL.Scroller.prototype = new BAJL.Observable;

/**
 * initialize.
 * @private
 */
BAJL.Scroller.prototype.init = function() {
	if (!this.node || this.node.nodeType != Node.ELEMENT_NODE) {
		throw new ReferenceError('BAJL.Scroller.init: scrolling element is not found.');
	} else {
		var $node = ($(this.node).is('html, body')) ? $(document) : $(this.node);

		// wasting return value of 'abort()'. it is workaround for IE, to avoid reviving canceled click event...
		$node.click     (BAJL.Delegate(function() { this.abort() }, this));
		$node.mousewheel(BAJL.Delegate(function() { this.abort() }, this));
	}
}

/**
 * scroll to the specified coordinate.
 * @param {Number} x    X-coordinate of the scroll destination (px)
 * @param {Number} y    Y-coordinate of the scroll destination (px)
 * @return this instance
 * @type BAJL.Scroller
 */
BAJL.Scroller.prototype.scrollTo = function(x, y) {
	if (isNaN(x) || isNaN(y)) {
		throw new TypeError('BAJL.Scroller.scrollTo: all arguments must be numbers.');
	} else {
		var zoom = 1;  // temporary, correct zoom ratio is needed for IE7...
		var maxX = Math.max(0, this.node.scrollWidth  - this.node.clientWidth );
		var maxY = Math.max(0, this.node.scrollHeight - this.node.clientHeight);

		var params = {
			  scrollLeft : Math.min(maxX, Math.max(0, Math.round((x + this.offsetX) * zoom)))
			, scrollTop  : Math.min(maxY, Math.max(0, Math.round((y + this.offsetY) * zoom)))
		};
		var options = {
			  duration : this.duration
			, easing   : this.easing
			, step     : BAJL.Delegate(this.step    , this)
			, complete : BAJL.Delegate(this.complete, this)
		};

		var $node = (BAJL.ua.isSafari && $(this.node).is('html')) ? $(document.body) : $(this.node);

		if ($node.scrollLeft() != params.scrollLeft || $node.scrollTop() != params.scrollTop) {
			this.abort();
			this.doCallbackByName('onStart');
			this.scrolling = true;
			$node.animate(params, options);
		}
	}
	return this;
}

/**
 * scroll to the position of the specified element node.
 * @param {Element|jQuery|String} node    an element as scroll destination
 * @return this instance
 * @type BAJL.Scroller
 */
BAJL.Scroller.prototype.scrollToNode = function(node) {
	node = $(node).get(0);
	if (!node || node.nodeType != Node.ELEMENT_NODE) {
		throw new TypeError('BAJL.Scroller.scrollToNode: first argument must be an element node.');
	} else {
		var $base = $(this.node);
		var $node = $(node);
		if ($node.parents().filter(function() { return (this == $base.get(0)) }).get(0)) {
			var basePos = $base.is('html, body') ? { left : 0, top : 0 } : $base.offset();
			var baseSrl = $base.is('html, body') ? { left : 0, top : 0 } : { left : $base.scrollLeft(), top : $base.scrollTop() };
			var nodePos = $node.offset();
			this.scrollTo(
				  nodePos.left + baseSrl.left - basePos.left + (BAJL.ua.isSafari ? 4 : 0)
				, nodePos.top  + baseSrl.top  - basePos.top
			);
		}
	}
	return this;
}

/**
 * callback func for scrolling
 * @private
 */
BAJL.Scroller.prototype.step = function() {
	this.doCallbackByName('onScroll');
}

/**
 * callback func for completed scrolling
 * @private
 */
BAJL.Scroller.prototype.complete = function() {
	if (this.scrolling) {
		this.scrolling = false;
		this.doCallbackByName('onComplete');
	}
}

/**
 * abort scrolling.
 * @return this instance
 * @type BAJL.Scroller
 */
BAJL.Scroller.prototype.abort = function() {
	if (this.scrolling) {
		$(this.node).stop();
		this.scrolling = false;
		this.doCallbackByName('onAbort');
	}
	return this;
}

/**
 * process callback.
 * @param {String} name    callback name (preferred to start with 'on')
 * @private
 */
BAJL.Scroller.prototype.doCallbackByName = function(name) {
	this.doCallback(name, this.node.scrollLeft, this.node.scrollTop);
}



/* -------------------- Setup : BAJL.PageScroller -------------------- */

$(function() {
	var S = BAJL.settings.PageScroller;

	if (S.enabled) {
		// create instance
		var node     = BAJL.ua.isQuirksMode ? document.body : document.documentElement;
		var scroller = new BAJL.Scroller(node, S.offsetX, S.offsetY, S.duration, S.easing);
		scroller.lastAnchor = null;  // stores last clicked anchor element

		/** page scroller; an instance of {@link BAJL.Scroller}.
		    @field
		    @type BAJL.Scroller */
		BAJL.PageScroller = scroller; // expose instance to global

		// add event
		$(document).click(function(e) {
			var $anchor = $(e.target).closest('a, area').filter(function() {
				var ignore = $(this).hasClass(S.ignore);
				var target = $(this).attr('target');
				return (!ignore && (!target || target == '_self'));
			}).eq(0);

			var $target = $anchor.map(function() {
				var href = this.getAttribute('href') || '';
				var hash = this.hash || '#';  //  supplement of '#' is workaround for IE6.
				var path = href.replace(hash, '');
				if (hash != '#' && (!path || path == location.href.split('#')[0])) {
					return $(hash);
				} else {
					return null;
				}
			}).get(0);

			if ($target && $target.get(0)) {
				e.preventDefault();
				e.stopPropagation();
				$anchor.blur();
				scroller.lastAnchor = $anchor.get(0);
				scroller.scrollToNode($target);
			}
		});

		// add callbacks
		var callback = function(func, delFlag) {
			return function(x, y) {
				if (typeof func == 'function') func(x, y, scroller.lastAnchor);
				if (delFlag) scroller.lastAnchor = null;
			}
		};
		scroller.addCallback('onStart'   , callback(S.onStart   , false));
		scroller.addCallback('onScroll'  , callback(S.onScroll  , false));
		scroller.addCallback('onAbort'   , callback(S.onAbort   , true ));
		scroller.addCallback('onComplete', callback(S.onComplete, true ));
	}
});



/* -------------------- for JSDoc toolkit output -------------------- */
/**
 * callback functions for {@link BAJL.Scroller}
 * @name BAJL.Scroller.callback
 * @namespace callback functions for {@link BAJL.Scroller}
 */
/**
 * a callback for when scrolling starts
 * @name BAJL.Scroller.callback.onStart
 * @function
 * @param {Number} x    current scroll position (X-coordinate) (px)
 * @param {Number} y    current scroll position (Y-coordinate) (px)
 */
/**
 * a callback for during scrolling continues
 * @name BAJL.Scroller.callback.onScroll
 * @function
 * @param {Number} x    current scroll position (X-coordinate) (px)
 * @param {Number} y    current scroll position (Y-coordinate) (px)
 */
/**
 * a callback for when scrolling is aborted
 * @name BAJL.Scroller.callback.onAbort
 * @function
 * @param {Number} x    current scroll position (X-coordinate) (px)
 * @param {Number} y    current scroll position (Y-coordinate) (px)
 */
/**
 * a callback for when scrolling is completed
 * @name BAJL.Scroller.callback.onComplete
 * @function
 * @param {Number} x    current scroll position (X-coordinate) (px)
 * @param {Number} y    current scroll position (Y-coordinate) (px)
 */

/**
 * callback functions for {@link BAJL.PageScroller}
 * @name BAJL.PageScroller.callback
 * @namespace callback functions for {@link BAJL.PageScroller}
 */
/**
 * a callback for when scrolling starts
 * @name BAJL.PageScroller.callback.onStart
 * @function
 * @param {Number}  x             current scroll position (X-coordinate) (px)
 * @param {Number}  y             current scroll position (Y-coordinate) (px)
 * @param {Element} lastAnchor    an anchor element which is clicked recently
 */
/**
 * a callback for during scrolling continues
 * @name BAJL.PageScroller.callback.onScroll
 * @function
 * @param {Number}  x             current scroll position (X-coordinate) (px)
 * @param {Number}  y             current scroll position (Y-coordinate) (px)
 * @param {Element} lastAnchor    an anchor element which is clicked recently
 */
/**
 * a callback for when scrolling is aborted
 * @name BAJL.PageScroller.callback.onAbort
 * @function
 * @param {Number}  x             current scroll position (X-coordinate) (px)
 * @param {Number}  y             current scroll position (Y-coordinate) (px)
 * @param {Element} lastAnchor    an anchor element which is clicked recently
 */
/**
 * a callback for when scrolling is completed
 * @name BAJL.PageScroller.callback.onComplete
 * @function
 * @param {Number}  x             current scroll position (X-coordinate) (px)
 * @param {Number}  y             current scroll position (Y-coordinate) (px)
 * @param {Element} lastAnchor    an anchor element which is clicked recently
 */



/* -------------------- backward compatibilities -------------------- */

if (BAJL.settings.common.useBackCompat) {
	BAJL.CreateBackCompat({
		  'BA_SMOOTHSCROLL_AS'  : BAJL.PageScroller
		, 'BASmoothScroll'      : function(      offsetX, offsetY, duration, interval, func) { return BAJL.PageScroller }
		, 'BASmoothScrollField' : function(node, offsetX, offsetY, duration, interval, func) { return new BAJL.Scroller(node, offsetX, offsetY, duration) }
	});
}



})(jQuery);
