/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Image Swap / Rollover Control
 *
 *    @version rev012.2008-10-11
 *    @requires common.js
 */
/* -------------------------------------------------------------------------- */

var BA_ROLLOVER_AS_INSTANCES = [];



/* -------------------- Settings for BARolloverAutoSetup -------------------- */

var BA_ROLLOVER_AS_ENABLED  = true;
var BA_ROLLOVER_AS_SETTINGS = {
	'rollover' : {
		findAtOnce : false,
		targets    : ['a', 'input'],  // this will be ignored when 'findAtOnce' is false.
		exclude    : 'norollover',
		statusSet  : {
			'normal' : '',
			'hover'  : '_o'
		},
		handlers   : {
			'mouseover' : function(node) { this.setStatus('hover'  ); },
			'mouseout'  : function(node) { this.setStatus('default'); }
		}
	}
};



/* -------------------- Constructor : BARollover -------------------- */
/**
 * provides universal rollover (this can apply to any elements, not only img element!)
 * @class universal rollover
 * @constructor
 * @see BAImageSwapper
 * @param {Element} node            top-level element node of rollover behavior      (required)
 * @param {Object}  statusSet       associative array of status and it's suffix sign (required)
 * @param {String}  excludeCName    className for the image that is not expected to rollover
 */
function BARollover(node, statusSet, excludeCName) {
	/** top-level element node of rollover behavior.
	    @type Element @const @private */
	this.node         = node;
	/** associative array of pairs of status and it's suffix sign.
	    @type Object @private */
	this.statusSet    = statusSet || {};
	/** current rollover status.
	    @type String @private */
	this.status       = 'default';
	/** prefix for 'pseudo className' added according to status.
	    @type String @const @private */
	this.cNamePrefix  = 'pseudo-';
	/** className for the image that is not expected to rollover
	    @type String @const @private */
	this.excludeCName = excludeCName || '';
	/** the array of BAImageSwapper instances.
	    @type Array @private */
	this.swappers     = [];
	
	if (BA.env.isDOMReady) {
		this.init();
	}
}

/** 
 * initialize, setup nodes.
 * @private
 */
BARollover.prototype.init = function() {
	if (!this.node || this.node.nodeType != 1) {
		throw 'BARollover: first argument must be an element node.';
	} else {
		var nodes = [BARegisterDOMMethodsTo(this.node)];
		['img', 'input'].forEach(function(name) {
			nodes = nodes.concat(this.node.getElementsByTagNameBA(name));
		}, this);
		nodes.forEach(function(node) {
			if (this.nodeValidate(node)) {
				this.swappers.push(new BAImageSwapper(node, this.statusSet));
			}
		}, this);
	}
}

/** 
 * validate the node
 * @param {BAElement} node    node to validate
 * @return true when node is an acceptable node
 * @private
 * @type Boolean
 */
BARollover.prototype.nodeValidate = function(node) {
	if (!node || !node.nodeName) {
		return false;
	} else if (!node.nodeName.match(/^(img|input)$/i)) {
		return false
	} else if (node.nodeName.match(/^input$/i) && !node.getAttributeBA('type').match(/^image$/i)) {
		return false
	} else if (this.excludeCName && node.hasClassNameBA(this.excludeCName)) {
		return false
	} else {
		return true;
	}
}

/** 
 * get default status of the image swapper
 * @param {Number} index    index num of image swapper.
 * @return default status of the swapper specified by index number - if no index number is given, return first swapper's.
 * @type String
 */
BARollover.prototype.getDefaultStatus = function(index) {
	if (typeof index != 'number') {
		index = 0;
	}
	if (!this.swappers[index]) {
		throw 'BARollover.getDefaultStatus: image swapper is not found.';
	} else {
		return this.swappers[index].getDefaultStatus();
	}
}

/** 
 * get rollover status.
 * @return rollover status
 * @type String
 */
BARollover.prototype.getStatus = function(index) {
	return this.status;
}

/** 
 * get all statuses of image swappers
 * @return an array of status of all image swappers
 * @type Array
 */
BARollover.prototype.getSwapperStatus = function() {
	return this.swappers.map(function(swapper) { return swapper.getStatus() });
}


/** 
 * set rollover status, descendant images of 'this.node' are rollover at once!
 * @param {String} status    status text (required)
 */
BARollover.prototype.setStatus = function(status) {
	this.swappers.forEach(function(image) {
		image.setStatus(status);
	});
	this.node.removeClassNameBA(this.cNamePrefix + this.status);
	if (this.statusSet[status]) {
		this.node.appendClassNameBA(this.cNamePrefix + status);
	}
	this.status = status;
}



