/* -------------------------------------------------------------------------- */
/**
 *    @fileoverview
 *       Image Swap / Rollover Control
 *
 *    @version 2.4.20100921
 *    @requires jquery.js
 *    @requires bajl.js
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* -------------------- Settings for BAJL.Rollover -------------------- */
/**
 * settings for {@link BAJL.Rollover}
 * @namespace settings for {@link BAJL.Rollover}
 * @fieldOf BAJL.settings
 * @property {Boolean}               autoSetup.enabled          autosetup is enabled or not.
 * @property {Object}                presets                    for autosetup; pairs of jquery selector text and {@link BAJL.Rollover.Setting} object
 * @property {BAJL.Rollover.Setting} presets[jQuerySelector]    for autosetup; rollover setting object for an element which is indicated by jQuerySelector
 */
BAJL.settings.Rollover = {
	  'autoSetup' : {
		  'enabled' : true
	}
	, 'presets' : {
		  '.rollover' : {
			  'findAtOnce'  : false
			, 'exclude'     : '.norollover'
			, 'statusSet'   : {
				  'normal' : ''
				, 'stay'   : 's'
				, 'hover'  : 'o'
			}
			, 'handlers'    : {
				  'mouseover' : function(node, event) { this.setStatus('hover'  ) }
				, 'mouseout'  : function(node, event) { this.setStatus('default') }
			}
			, 'cnamePrefix' : 'pseudo-'
		}
	}
}



/* -------------------- AutoSetup : BAJL.Rollover -------------------- */

$(function() {
	var settings = BAJL.settings.Rollover;
	if (settings.autoSetup.enabled) {
		$.each(settings.presets, function(expr, setting) {
			BAJL.Rollover.setup(expr, setting);
		});
	}
});



/* -------------------- jQuery.fn : BAJL_Rollover -------------------- */
/**
 * BAJL.Rollover as jQuery plugin
 * @param {BAJL.Rollover.Setting.statusSet} statusSet        associative array of status name and its suffix sign in image filename.
 * @param {String}                          [exclude]        expression to specify elements which is not expected to rollover.
 * @param {String}                          [cnamePrefix]    prefix for "status pseudo className" added according to status.
 * @returns jQuery
 * @type jQuery
 */
jQuery.fn.BAJL_Rollover = function (statusSet, exclude, cnamePrefix) {
	return this.each(function(){ new BAJL.Rollover(this, statusSet, exclude, cnamePrefix) });
}



/* -------------------- Class : BAJL.Rollover -------------------- */
/**
 * provides universal rollover (this can apply to any elements, not only img element!)
 * @class universal rollover
 * @constructor
 * @see BAJL.ImageSwapper
 * @param {Element|jQuery|String}           node             top-level element node of rollover behavior
 * @param {BAJL.Rollover.Setting.statusSet} statusSet        associative array of status name and its suffix sign in image filename
 * @param {String}                          [exclude]        expression to specify elements which is not expected to rollover
 * @param {String}                          [cnamePrefix]    prefix for "status pseudo className" added according to status.
 */
BAJL.Rollover = function(node, statusSet, exclude, cnamePrefix) {
	/** top-level element node of rollover behavior.
	    @type Element
	    @constant
	    @private */
	this.node        = $(node).get(0);
	/** associative array of pairs of status and it's suffix sign.
	    @type Object
	    @private */
	this.statusSet   = $.extend({}, statusSet);
	/** current rollover status.
	    @type String
	    @private */
	this.status      = 'default';
	/** expression to specify elements which is not expected to rollover
	    @type String
	    @constant
	    @private */
	this.exclude     = exclude || '';
	/** prefix for "status pseudo className" added according to status.
	    @type String
	    @constant
	    @private */
	this.cnamePrefix = cnamePrefix || '';
	/** the array of BAJL.ImageSwapper instances.
	    @type BAJL.ImageSwapper[]
	    @private */
	this.swappers    = [];
	
	if (BAJL.env.isDOMReady) {
		this.init();
	}
}

/* ---------- class methods/props ---------- */

/**
 * an array of instances of this class.
 * @type BAJL.Rollover[]
 */
BAJL.Rollover.instances = [];

/**
 * store an instance created from this class
 * @param {BAJL.Rollover} instance    an instance object to store
 * @return an instance object stored
 * @type BAJL.Rollover
 */
