/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       slide canvas (carrousel).
 *
 *    @version 2.3.20100302
 *    @requires jquery.js
 *    @requires bajl.js
 *    @requires bajl.scroller.js
 *    @requires bajl.slideCanvas.css
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* -------------------- Settings for BAJL.SlideCanvas -------------------- */
/** 
 * settings for {@link BAJL.SlideCanvas}
 * @namespace settings for {@link BAJL.SlideCanvas}
 * @fieldOf BAJL.settings
 * @property {Boolean} autoSetup.enabled             autosetup is enabled or not.
 * @property {Object}  presets                       an associative array of pairs of an expression to find base element of slide canvas and its setting object ({@link BAJL.SlideCanvas.Setting})
 * @property {String}  className.enabled             className for when the slide canvas is enabled.
 * @property {String}  className.discarded           className for when the slide canvas is applied but discarded.
 * @property {String}  buttons.className.disabled    className for the button which is disabled
 * @property {String}  buttons.className.selected    className for the button which is selected
 */
BAJL.settings.SlideCanvas = {
	  'autoSetup' : {
		  'enabled'  : true
	}
	, 'presets' : {
		  'div.slide-canvas' : {
			  'visibleUnit' : 1
			, 'interval'    : 0
			, 'duration'    : 375
			, 'easing'      : 'easeInOutCubic'
			, 'elementExpr' : {
				  'viewport'     : 'div.slide-viewport'
				, 'unit'         : 'div.slide-unit'
				, 'prevButton'   : 'li.slide-prev a'
				, 'nextButton'   : 'li.slide-next a'
				, 'selectButton' : 'li.slide-select a'
			}
		}
	}
	, 'className' : {
		  'enabled'   : 'slide-canvas-enabled'
		, 'discarded' : 'slide-canvas-discarded'
		, 'scrolling' : 'slide-canvas-is-scrolling'
		, 'selected'  : 'slide-canvas-selected-unit'
	}
	, 'buttons' : {
		  'className' : {
			  'disabled' : 'pseudo-disabled'
			, 'selected' : 'pseudo-selected'
		}
	}
};



/* -------------------- jQuery.fn : BAJL_SlideCanvas -------------------- */
/**
 * BAJL.SlideCanvas as jQuery plugin
 * @param {BAJL.SlideCanvas.Setting} setting    an associative array to set the slide canvas up
 * @returns jQuery
 * @type jQuery
 */
jQuery.fn.BAJL_SlideCanvas = function (setting) {
	return this.each(function(){ new BAJL.SlideCanvas(this, setting) });
}



/* -------------------- Class : BAJL.SlideCanvas -------------------- */
/**
 * creates slide canvas (carrousel) behavior controller
 * @class slide canvas (carrousel)
 * @extends BAJL.Observable
 * @param {Element|jQuery|String}    node         base element node for slide canvas
 * @param {BAJL.SlideCanvas.Setting} [setting]    option setting value; an associative array
 */
BAJL.SlideCanvas = function(node, setting) {
	/** base node of this slide canvas
	    @type Element
	    @private
	    @constant */
	this.node         = $(node).get(0);
	/** settings for this slide canvas
	    @type BAJL.SlideCanvas.Setting
		@private
		@constant */
	this.setting      = $.extend(new BAJL.SlideCanvas.Setting, setting);
	/** interval timer object which controls rotation
	    @type BAJL.Interval
	    @private */
	this.rotateTimer  = null;
	/** smooth scroller instance
	    @type BAJL.Scroller
	    @private */
	this.scroller     = null;
	/** an array of elements as slide units.
	    @type Element[]
	    @private */
	this.slideUnits   = [];
	/** an array of instances of select buttons.
	    @type BAJL.SlideCanvas.SelectButton[]
	    @private */
	this.selectButton = [];
	/** an array of instances of prev buttons.
	    @type BAJL.SlideCanvas.StepButton[]
	    @private */
	this.prevButton   = [];
	/** an array of instances of next buttons.
	    @type BAJL.SlideCanvas.StepButton[]
	    @private */
	this.nextButton   = [];
	/** number of currently shown slide unit.
	    @type Number
	    @private */
	this.currentNum   = 0;
	
	if (BAJL.env.isDOMReady) {
		this.init();
		this.startRotate(this.setting.interval);
	}
}