/* -------------------- Constructor : BAImageSwapper -------------------- */
/**
 * image src swapper.
 * this is usually used as unit instance of BARollover
 * @class single image swapper
 * @constructor
 * @see BARollover
 * @param {Element} node         image element node (img/input type="image") (required)
 * @param {Object}  statusSet    associative array of status name and it's suffix sign (required)
 */
function BAImageSwapper(node, statusSet) {
	/** image element node.
	    @type Element @const @private */
	this.node          = node;
	/** associative array of status name and it's suffix sign.
	    @type Object @private */
	this.statusSet     = statusSet || {};
	/** current image status.
	    @type String @private */
	this.status        = '';
	/** default (original) image status.
	    @type String @private */
	this.defaultStatus = '';
	/** associative array of pairs of status and it's image (Image object).
	    @type Object @private */
	this.images        = {};

	if (BA.env.isDOMReady) {
		this.init();
	}
}

/** 
 * initialize, setup nodes.
 * @private
 */
BAImageSwapper.prototype.init = function () {
	if (!this.nodeValidate(BARegisterDOMMethodsTo(this.node))) {
		throw 'BAImageSwapper: first argument must be an element node.';
	} else {
		var arr = [];
		for (var i in this.statusSet) {
			arr.push(this.statusSet[i]);
		}
		var statusPtn = new RegExp('(' + arr.join('|') + ')$');
		var suffixPtn = /\.(jpe?g|gif|png)$/i;
	
		var src    = this.node.getAttributeBA('src');
		var suffix = (suffixPtn.test(src )) ? src.match (suffixPtn)[0] : '';
		var name   = src.replace (suffixPtn, '');
		var status = (statusPtn.test(name)) ? name.match(statusPtn)[0] : '';
		var remain = name.replace(statusPtn, '');
		if (suffix && remain) {
			for (var i in this.statusSet) {
				this.images[i] = BAPreloadImage(remain + this.statusSet[i] + suffix);
				if (this.statusSet[i] == status) {
					this.defaultStatus = this.status = i;
				}
			}
		}
	}
}

/** 
 * check node, node is image node or not.
 * @param {BAElement} node    node to check
 * @return true when node is a image node
 * @private
 * @type Boolean
 */
BAImageSwapper.prototype.nodeValidate = function(node) {
	if (!node || !node.nodeName) {
		return false;
	} else if (!node.nodeName.match(/^(img|input)$/i)) {
		return false
	} else if (node.nodeName.match(/^input$/i) && !node.getAttributeBA('type').match(/^image$/i)) {
		return false
	} else {
		return true;
	}
}

/** 
 * get image status
 * @return real image status (instead of 'default')
 * @type String
 */
BAImageSwapper.prototype.getStatus = function () {
	return this.status;
}

/** 
 * get default image status
 * @return default image status
 * @type String
 */
BAImageSwapper.prototype.getDefaultStatus = function () {
	return this.defaultStatus;
}

/** 
 * set image status (switch image src by binded status name)
 * @param {String} status    status text (required)
 */
BAImageSwapper.prototype.setStatus = function (status) {
	if (status == 'default') {
		status = this.defaultStatus;
	}
	if (this.images[status]) {
		this.status = status;
//		if (this.images[status].complete) {
			this.node.setAttributeBA('src', this.images[status].src);
			this.setStatus_AILoader(this.images[status].src);  // for WinIE5.5/6.x
//		}
	}
}

/**
 * set src of the image that applied AlphaImageLoader (for WinIE55/60).
 * @param {String} src    image's url to set.
 * @private
 */
BAImageSwapper.prototype.setStatus_AILoader = function(src) {
	if (this.node.__BAAILoaderProcessed__ && src && src.substr(src.length - 4).toLowerCase() == '.png') {
		var span = this.node.nextSibling;
		span.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '", sizingMethod="scale")';
	}
}



/* -------------------- Constructor : BARolloverSetupByClassName -------------------- */
/**
 * Setup rollover behavior on the whole page, for BARolloverAutoSetup()
 * @class className-based initializer of BARollover
 * @param {String} className      className of nodes to be set rollover behavior (required)
 * @param {Object} settingData    associative array of 'findAtOnce', 'targets', 'exclude', 'statusSet', and 'handlers' (required)
 * @see BARollover
 * @constructor
 */
