/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Image Switcher. (tentative, planned to extend BATabView)
 *
 *    @version rev013.2008-04-04
 *    @requires common.js
 *    @requires rollover.js  (when use image switch buttons)
 */
/* -------------------------------------------------------------------------- */



/* --------------- Constructor : BAImageSwitcher --------------- */
/**
 * provides image switching interface.
 * @class image switcher
 * @constructor
 * @param {String} targetID      switching image node ID. (required)
 * @param {String} switcherID    image switch controll button (image) node ID.
 */
function BAImageSwitcher(targetID, switcherID) {
	/** index number of the currently shown image.
	    @type Number @private */
	this.currentIdx  = 0;
	/** array of Image objects for switchng.
	    @type Array @private */
	this.imgs        = [];
	/** image element node which will be replaced 'img src'
	    @type BAElement @private */
	this.imgNode     = null;
	/** an instance of image switch controller.
	    @type BAImageSwitchController @private */
	this.controller  = null;
	/** image cross fader instance.
	    @type BAImageCrossFader @private */
	this.crossFader  = null;
	/** store point of the timer for image rotation.
	    @type BASetInterval @private */
	this.rotateTimer = null;
	
	if (arguments.length) {
		this.init(targetID, switcherID);
	}
}

BAImageSwitcher.prototype = {
	/**
	 * initialize.
	 * @param {String} targetID      ID of container node of the switching image node. (required)
	 * @param {String} switcherID    ID of container node of the image switch controll buttons.
	 * @private
	 */
	init : function(targetID, switcherID) {
		if (typeof targetID != 'string' || !targetID) {
			throw 'BAImageSwitcher.init: first argument must be a string.';
		} else {
			var imgNode = document.getElementByIdBA(targetID);
			if (!imgNode || imgNode.nodeName.toLowerCase() != 'img') {
				throw 'BAImageSwitcher.init: node which specified by ID is not exist, or not an img element node.';
			} else {
				this.imgNode = imgNode;
				if (switcherID) {
					this.controller = new BAImageSwitchController(switcherID, this);
				}
			}
		}
	},

	/** 
	 * add images to switch.
	 * @param {String} args    'arguments object' : URLs to add image to switch (required)
	 */
	addImage : function(args) {
		if (Array.prototype.some.call(arguments, function(src) { return (typeof src != 'string') })) {
			throw 'BAImageSwitcher.addImage: each of arguments must be a string (image URL).';
		} else {
			Array.prototype.forEach.call(arguments, function(src) {
				if (this.imgs.indexOf(src) == -1) {
					this.imgs.push({src : src});
					if (this.controller) {
						this.controller.addButton(this.imgs.length - 1);
					}
				}
			}, this);
		}
	},

	/** 
	 * turn on 'image cross fader'.
	 * @param {Number} step        an accrual of opacity value of the next image.
	 * @param {Number} interval    an increase interval of opacity value of the next image.
	 */
	enableCrossFader : function(step, interval) {
		if (!this.imgNode
		    || BA.ua.isIE     && !this.imgNode.runtimeStyle /* WinIE 5.0, MacIE all */
		    || BA.ua.isSafari && BA.ua.revision < 125       /* Safari before 1.2    */
		    || BA.ua.isOpera  && BA.ua.revision < 9.0       /* Opera  before 9.0    */) {
				return;
		} else if (!this.crossFader) {
			this.crossFader = new BAImageCrossFader(this.imgNode);
			this.setCrossFaderParam(step, interval);
		}
	},

	/** 
	 * turn off 'image cross fader'.
	 */
	disableCrossFader : function() {
		this.crossFader = null;
	},

	/** 
	 * set cross fader parameters.
	 * @param {Number} step        an accrual of opacity value of the next image.
	 * @param {Number} interval    an increase interval of opacity value of the next image.
	 */
	setCrossFaderParam : function(step, interval) {
		if (this.crossFader) {
			this.crossFader.setParam(step, interval);
		}
	},

	/**
	 * switch image by index number.
	 * @param {Number} idx    index number of the image to switch. (required)
	 */
	switchTo : function(idx) {
		var imgToSwitch = this.imgs[idx];
		if (!imgToSwitch) {
			throw 'BAImageSwitcher.switchTo: ' + ((typeof idx != 'number') ? 'first argument must be a number.' : 'specified index number is unavailable.');
		} else if (this.imgNode /* && imgToSwitch.complete */) {
			var srcToSwitch = imgToSwitch.src;
			if (this.crossFader) {
				this.crossFader.startTransition(srcToSwitch);
			} else {
				this.imgNode.src = srcToSwitch;
			}
			if (this.controller) {
				this.controller.activate(idx);
			}
			this.currentIdx = idx;
		}
	},

	/**
	 * switch image at random.
	 */
	switchAtRandom : function() {
		var max = this.imgs.length - 1;
		var idx = this.currentIdx;
		while (idx == this.currentIdx && max > 0) {
			idx = Math.floor(Math.random() * (max + 1));
		}
		this.switchTo(idx);
	},

	/**
	 * start rotation of the images.
	 * @param {Number} ms       rotation interval (ms) (required)
	 * @param {Number} start    index number of the starting image
	 */
	startRotate : function(ms, start) {
		if (typeof ms != 'number' || ms < 0) {
			ms = 0;
		}
		if (typeof start != 'number' || start < 0 || start >= this.imgs.length) {
			start = this.currentIdx;
		}
		this.stopRotate();
		this.switchTo(start);
		this.rotateTimer = new BASetInterval(function() {
			var idx = this.currentIdx + 1;
			if (idx == this.imgs.length) {
				idx = 0;
			}
			this.switchTo(idx);
		}, ms, this);
	},

	/**
	 * start randomly rotation of the images.
	 * @param {Number}  ms    rotation interval (ms)
	 */
	startRotateRandomly : function(ms) {
		if (typeof ms != 'number' || ms < 0) {
			ms = 0;
		}

		function randomSort(arr){
			for (var i = 0, n = arr.length; i < n; i++) {
				arr.reverse();
				arr.splice(parseInt(Math.random() * (n - 1)), 0, arr.pop());
			}
			arr.sort(function(){ return parseInt(Math.random() * 3 - 1) });
		}

		randomSort(this.imgs);

		if (typeof start != 'number' || start < 0 || start >= this.imgs.length) {
			start = this.currentIdx;
		}

		this.stopRotate();
		this.switchTo(start);
		BAPreloadImage(this.imgs[start + 1].src);
//		this.switchAtRandom();
		this.rotateTimer = new BASetInterval(function() {
			var idx = this.currentIdx + 1;
			if (idx == this.imgs.length) {
				idx = 0;
				randomSort(this.imgs);
			}
			this.switchTo(idx);

			var next = idx + 1;
			if (next == this.imgs.length) {
				next = 0;
			}
			BAPreloadImage(this.imgs[next].src);
//			this.switchAtRandom();
		}, ms, this);
	},

	/**
	 * stop rotation of the images.
	 */
	stopRotate : function() {
		if (this.rotateTimer) {
			this.rotateTimer.clearTimer();
		}
	}
}






