/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Smooth Scroller
 *
 *    @version 1.0.20100707
 *    @requires jquery.js
 *    @requires jquery.easing.js
 *    @requires jquery.mousewheel.js
 *    @requires cwj.js
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* -------------------- Settings for CWJL.PageScroller -------------------- */
/** 
 * settings for {@link CWJL.PageScroller}
 * @namespace settings for {@link CWJL.PageScroller}
 * @fieldOf CWJL.settings
 * @property {Boolean} enabled     flag for enable auto setup CWJL.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 {CWJL.PageScroller.callback.onStart}    onStart       a callback for when scrolling starts
 * @property {CWJL.PageScroller.callback.onScroll}   onScroll      a callback for during scrolling continues
 * @property {CWJL.PageScroller.callback.onAbort}    onAbort       a callback for when scrolling is aborted
 * @property {CWJL.PageScroller.callback.onComplete} onComplete    a callback for when scrolling is completed
 */
CWJL.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 = CWJL.ua;
if (lastAnchor && (B.isGecko || B.isIE || (B.isSafari && B.version > 522))) {
location.href = lastAnchor.href;
}
}
};



/* -------------------- Class : CWJL.Scroller -------------------- */
/**
 * provide smooth scroll behavior to the scrollable block elements.
 * @class smooth scroller
 * @extends CWJL.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
 */
CWJL.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 (CWJL.env.isDOMReady) {
this.init();
}
}

CWJL.Scroller.prototype = new CWJL.Observable;

/**
 * initialize.
 * @private
 */
CWJL.Scroller.prototype.init = function() {
if (!this.node || this.node.nodeType != Node.ELEMENT_NODE) {
throw new ReferenceError('CWJL.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     (CWJL.Delegate(function() { this.abort() }, this));
$node.mousewheel(CWJL.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 CWJL.Scroller
 */
CWJL.Scroller.prototype.scrollTo = function(x, y) {
if (isNaN(x) || isNaN(y)) {
throw new TypeError('CWJL.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     : CWJL.Delegate(this.step    , this)
, complete : CWJL.Delegate(this.complete, this)
};

var $node = (CWJL.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 CWJL.Scroller
 */
CWJL.Scroller.prototype.scrollToNode = function(node) {
node = $(node).get(0);
if (!node || node.nodeType != Node.ELEMENT_NODE) {
throw new TypeError('CWJL.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 + (CWJL.ua.isSafari ? 4 : 0)
, nodePos.top  + baseSrl.top  - basePos.top
);
}
}
return this;
}

/**
 * callback func for scrolling
 * @private
 */
CWJL.Scroller.prototype.step = function() {
this.doCallbackByName('onScroll');
}

/**
 * callback func for completed scrolling
 * @private
 */
CWJL.Scroller.prototype.complete = function() {
if (this.scrolling) {
this.scrolling = false;
this.doCallbackByName('onComplete');
}
}

/**
 * abort scrolling.
 * @return this instance
 * @type CWJL.Scroller
 */
CWJL.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
 */
CWJL.Scroller.prototype.doCallbackByName = function(name) {
this.doCallback(name, this.node.scrollLeft, this.node.scrollTop);
}



/* -------------------- Setup : CWJL.PageScroller -------------------- */

$(function() {
var S = CWJL.settings.PageScroller;

if (S.enabled) {
// create instance
var node     = CWJL.ua.isQuirksMode ? document.body : document.documentElement;
var scroller = new CWJL.Scroller(node, S.offsetX, S.offsetY, S.duration, S.easing);
scroller.lastAnchor = null;  // stores last clicked anchor element

/** page scroller; an instance of {@link CWJL.Scroller}.
    @field
    @type CWJL.Scroller */
CWJL.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 CWJL.Scroller}
 * @name CWJL.Scroller.callback
 * @namespace callback functions for {@link CWJL.Scroller}
 */
/**
 * a callback for when scrolling starts
 * @name CWJL.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 CWJL.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 CWJL.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 CWJL.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 CWJL.PageScroller}
 * @name CWJL.PageScroller.callback
 * @namespace callback functions for {@link CWJL.PageScroller}
 */
/**
 * a callback for when scrolling starts
 * @name CWJL.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 CWJL.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 CWJL.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 CWJL.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 (CWJL.settings.common.useBackCompat) {
CWJL.CreateBackCompat({
  'CWJ_SMOOTHSCROLL_AS'  : CWJL.PageScroller
, 'CWJSmoothScroll'      : function(      offsetX, offsetY, duration, interval, func) { return CWJL.PageScroller }
, 'CWJSmoothScrollField' : function(node, offsetX, offsetY, duration, interval, func) { return new CWJL.Scroller(node, offsetX, offsetY, duration) }
});
}



})(jQuery);