BAJL.Rollover.storeInstance = function(instance) {
	if (!instance || !(instance instanceof BAJL.Rollover)) {
		throw new TypeError('BAJL.Rollover.storeInstance: first argument must be an instance of BAJL.Rollover');
	} else {
		$(instance.node).data('BAJL.Rollover.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.Rollover instance
 * @type BAJL.Rollover
 */
BAJL.Rollover.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.Rollover.InstanceID')];
	} else {
console.log(arg);
console.log('nodeName:' + arg.nodeName);
console.log('nodeType:' + arg.nodeType);
console.log('typeof jquery:' + typeof arg.jquery);
console.log('typeof:' + typeof arg);
		throw new TypeError('BAJL.Rollover.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.Rollover} instance    an instance object to delete
 * @return an instance object stored
 * @type BAJL.Rollover
 */
BAJL.Rollover.disposeInstance = function(instance) {
	if (!instance || !(instance instanceof BAJL.Rollover)) {
		throw new TypeError('BAJL.Rollover.disposeInstance: first argument must be an instance of BAJL.Rollover');
	} else if (instance.node) {
		BAJL.Rollover.instances.splice($(instance.node).data('BAJL.Rollover.InstanceID'), 1, undefined);
		instance.dispose(true);
	}
}

/**
 * create rollover behavior on the whole page.
 * @param {String}                targets    expression to find target elements to set rollover behavior
 * @param {BAJL.Rollover.Setting} setting    setting data object; an associative array.
 */
BAJL.Rollover.setup = function(targets, setting) {
	var setting = $.extend(new BAJL.Rollover.Setting, setting);

	if (setting.findAtOnce) {
		$(targets).each(function() {
			_initRollover(this, setting);
		});
	} else {
		$(document).mouseover(function(e) {
			$(e.target).closest(targets).each(function() {
				$(_initRollover(this, setting)).triggerHandler(e.type);
			});
		});
	}

	function _initRollover(node, setting) {
		if (!BAJL.Rollover.getInstance(node)) {
			new BAJL.Rollover(node, setting.statusSet, setting.exclude, setting.cnamePrefix);
			for (var type in setting.handlers) {
				$(node).bind(type, setting.handlers, BAJL.Delegate(_fireEvent, node));
			}
		}
		return node;
	}

	function _fireEvent(e) {
		var type     = e.type;
		var handler  = e.data[type];
		var node     = e.currentTarget || this;
		var related  = e.relatedTarget;
		var rollover = BAJL.Rollover.getInstance(node);
		var flag     = Boolean(rollover);
		if (flag && related) {
			flag = (function(_node) {
				return (!_node)         ? true  :
				       ( _node == node) ? false :
				                          arguments.callee(_node.parentNode);
			})(related);
		}
		if (flag && handler) {
			handler.call(rollover, node, e);
		}
	}
}

/* ---------- instance methods ---------- */

/**
 * initialize, setup nodes.
 * @private
 */
BAJL.Rollover.prototype.init = function(){
	var $node = $(this.node);
	if (!$node.BAJL_HasElement()) {
		throw new ReferenceError('BAJL.Rollover#init: base element to rollover is not given.');
	} else if (!BAJL.Rollover.getInstance($node)) {
		var img = 'img, input:image';
		$node.find(img).andSelf().filter(img).not(this.exclude).get().forEach(function(node) {
			this.swappers.push(new BAJL.ImageSwapper(node, this.statusSet));
		}, this);
		BAJL.Rollover.storeInstance(this);
	}
}

/**
 * dispose this instace
 * @param {Boolean} preventRecursion    'true' to prevent recursion
 */
BAJL.Rollover.prototype.dispose = function(preventRecursion) {
	if (!preventRecursion) {
		BAJL.Rollover.disposeInstance(this);
	} else {
		$.each(this, BAJL.Delegate(function(prop) {
			delete this[prop];
			if (typeof this[prop] == 'function') this[prop] = new Function;
		}, this));
	}
}

/**
 * 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
 */
BAJL.Rollover.prototype.getDefaultStatus = function(index) {
	if (typeof index != 'number') {
		index = 0;
	}
	if (!this.swappers[index]) {
		throw new ReferenceError('BAJL.Rollover.getDefaultStatus: image swapper is not found.');
	} else {
		return this.swappers[index].getDefaultStatus();
	}
}

/**
 * get rollover status.
 * @return rollover status
 * @type String
 */
BAJL.Rollover.prototype.getStatus = function(index) {
	return this.status;
}

/**
 * get all statuses of image swappers
 * @return an array of status of all image swappers
 * @type Array
 */
BAJL.Rollover.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
 * @return this instance
 * @type BAJL.Rollover
 */
BAJL.Rollover.prototype.setStatus = function(status) {
	this.swappers.forEach(function(image) {
		image.setStatus(status);
	});

	$(this.node)
		.removeClass(this.cnamePrefix + this.status)
		.toggleClass(this.cnamePrefix +      status, this.cnamePrefix && typeof this.statusSet[status] == 'string');

	this.status = status;
	return this;
}



/* -------------------- Class : BAJL.ImageSwapper -------------------- */
/**
 * image src swapper.
 * this is usually used as unit instance of BAJL.Rollover
 * @class single image swapper
 * @constructor
 * @see BAJL.Rollover
 * @param {Element|jQuery|String}   node         image element node (img/input[type="image"])
 * @param {BAJL.Rollover.statusSet} statusSet    associative array of status name and its suffix sign in image filename
 */
BAJL.ImageSwapper = function(node, statusSet) {
	/** image element node.
	    @type Element
	    @constant
	    @private */
	this.node          = $(node).get(0);
	/** associative array of status name and it's suffix sign.
	    @type Object
	    @private */
	this.statusSet     = $.extend({}, 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 (BAJL.env.isDOMReady) {
		this.init();
	}
}

/* ---------- class methods/props ---------- */

/**
 * an array of instances of this class.
 * @type BAJL.ImageSwapper[]
 */
BAJL.ImageSwapper.instances = [];

/* ---------- instance methods ---------- */

/**
 * initialize, setup nodes.
 * @private
 */
BAJL.ImageSwapper.prototype.init = function(){
	if (!$(this.node).is('img, input:image')) {
		throw new TypeError('BAJL.ImageSwapper: first argument must be an img element or input[type="image"] element.');
	} 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).attr('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] = BAJL.PreloadImage(remain + this.statusSet[i] + suffix);
				if (this.statusSet[i] == status) {
					this.defaultStatus = this.status = i;
				}
			}
		}
		BAJL.ImageSwapper.instances.push(this);
	}
}

/**
 * get image status
 * @return real image status (instead of 'default')
 * @type String
 */
BAJL.ImageSwapper.prototype.getStatus = function () {
	return this.status;
}

/**
 * get default image status
 * @return default image status
 * @type String
 */
BAJL.ImageSwapper.prototype.getDefaultStatus = function () {
	return this.defaultStatus;
}

/**
 * set image status (switch image src by binded status name)
 * @param {String} status    status text
 * @return this instance
 * @type BAJL.ImageSwapper
 */
BAJL.ImageSwapper.prototype.setStatus = function (status) {
	if (status == 'default') {
		status = this.defaultStatus;
	}
	if (this.images[status]) {
		this.status = status;
//		if (this.images[status].complete) {
			$(this.node).attr('src', this.images[status].src);
			this.setStatus_AILoader(this.images[status].src);  // for WinIE5.5/6.x
//		}
	}
	return this;
}

/**
 * set src of the image that applied AlphaImageLoader (for WinIE55/60).
 * @param {String} src    image's url to set.
 * @private
 */
BAJL.ImageSwapper.prototype.setStatus_AILoader = function(src) {
	if (this.node.__BAJL_AILoader_Processed__ && src && src.substr(src.length - 4).toLowerCase() == '.png') {
		var span = this.node.nextSibling;
		span.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '", sizingMethod="scale")';
	}
}



/* -------------------- Class : BAJL.Rollover.Setting -------------------- */
/**
 * setting data object for {@link BAJL.Rollover.setup}
 * @class setting data object for {@link BAJL.Rollover.setup}
 */
BAJL.Rollover.Setting = function() {
	/** flag to find rollover target elements at once.
	    @type Boolean */
	this.findAtOnce  = false;
	/** expression to find elements which is not expected to rollover.
	    @type String */
	this.exclude     = '.norollover'
	/** associative array of status name and its suffix sign in image filename.
	    @type BAJL.Rollover.Setting.statusSet */
	this.statusSet   = { 'normal' : '', 'hover' : 'o' }
	/** associative array of pairs of event type name and its event handler function.
	    @type BAJL.Rollover.Setting.handlers */
	this.handlers    = {
		  'mouseover' : function(node) { this.setStatus('hover'  ) }
		, 'mouseout'  : function(node) { this.setStatus('default') }
	}
	/** prefix for "status pseudo className" added according to status.
	    @type String */
	this.cnamePrefix = 'pseudo-';
}



/* -------------------- for JSDoc toolkit output -------------------- */
/**
 * associative array of status name and its suffix sign in image filename.
 * @name BAJL.Rollover.Setting.statusSet
 * @namespace rollover status set for {@link BAJL.Rollover}, {@link BAJL.ImageSwapper},
 * @example &nbsp;
 *   // example
 *   { 'normal' : '_n' 'stay' : '_s' 'hover'  : '_o' }
 */
/**
 * associative array of pairs of event type name and its event handler function.
 * @name BAJL.Rollover.Setting.handlers
 * @namespace preset of rollover event handlers
 * @example &nbsp;
 *   // 'node'  argument is an element node of current target of the event
 *   // 'event' argument is an element node of current target of the event
 *   // 'this' is BAJL.Rollover instance object
 *   {
 *     'mouseover' : function(node, event) { this.setStatus('hover'  ) }
 *   , 'mouseout'  : function(node, event) { this.setStatus('default') }
 *   , 'click'     : function(node, event) { event.preventDefault()    }
 *   }
 */



/* -------------------- for backward compatibilities -------------------- */

if (BAJL.settings.common.useBackCompat) {
	BAJL.CreateBackCompat({
		  'BARollover'                 : function(node, statusSet, excludeCName) { return new BAJL.Rollover(node, statusSet, excludeCName ? '.' + excludeCName : '') }
		, 'BAImageSwapper'             : BAJL.ImageSwapper
		, 'BARolloverSetupByClassName' : function() { throw new ReferenceError('BARolloverSetupByClassName : this function is deleted') }
	});
}



})(jQuery);

