/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Smooth Scroller
 *
 *    @version rev001 2009/08/26
 *    @requires common.js
 */
/* -------------------------------------------------------------------------- */


var CWJ_SMOOTHSCROLL_AS;



/* -------------------- Settings for CWJSmoothScrollAutoSetup -------------------- */

var CWJ_SMOOTHSCROLL_AS_ENABLED           = true;
var CWJ_SMOOTHSCROLL_AS_FIND_ATONCE       = false;
var CWJ_SMOOTHSCROLL_AS_USE_POSTPROCESS   = true;
var CWJ_SMOOTHSCROLL_AS_OFFSET_X          = 0;
var CWJ_SMOOTHSCROLL_AS_OFFSET_Y          = 0;
var CWJ_SMOOTHSCROLL_AS_DURATION          = 1000;
var CWJ_SMOOTHSCROLL_AS_INTERVAL          = 10;
var CWJ_SMOOTHSCROLL_AS_IGNORE_NODE_CNAME = 'noSmoothScroll'

var CWJ_SMOOTHSCROLL_AS_EASING_FUNC       = null;   // function(t, b, c, d) { ... };

var CWJ_SMOOTHSCROLL_AS_POSTPROCESS_FUNC  = function(x, y) {
	if (CWJ.ua.isGecko || CWJ.ua.isWinIE || (CWJ.ua.isSafari && CWJ.ua.revision > 522)) {
		location.href = CWJ_SMOOTHSCROLL_AS.fromNode.getAttributeCWJ('href');
	}
}



/* -------------------- Constructor : CWJSmoothScroll inherits CWJObservable -------------------- */

function CWJSmoothScroll(offsetX, offsetY, duration, interval, func) {
	this.offsetX     = (typeof offsetX == 'number') ? offsetX  : 0;
	this.offsetY     = (typeof offsetY == 'number') ? offsetY  : 0;
	this.duration    = (duration > 0) ? duration : 1500;
	this.interval    = (interval > 0) ? interval : 5;
	this.fromPos     = { X : 0, Y : 0 };
	this.toPos       = { X : 0, Y : 0 };
	this.easingTimer = null;
	this.elapseTimer = null;

	if (CWJ.env.isDOMReady) {
		this.setEasingFunc(func);
	}
}

CWJSmoothScroll.prototype = new CWJObservable;

CWJSmoothScroll.prototype.scrollTo = function(x, y) {
	if (isNaN(x) || isNaN(y)) {
		throw 'CWJSmoothScroll.scrollTo: arguments must be a number';
	} else {
		this.abort();
		var geom = CWJGetGeometry();
		var maxX = (geom.pageW > geom.windowW) ? geom.pageW - geom.windowW : 0;
		var maxY = (geom.pageH > geom.windowH) ? geom.pageH - geom.windowH : 0;
		var posX = Math.round((x + this.offsetX) * geom.zoom);
		var posY = Math.round((y + this.offsetY) * geom.zoom);
		this.toPos = {
			X : (posX < 0) ? 0 : (posX > maxX) ? maxX : posX,
			Y : (posY < 0) ? 0 : (posY > maxY) ? maxY : posY
		}
		this.start();
	}
}

CWJSmoothScroll.prototype.scrollToNode = function(node) {
	if (!node || node.instanceOf != 'CWJElement') {
		throw 'CWJSmoothScroll.scrollToNode: first argument must be a CWJElement node';
	} else {
		var nodePos = node.getAbsoluteOffsetCWJ();
		this.scrollTo(nodePos.X, nodePos.Y);
	}
}

CWJSmoothScroll.prototype.start = function() {
	this.clearTimer();

	var geom     = CWJGetGeometry();
	this.fromPos = {
		X : geom.scrollX,
		Y : geom.scrollY
	};
	if (this.fromPos.X != this.toPos.X || this.fromPos.Y != this.toPos.Y) {
		this.elapseTimer = new CWJTimer;
		this.easingTimer = new CWJSetInterval(this.scrollProcess, this.interval, this);
		this.doCallBack('onStart'   , this.fromPos.X, this.fromPos.Y);
	} else {
		this.doCallBack('onComplete', geom.scrollX, geom.scrollY);
	}
}

CWJSmoothScroll.prototype.scrollProcess = function() {
	var elapse    = Math.min(this.elapseTimer.getTime(), this.duration);
	var distance  = {
		X : this.toPos.X - this.fromPos.X,
		Y : this.toPos.Y - this.fromPos.Y
	};
	var factor    = {
		X : (distance.X > 0) ? 1 : -1,
		Y : (distance.Y > 0) ? 1 : -1
	};
	var newPos    = {
		X : this.fromPos.X + this.easingFunc(elapse, 0, Math.abs(distance.X), this.duration) * factor.X,
		Y : this.fromPos.Y + this.easingFunc(elapse, 0, Math.abs(distance.Y), this.duration) * factor.Y
	};
	var completed = {
		X : Boolean(Math.abs(this.toPos.X - newPos.X) < 1),
		Y : Boolean(Math.abs(this.toPos.Y - newPos.Y) < 1)
	};
	if (elapse < this.duration && (!completed.X || !completed.Y)) {
		this.realScrollTo(newPos.X, newPos.Y);
		this.doCallBack('onScroll', newPos.X, newPos.Y);
	} else {
		this.realScrollTo(this.toPos.X, this.toPos.Y);
		this.doCallBack('onScroll', this.toPos.X, this.toPos.Y);
		this.stop();
	}
}