/* --------------- Constructor : BAImageSwitchController --------------- */
/**
 * provides image switch controller.
 * @class image switch controller
 * @constructor
 * @param {String}          switcherID         image switch controll button (image) node ID. (required)
 * @param {BAImageSwitcher} relatedSwitcher    related object (BAImageSwitcher instance).
 */
function BAImageSwitchController(switcherID, relatedSwitcher) {
	/** related object (BAImageSwitcher instance).
	    @type BAImageSwitcher @private @const */
	this.relatedSwitcher = relatedSwitcher;
	/** element node as the button.
	    @type BAElement @private */
	this.btnNode         = null;
	/** array of BARollover instances whose constructed by button nodes.
	    @type Array @private */
	this.buttons         = [];
	/** associative array, rollover settings for BARollover.
	    @type Object @private @const */
	this.btnStatus       = {
		'normal' : '',
		'active' : 'a'
	};
	/** class name for button nodes.
	    @type String @private @const */
	this.btnNodeCName    = 'BAImageSwitchControllerButton';
	
	this.init(switcherID);
}

BAImageSwitchController.prototype = {
	/**
	 * initialize.
	 * @param {String} switcherID    ID of container node of the image switch controll buttons. (required)
	 * @private
	 */
	init : function(switcherID) {
		if (typeof switcherID != 'string' || !switcherID) {
			throw 'BAImageSwitchController.init: first argument must be a string.';
		} else {
			var btnNode = document.getElementByIdBA(switcherID);
			if (btnNode) {
				this.btnNode = btnNode;
			}
		}
	},

	/**
	 * add image switching button.
	 * @param {Number} idx    index number of the image which related to added new button. (required)
	 */
	addButton : function(idx) {
		if (typeof idx != 'number' || idx < 0) {
			throw 'BAImageSwitchController.addButton: first argument must be an integer more than 0.';
		} else if (this.btnNode) {
			var node = BARegisterDOMMethodsTo(this.btnNode.cloneNode(true), true);
			
			var btns = this.btnNode.getParentNodeBA().getElementsByClassNameBA(this.btnNodeCName);
			var last = btns[btns.length -1] || this.btnNode;
			this.btnNode.parentNode.insertBefore(node, last.nextSibling);
			
			node.__BAImageSwitchController_btnIdx__ = idx;
			node.removeAttribute('id');
			node.appendClassNameBA(this.btnNodeCName);
			node.addEventListenerBA('click', function(e) {
				this.relatedSwitcher.switchTo(e.currentTarget.__BAImageSwitchController_btnIdx__);
			}, this);

			if (typeof BARollover == 'function') {
				this.buttons[idx] = new BARollover(node, this.btnStatus);
			}
		}
	},

	/**
	 * set button status to activate.
	 * @param {Number} idx    index number of the button (required)
	 */
	activate : function(idx) {
		var button = this.buttons[idx];
		if (button) {
			this.reset();
			button.setStatus('active');
		}
	},

	/**
	 * reset (set status to normal) all button.
	 */
	reset : function() {
		this.buttons.forEach(function(button) { button.setStatus('normal') });
	}
}