function BARolloverSetupByClassName(className, settingData) {
	if (!className || !settingData) return;
	
	/** flag to find rollover targets at once after onload.
	    @type Boolean @const @private */
	this.findAtOnce   = settingData.findAtOnce;
	/** base node to search rollover target nodes.
	    @type BAElement @const @private */
	this.baseNode     = document;
	/** className of nodes to be set rollover behavior
	    @type String @const @private */
	this.targetCName  = className;
	/** array of element node names for 'rollover target'
	    @type Array @const @private */
	this.targetEName  = settingData.targets || ['*'];
	/** className for the image that is not expected to rollover
	    @type String @const @private */
	this.excludeCName = settingData.exclude;
	/** associative array of pairs of status and it's suffix sign.
	    @type Object @private */
	this.statusSet    = settingData.statusSet;
	/** associative array of pairs of mouse event type and it's event hander.
	    @type Object @private */
	this.handlers     = settingData.handlers;
	
	if (BA.env.isDOMReady) {
		this.init();
	}
}

/** 
 * initialize.
 * @private
 */
BARolloverSetupByClassName.prototype.init = function() {
	if (this.findAtOnce) {
		this.initNodes_atOnce();
	} else {
		this.initNodes_sequential();
	}
}

/** 
 * find rollover targets at once after onload.
 * @private
 */
BARolloverSetupByClassName.prototype.initNodes_atOnce = function() {
	this.targetEName.forEach(function(nodeName) {
		this.baseNode.getElementsByClassNameBA(this.targetCName, nodeName).forEach(function(node) {
			this.initNode(node);
		}, this);
	}, this);
}

/** 
 * find rollover targets sequentially.
 * @private
 */
BARolloverSetupByClassName.prototype.initNodes_sequential = function() {
	document.addEventListenerBA('mouseover', function(e) {
		var tname = this.targetCName;
		var tnode = (function(node) {
			BARegisterDOMMethodsTo(node);
			return (node.instanceOf == 'BAElement' && node.hasClassNameBA && node.hasClassNameBA(tname)) ?
				node : (node.parentNode) ?
					arguments.callee(node.parentNode) : null;
		})(e.target);
		if (tnode) {
			this.initNode(tnode);
			var _e = {};
			for (var prop in e) {
				_e[prop] = e[prop];
			}
			_e.type = 'mouseover';
			_e.currentTarget = tnode;
			this.fireEvent(_e);
		}
	}, this);
}

/** 
 * create BARollover instance, and set event handlers.
 * @param {BAElement} node    the node as rollover target
 * @private
 */
BARolloverSetupByClassName.prototype.initNode = function(node) {
	if (typeof node.__BARolloverSetupByClassName_instanceID__ != 'number') {
		node.__BARolloverSetupByClassName_instanceID__ = BA_ROLLOVER_AS_INSTANCES.length;
		var RO = new BARollover(node, this.statusSet, this.excludeCName);
		BA_ROLLOVER_AS_INSTANCES.push(RO);
		for (var eventType in this.handlers) {
			node.addEventListenerBA(eventType, this.fireEvent, this);
		}
	}
}

/**
 * call callback function.
 * @param {Event} e    event object (babbleable events only).
 * @private
 */
BARolloverSetupByClassName.prototype.fireEvent = function(e) {
	var type    = e.type;
	var node    = e.currentTarget;
	var related = e.relatedTarget;
	var flag    = true;
	if (e.relatedTarget) {
		flag = (function(_node) {
			return (!_node)         ? true  :
			       ( _node == node) ? false :
			                          arguments.callee(_node.parentNode);
		})(e.relatedTarget);
	}
	if (flag && this.handlers[type]) {
		var id = node.__BARolloverSetupByClassName_instanceID__;
		this.handlers[type].call(BA_ROLLOVER_AS_INSTANCES[id], node);
	}
}








/* -------------------- Function : BARolloverAutoSetup -------------------- */
/**
 * Auto setup BARollover behavior.
 * @see BARolloverSetupByClassName
 */
function BARolloverAutoSetup() {
	if (BA_ROLLOVER_AS_ENABLED && !BAAlreadyApplied(arguments.callee)) {
		for (var className in BA_ROLLOVER_AS_SETTINGS) {
			new BARolloverSetupByClassName(className, BA_ROLLOVER_AS_SETTINGS[className]);
		}
	}
}







/* -------------------- Main : register start-up -------------------- */

if (typeof BA == 'object' && BA.ua.isDOMReady) {
	BAAddOnload(BARolloverAutoSetup);
}