CWJSmoothScroll.prototype.realScrollTo = function(x, y) {
	window.scrollTo(x, y);
}

CWJSmoothScroll.prototype.stop = function() {
	if (this.elapseTimer) {
		this.clearTimer();
		var geom = CWJGetGeometry();
		this.doCallBack('onComplete', geom.scrollX, geom.scrollY);
	}
}

CWJSmoothScroll.prototype.abort = function() {
	if (this.elapseTimer) {
		this.clearTimer();
		var geom = CWJGetGeometry();
		this.doCallBack('onAbort'   , geom.scrollX, geom.scrollY);
	}
}

CWJSmoothScroll.prototype.setEasingFunc = function(func) {
	if (typeof func == 'function' && typeof func(0, 0, 0, 0) == 'number') {
		this.easingFunc = func;
	}
}

CWJSmoothScroll.prototype.easingFunc = function(t, b, c, d) {
	// quartic easing out
	var ts = (t /= d) * t;
	var tc = ts * t;
	return b + c * (tc * ts + -5 * ts * ts + 10 * tc -10 * ts + 5 * t);
}

CWJSmoothScroll.prototype.clearTimer = function() {
	if (this.elapseTimer) {
		this.elapseTimer = null;
	}
	if (this.easingTimer) {
		this.easingTimer.clearTimer();
		this.easingTimer = null;
	}
}






/* -------------------- Constructor : CWJSmoothScrollField inherits CWJSmoothScroll -------------------- */

function CWJSmoothScrollField(node, offsetX, offsetY, duration, interval, func) {
	if (!node || node.instanceOf != 'CWJElement') {
		throw 'CWJSmoothScrollField: first argument must be a CWJElement node.';
	}

	this.node    = node;
	this.offsetX     = (typeof offsetX == 'number') ? offsetX  : 0;
	this.offsetY     = (typeof offsetY == 'number') ? offsetY  : 0;
	this.duration    = (duration > 0) ? duration : 1500;
	this.interval    = (interval > 0) ? interval : 5;
	this.fromPos     = { X : 0, Y : 0 };
	this.toPos       = { X : 0, Y : 0 };
	this.easingTimer = null;
	this.elapseTimer = null;

	if (CWJ.env.isDOMReady) {
		this.setEasingFunc(func);
	}
}

CWJSmoothScrollField.prototype = new CWJSmoothScroll;

CWJSmoothScrollField.prototype.scrollTo = function(x, y) {
	if (isNaN(x) || isNaN(y)) {
		throw 'CWJSmoothScrollField.scrollTo: arguments must be a number';
	} else {
		this.abort();
		var geom = CWJGetGeometry();
		var maxX = this.node.scrollWidth  - this.node.offsetWidth ; if (maxX < 0) maxX = 0;
		var maxY = this.node.scrollHeight - this.node.offsetHeight; if (maxY < 0) maxY = 0;
		var posX = Math.round((x + this.offsetX) * geom.zoom);
		var posY = Math.round((y + this.offsetY) * geom.zoom);
		this.toPos = {
			X : (posX < 0) ? 0 : (posX > maxX) ? maxX : posX,
			Y : (posY < 0) ? 0 : (posY > maxY) ? maxY : posY
		}
		this.start();
	}
}

CWJSmoothScrollField.prototype.scrollToNode = function(node) {
	if (!node || node.instanceOf != 'CWJElement') {
		throw 'CWJSmoothScrollField.scrollToNode: first argument must be a CWJElement node';
	} else {
		var fieldPos = this.node.getAbsoluteOffsetCWJ();
		var nodePos  =      node.getAbsoluteOffsetCWJ();
		var reviseX  = (CWJ.ua.isSafari) ? 4 : 0;
		this.scrollTo(nodePos.X - fieldPos.X + reviseX, nodePos.Y - fieldPos.Y);
	}
}

CWJSmoothScrollField.prototype.start = function() {
	this.clearTimer();

	this.fromPos = {
		X : this.node.scrollLeft,
		Y : this.node.scrollTop
	};
	if (this.fromPos.X != this.toPos.X || this.fromPos.Y != this.toPos.Y) {
		this.elapseTimer = new CWJTimer;
		this.easingTimer = new CWJSetInterval(this.scrollProcess, this.interval, this);
		this.doCallBack('onStart', this.fromPos.X, this.fromPos.Y);
	} 
}