BAJL.SlideCanvas.prototype = new BAJL.Observable;

/* ---------- class methods/props ---------- */

/**
 * an array of instances of this class.
 * @type BAJL.SlideCanvas[]
 */
BAJL.SlideCanvas.instances = [];

/**
 * store an instance created from this class
 * @param {BAJL.SlideCanvas} instance    an instance object to store
 * @return an instance object stored
 * @type BAJL.SlideCanvas
 */
BAJL.SlideCanvas.storeInstance = function(instance) {
	if (!instance || !(instance instanceof BAJL.SlideCanvas)) {
		throw new TypeError('BAJL.SlideCanvas.storeInstance: first argument must be an instance of BAJL.SlideCanvas');
	} else {
		$(instance.node).data('BAJL.SlideCanvas.InstanceID', this.instances.push(instance) - 1);
	}
}

/**
 * get an instance created from this class
 * @param {Number|Element|jQuery|String} arg    instance-ID number, or element node which was applied to this class
 * @return BAJL.SlideCanvas instance
 * @type BAJL.SlideCanvas
 */
BAJL.SlideCanvas.getInstance = function(arg) {
	if (typeof arg == 'number') {
		return this.instances[arg];
	} else if (arg && (arg.nodeType == Node.ELEMENT_NODE || typeof arg.jquery == 'string' || typeof arg == 'string')) {
		return this.instances[$(arg).data('BAJL.SlideCanvas.InstanceID')];
	} else {
		throw new TypeError('BAJL.SlideCanvas.getInstance: first argument must be an ID number, element node, jQuery object, or jQuery selector text.');
	}
}

/**
 * dipose an instance created from this class
 * @param {BAJL.SlideCanvas} instance    an instance object to delete
 * @return an instance object stored
 * @type BAJL.SlideCanvas
 */
BAJL.SlideCanvas.disposeInstance = function(instance) {
	if (!instance || !(instance instanceof BAJL.SlideCanvas)) {
		throw new TypeError('BAJL.SlideCanvas.disposeInstance: first argument must be an instance of BAJL.SlideCanvas');
	} else if (instance.node) {
		BAJL.SlideCanvas.instances.splice($(instance.node).data('BAJL.SlideCanvas.InstanceID'), 1, undefined);
		instance.dispose(true);
	}
}

/* ---------- instance methods ---------- */

/**
 * initialize
 * @private
 */
BAJL.SlideCanvas.prototype.init = function() {
	var $node = $(this.node);

	if (!$node.BAJL_HasElement()) {
		throw new ReferenceError('BAJL.SlideCanvas#init: base element node is not given.');
	} else if (!BAJL.SlideCanvas.getInstance($node)) {
		var CS        = BAJL.settings.SlideCanvas;
		var IS        = this.setting;
		var EE        = IS.elementExpr;
		var $viewport = $node.find(EE.viewport).eq(0);

		// init scroll field.
		this.scroller = new BAJL.Scroller(
			  /* node     */ $viewport
			, /* offsetX  */ (BAJL.ua.isSafari) ? -4 : 0
			, /* offsetY  */ 0
			, /* duration */ IS.duration
			, /* easing   */ IS.easing
		);
		this.initScrollReviser($viewport); // for Gecko, temporary

		// prepare slide nodes.
		this.slideUnits = $viewport.find(EE.unit).get();

		if (this.slideUnits.length == IS.visibleUnit) {
			// indicate discarded className
			$node.addClass(CS.className.discarded);
		} else if (this.slideUnits.length > IS.visibleUnit) {
			// indicate enabled className
			$node.addClass(CS.className.enabled);
			this.createWastingInsForOpera();      // for opera, temporary

			if ($viewport.attr('scrollWidth') == 0) {  // safety and opera, temporary
				$node.removeClass(CS.className.enabled);
			} else {
				// create prev buttons
				this.prevButton = $node.find(EE.prevButton).get().map(function(node) {
					var button = new BAJL.SlideCanvas.StepButton(node, -1);
					button.addCallback('onClick', this.selectBy, this);
					return button;
				}, this);

				// create next buttons
				this.nextButton = $node.find(EE.nextButton).get().map(function(node) {
					var button = new BAJL.SlideCanvas.StepButton(node, +1);
					button.addCallback('onClick', this.selectBy, this);
					return button;
				}, this);

				// create select buttons
				this.selectButton = $node.find(EE.selectButton).get().map(function(node, i) {
					var button = new BAJL.SlideCanvas.SelectButton(node, i);
					button.addCallback('onClick', this.select, this);
					return button;
				}, this);

				// select first slide.
				this.select(0);
			}
		}
		
		BAJL.SlideCanvas.storeInstance(this);
	}
}