/* --------------- Constructor : BAImageCrossFader --------------- */
/**
 * provides cross fade transition effect for image switching.
 * @class cross fade transition
 * @constructor
 * @param {BAElement} imgNode    img element node that cross fade effect will be given. (required)
 */
function BAImageCrossFader(imgNode) {
	/** img element node that cross fade effect will be given.
	    @type BAElement @private @const */
	this.imgNode     = imgNode;
	/** element node as next image, this hang over the current image element like a layer.
	    @type BAElement @private @const */
	this.nextImgNode = null;
	/** step count of increase opacity value of 'next image'.
	    @type Number @private */
	this.step        = 1;
	/** interval time to increase opacity value. (ms)
	    @type Number @private */
	this.interval    = 0;
	/** percent value of current opacity of the next image.
	    @type Number @private */
	this.opacity     = 0;
	/** store point of the timer for cross fade transition.
	    @type BASetInterval @private */
	this.transTimer  = null;
}

BAImageCrossFader.prototype= {
	/**
	 * set parameters for cross fade transition effect.
	 * @param {Number} step        an accrual of opacity value of the next image.
	 * @param {Number} interval    an increase interval of opacity value of the next image.
	 */
	setParam : function(step, interval) {
		if (typeof step != 'number' || step < 1) {
			throw 'BAImageCrossFader.setParam: first arguments must be an integer more than 1.';
		} else {
			this.step = step;
		}
		if (typeof interval != 'undefined' && typeof interval != 'number' || interval < 0) {
			throw 'BAImageCrossFader.setParam: second arguments must be an integer more than 0.';
		} else if (typeof interval != 'undefined') {
			this.interval = interval;
		}
	},

	/**
	 * start cross fade transition effect.
	 * @param {String} nextImgSrc    url ('img src') of the next image. (required)
	 */
	startTransition : function(nextImgSrc) {
		if (typeof nextImgSrc != 'string') {
			throw 'BAImageCrossFader.startTransition: first arguments must be an string (next image\'s URL).';
		} else {
			this.stopTransition();
	
			// create a node as next image.
			if (this.nextImgNode) {
				this.nextImgNode.parentNode.removeChildBA(this.nextImgNode);
			}
			this.nextImgNode = document.createElementBA('img');
			this.setOpacityOfNextImg(0);
			this.nextImgNode.style.position = 'absolute';
/*
			this.nextImgNode.style.left     = this.imgNode.getAbsoluteOffsetBA().X + 'px';
			this.nextImgNode.style.top      = this.imgNode.getAbsoluteOffsetBA().Y + 'px';
*/
			this.nextImgNode.style.left     = '0';
			this.nextImgNode.style.top      = '0';
			this.nextImgNode.src            = nextImgSrc;
			this.imgNode.parentNode.appendChildBA(this.nextImgNode);
	
			this.transTimer = new BASetInterval(function() {
				if (this.opacity < 99) {
					this.setOpacityOfNextImg();
				} else {
					this.imgNode.src = this.nextImgNode.src;
					this.stopTransition();
				}
			}, this.interval, this);
		}
	},

	/**
	 * stop cross fade transition effect.
	 */
	stopTransition : function() {
		if (this.transTimer) {
			this.transTimer.clearTimer();
		}
	},

	/**
	 * set opacity of the next image.
	 * if opacity is not specified, simply increases opacity value by 'this.step'.
	 * @param {Number} opacity    percent value of opacity of the next image.
	 * @private
	 */
	setOpacityOfNextImg : function(opacity) {
		if (typeof opacity != 'number') {
			opacity = this.opacity + this.step;
		}
		opacity = Math.min(opacity, 99);
		opacity = Math.max(opacity,  0);

		this.nextImgNode.style.visibility = (opacity == 0) ? 'hidden' : 'visible';
		if (this.nextImgNode.runtimeStyle) {
			var value = opacity.toString();
			this.nextImgNode.runtimeStyle.filter = 'Alpha(opacity=${0})'.formatTextBA([value]);
		} else {
			var value = (opacity / 100).toString();
			this.nextImgNode.style.opacity    = value;
			this.nextImgNode.style.MozOpacity = value;
		}
		this.opacity = opacity;
	}
}