CWJSmoothScrollField.prototype.stop = function() {
	if (this.elapseTimer) {
		this.clearTimer();
		this.doCallBack('onComplete', this.node.scrollLeft, this.node.scrollTop);
	}
}

CWJSmoothScrollField.prototype.abort = function() {
	if (this.elapseTimer) {
		this.clearTimer();
		this.doCallBack('onAbort'   , this.node.scrollLeft, this.node.scrollTop);
	}
}

CWJSmoothScrollField.prototype.realScrollTo = function(x, y) {
	this.node.scrollLeft = x;
	this.node.scrollTop  = y;
}






/* -------------------- Function : CWJSmoothScrollAutoSetup inherit CWJSmoothScroll -------------------- */

function CWJSmoothScrollAutoSetup() {
	this.offsetX     = (CWJ_SMOOTHSCROLL_AS_OFFSET_X > 0) ? CWJ_SMOOTHSCROLL_AS_OFFSET_X : 0;
	this.offsetY     = (CWJ_SMOOTHSCROLL_AS_OFFSET_Y > 0) ? CWJ_SMOOTHSCROLL_AS_OFFSET_Y : 0;
	this.duration    = (CWJ_SMOOTHSCROLL_AS_DURATION > 0) ? CWJ_SMOOTHSCROLL_AS_DURATION : 1500;
	this.interval    = (CWJ_SMOOTHSCROLL_AS_INTERVAL > 0) ? CWJ_SMOOTHSCROLL_AS_INTERVAL : 5;
	this.fromPos     = { X : 0, Y : 0 };
	this.toPos       = { X : 0, Y : 0 };
	this.easingTimer = null;
	this.elapseTimer = null;
	this.fromNode = null;

	if (CWJ.env.isDOMReady) {
		this.setEasingFunc(CWJ_SMOOTHSCROLL_AS_EASING_FUNC);
		this.init();
	}
}

CWJSmoothScrollAutoSetup.prototype = new CWJSmoothScroll;

CWJSmoothScrollAutoSetup.prototype.init = function() {
	// setup event handlers
	document.addEventListenerCWJ('click'     , this.abort, this);
	document.addEventListenerCWJ('mousewheel', this.abort, this);

	if (CWJ_SMOOTHSCROLL_AS_FIND_ATONCE) {
		var nodes = CWJConcatNodeList(['a', 'area'].map(function(tagName) { return document.getElementsByTagNameCWJ(tagName) }));
		nodes.forEach(function(node) {
			node.addEventListenerCWJ('click', function(e) {
				if (!this.timer) {
					this.startByAnchor(e.currentTarget, e);
				}
			}, this);
		}, this);
	} else {
		document.addEventListenerCWJ('click', function(e) {
			if (!this.timer) {
				var node = e.target;
				if (['a', 'area'].indexOf(node.nodeName.toLowerCase()) == -1) {
					node = node.getAncestorsByTagNameCWJ('a')[0] || node.getAncestorsByTagNameCWJ('area')[0];
				}
				if (node) {
					this.startByAnchor(node, e);
				}
			}
		}, this);
	}

	// setup callback funcs
	if (CWJ_SMOOTHSCROLL_AS_USE_POSTPROCESS) {
		this.addCallBack('onComplete', CWJ_SMOOTHSCROLL_AS_POSTPROCESS_FUNC);
	}
}

CWJSmoothScrollAutoSetup.prototype.startByAnchor = function(anchor, e) {
	var flagID = this.getInternalLinkFragmentID(anchor);
	if (flagID) {
		var target = document.getElementByIdCWJ(flagID) || document.getElementsByNameCWJ(flagID)[0];
		if (target && this.validateAnchor(anchor)) {
			anchor.blur();
			if (e) {
				e.preventDefault();
				e.stopPropagation();
			}
			this.fromNode = anchor;
			this.scrollToNode(target);
		}
	}
}

CWJSmoothScrollAutoSetup.prototype.getInternalLinkFragmentID = function(node) {
	var ret = '';
	if (node && typeof node.nodeType == 'number') {
		var href = node.getAttributeCWJ('href');
		if (href && href.match(/#/)) {
			href = href.split('#');
			if (!href[0] || href[0] == location.href.split('#')[0]) {
				ret = href[1];
			}
		}
	}
	return ret;
}

CWJSmoothScrollAutoSetup.prototype.validateAnchor = function (node) {
	return !node.hasClassNameCWJ(CWJ_SMOOTHSCROLL_AS_IGNORE_NODE_CNAME);
}






/* -------------------- Main : register start-up -------------------- */

if (typeof CWJ == 'object' && CWJ.ua.isDOMReady && CWJ_SMOOTHSCROLL_AS_ENABLED) {
	CWJAddOnload(function() {
		CWJ_SMOOTHSCROLL_AS = CWJSingleton(CWJSmoothScrollAutoSetup);
	});
}