/**
 * dispose this instace
 * @param {Boolean} preventRecursion    'true' to prevent recursion
 */
BAJL.SlideCanvas.prototype.dispose = function(preventRecursion) {
	if (!preventRecursion) {
		BAJL.SlideCanvas.disposeInstance(this);
	} else {
		$.each(this, BAJL.Delegate(function(prop) {
			delete this[prop];
			if (typeof this[prop] == 'function') this[prop] = new Function;
		}, this));
	}
}

/**
 * revise scrollLeft on vertical scrolling of user operation (for Gecko only, temporary).
 * @param {Element|jQuery|String} viewport    viewport element node
 * @private
 */
BAJL.SlideCanvas.prototype.initScrollReviser = function(viewport) {
//	if (!BAJL.ua.isGecko) return;
//
//	var lock = false;
//	var posX = 0;
//	this.scroller.addCallback('onStart'   , function(x, y) { lock = true            });
//	this.scroller.addCallback('onComplete', function(x, y) { lock = false; posX = x });
//	$(viewport).scroll(function(e) { if (!lock) e.currentTarget.scrollLeft = posX });
}

/**
 * workaround for Opera (temporary).
 * @private
 */
BAJL.SlideCanvas.prototype.createWastingInsForOpera = function() {
	if (!BAJL.ua.isOpera) return;

	var slide = this.slideUnits[0];
	if (slide) {
		$(slide).append(
			$(document.createElement('ins'))
				.text('.')
				.css({
					  position   : 'relative'
					, left       : '-10px'
					, top        : '-10px'
					, display    : 'block'
					, width      : '1px'
					, height     : '1px'
					, fontSize   : '1px'
					, color      : 'transparent'
					, lineHeight : '0'
				})
		);
	}
}

/**
 * select a slide by slide number
 * @param {Number} num    slide number
 */
BAJL.SlideCanvas.prototype.select = function(num) {
	if (typeof num != 'number') {
		return;
	} else {
		this.startRotate();  // reset timer and continue rotation.

		var slide  = this.slideUnits[num];
		var cnames = BAJL.settings.SlideCanvas.className;
		if (slide) {
			this.currentNum = num;

			$(this.slideUnits)
				.removeClass(cnames.selected)
				.slice(num, num + this.setting.visibleUnit)
				.addClass(cnames.selected);
			this.doCallback('onStart', this.currentNum);
			
			// scroll slides.
			this.scroller
				.addCallback('onStart', function() {
					$(this.node).addClass(cnames.scrolling);
				} , this, 'disposable')
				.addCallback('onComplete', function() {
					$(this.node).removeClass(cnames.scrolling);
					this.doCallback('onSelect', this.currentNum);
				} , this, 'disposable')
				.abort()
				.scrollToNode(slide);

			// select/unselect select buttons
			this.selectButton.forEach(function(btn, i) {
				if (num == i) btn.select  ();
				else          btn.unselect();
			});

			// enable/disable prev/next buttons
			var min = 0;
			var max = this.slideUnits.length - this.setting.visibleUnit;
			this.prevButton.forEach(function(btn) {
				if (num == min) btn.disable();
				else            btn.enable ();
			});
			this.nextButton.forEach(function(btn) {
				if (num == max) btn.disable();
				else            btn.enable ();
			});
		}
	}
}

/**
 * select slide by difference from current selected slide number
 * @param {Number} step    deference from current selected slide number; typically '+1' or '-1'
 */
BAJL.SlideCanvas.prototype.selectBy = function(step) {
	if (typeof step != 'number' || step == 0) {
		return;
	} else {
		var num = this.currentNum + step;
		if (!this.slideUnits[num]) {
			num = (step > 0) ? 0 : this.slideUnits.length + step;
		}
		this.select(num);
	}
}

/**
 * start rotating of slides
 * @param {Number} [interval]    new interval (in milliseconds); if 0 given, or current interval time is 0, it doesn't start rotation!
 */
BAJL.SlideCanvas.prototype.startRotate = function(interval) {
	this.stopRotate();
	if (typeof interval == 'number') {
		this.setting.interval = interval;
	}
	if (this.setting.interval > 0) {
		this.rotateTimer = new BAJL.Interval(function() { this.selectBy(1) }, this.setting.interval, this);
	}
}

/**
 * stop rotating of slides
 */
BAJL.SlideCanvas.prototype.stopRotate = function() {
	if (this.rotateTimer) {
		this.rotateTimer.clear();
		this.rotateTimer = null;
	}
}



/* -------------------- Class : BAJL.SlideCanvas.StepButton -------------------- */
/**
 * creates step button for {@link BAJL.SlideCanvas}
 * @class step button for {@link BAJL.SlideCanvas}
 * @extends BAJL.Observable
 * @param {Element|jQuery|String}  node       base element node for step button
 * @param {Number}                [step=1]    number to forward/backward selection of the slides when this button is clicked
 */
BAJL.SlideCanvas.StepButton = function(node, step) {
	/** base element node for this button
	    @type Element
	    @private
	    @constant */
	this.node     = $(node).get(0);
	/** number to forward/backward selection of the slides when this button is clicked
	    @type Number
	    @private
	    @constant */
	this.step     = Number(step) || 1;
	/** is this button currently disabled?
	    @type Boolean
	    @private */
	this.disabled = false;

	if (BAJL.env.isDOMReady) {
		this.init();
	}
}

BAJL.SlideCanvas.StepButton.prototype = new BAJL.Observable;

/**
 * initialize
 * @private
 */
BAJL.SlideCanvas.StepButton.prototype.init = function() {
	var $node = $(this.node);
	if (!$node.BAJL_HasElement()) {
		throw new ReferenceError('BAJL.SlideCanvas.StepButton#init: base element node is not given.');
	} else {
		$node
			.click(BAJL.Delegate(this.onclick, this))
			.css  ('outline'  , 'none')
			.attr ('hideFocus', true  ); // workaround for IE
	}
}

/**
 * event handler for when the button is clicked.
 * @param {Event} e    event object
 * @event
 * @private
 */
BAJL.SlideCanvas.StepButton.prototype.onclick = function(e) {
	e.preventDefault();
	if (!this.disabled) this.doCallback('onClick', this.step);
}

/**
 * enable this button
 * @returns this instance
 * @type BAJL.SlideCanvas.StepButton
 */
BAJL.SlideCanvas.StepButton.prototype.enable = function() {
	this.disabled = false;
	$(this.node).removeClass(BAJL.settings.SlideCanvas.buttons.className.disabled);
	return this;
}

/**
 * disable this button
 * @returns this instance
 * @type BAJL.SlideCanvas.StepButton
 */
BAJL.SlideCanvas.StepButton.prototype.disable = function() {
	this.disabled = true;
	$(this.node).addClass(BAJL.settings.SlideCanvas.buttons.className.disabled);
	return this;
}



/* -------------------- Class : BAJL.SlideCanvas.SelectButton -------------------- */
/**
 * creates select button for {@link BAJL.SlideCanvas}
 * @class select button for {@link BAJL.SlideCanvas}
 * @extends BAJL.Observable
 * @param {Element|jQuery|String} node     base element node for select button
 * @param {Number}                index    number to select a slide when this button is clicked.
 */
BAJL.SlideCanvas.SelectButton = function(node, index) {
	/** base element node for this button
	    @type Element
	    @private
	    @constant */
	this.node = $(node).get(0);
	/** number to select a slide when this button is clicked.
	    @type Number
	    @private
	    @constant */
	this.index    = index;
	/** is this button currently selected?
	    @type Boolean
	    @private */
	this.selected = false;

	if (BAJL.env.isDOMReady) {
		this.init();
	}
}

BAJL.SlideCanvas.SelectButton.prototype = new BAJL.Observable;

BAJL.SlideCanvas.SelectButton.prototype.init = function() {
	var $node = $(this.node);
	if (!$node.BAJL_HasElement()) {
		throw new ReferenceError('BAJL.SlideCanvas.SelectButton#init: base element node is not given.');
	} else {
		$node
			.click(BAJL.Delegate(this.onclick, this))
			.css  ('outline'  , 'none')
			.attr ('hideFocus', true  ); // workaround for IE
	}
}

/**
 * event handler for when the button is clicked.
 * @param {Event} e    event object
 * @event
 * @private
 */
BAJL.SlideCanvas.SelectButton.prototype.onclick = function(e) {
	e.preventDefault();
	this.doCallback('onClick', this.index);
}

/**
 * select this button
 * @returns this instance
 * @type BAJL.SlideCanvas.SelectButton
 */
BAJL.SlideCanvas.SelectButton.prototype.select = function() {
	this.selected = true;
	$(this.node).addClass(BAJL.settings.SlideCanvas.buttons.className.selected);
	return this;
}

/**
 * unselect this button
 * @returns this instance
 * @type BAJL.SlideCanvas.SelectButton
 */
BAJL.SlideCanvas.SelectButton.prototype.unselect = function() {
	this.selected = false;
	$(this.node).removeClass(BAJL.settings.SlideCanvas.buttons.className.selected);
	return this;
}



/* -------------------- Class : BAJL.SlideCanvas.Setting -------------------- */
/**
 * setting data object for {@link BAJL.SlideCanvas}
 * @class setting data object for {@link BAJL.SlideCanvas}
 */
BAJL.SlideCanvas.Setting = function() {
	/** number of visible units at a time.
	    @type Number */
	this.visibleUnit = 1;
	/** milliseconds of rotate interval; if 0 then it doesn't rotate.
	    @type Number */
	this.interval    = 0;
	/** milliseconds of duration of scroll animation.
	    @type Number */
	this.duration    = 375;
	/** easing function name existing in jQuery.easing.
	    @type String */
	this.easing      = 'easeInOutCubic';
	/** an associative array of pairs of name and expression to find required elements in slide canvas.
	    @type Object */
	this.elementExpr = {
		  'viewport'     : 'div.slide-viewport'
		, 'unit'         : 'div.slide-unit'
		, 'prevButton'   : 'li.slide-prev a'
		, 'nextButton'   : 'li.slide-next a'
		, 'selectButton' : 'li.slide-select a'
	}
};



/* -------------------- AutoSetup -------------------- */

(function() {
	var settings = BAJL.settings.SlideCanvas;
	var sheets   = BAJL.StyleSheets();
	if (settings.autoSetup.enabled) {
		$.each(settings.presets, function(expr, setting) {
			sheets.insertRule(expr + '{ visibility: hidden }');
			$(function() {
				$(expr).BAJL_SlideCanvas(setting).css('visibility', 'visible');
			});
		});
	}
})();




/* -------------------- for JSDoc toolkit output -------------------- */
/**
 * callback functions for {@link BAJL.SlideCanvas}
 * @name BAJL.SlideCanvas.callback
 * @namespace callback functions for {@link BAJL.SlideCanvas}
 */
/**
 * a callback for when one of the slide scrolling is started.
 * @name BAJL.SlideCanvas.callback.onStart
 * @function
 * @param {Number} index    index number of a slide which is currently selected
 */
/**
 * a callback for when one of the slides is selected.
 * @name BAJL.SlideCanvas.callback.onSelect
 * @function
 * @param {Number} index    index number of a slide which is currently selected
 */



})(jQuery);

