/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Image Swap / Rollover Control
 *
 *    @version 1.0.20100707
 *    @requires jquery.js
 *    @requires cwj.js
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* -------------------- Settings for CWJL.Rollover -------------------- */
/** 
 * settings for {@link CWJL.Rollover}
 * @namespace settings for {@link CWJL.Rollover}
 * @fieldOf CWJL.settings
 * @property {Boolean} autoSetup.enabled    autosetup is enabled or not
 * @property {Object}  presets              an associative array of pairs of an expression to find top-level element node of rollover behavior and its setting object ({@link CWJL.Rollover.Setting})
 */
CWJL.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') }
}
}
}
}



/* -------------------- jQuery.fn : CWJL_Rollover -------------------- */
/**
 * CWJL.Rollover as jQuery plugin
 * @param {CWJL.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
 * @returns jQuery
 * @type jQuery
 */
jQuery.fn.CWJL_Rollover = function (statusSet, exclude) {
return this.each(function(){ new CWJL.Rollover(this, statusSet, exclude) });
}



/* -------------------- Class : CWJL.Rollover -------------------- */
/**
 * provides universal rollover (this can apply to any elements, not only img element!)
 * @class universal rollover
 * @constructor
 * @see CWJL.ImageSwapper
 * @param {Element|jQuery|String}           node         top-level element node of rollover behavior
 * @param {CWJL.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
 */
CWJL.Rollover = function(node, statusSet, exclude) {
/** 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';
/** prefix for 'pseudo className' added according to status.
    @type String
    @constant
    @private */
this.cNamePrefix = 'pseudo-';
/** expression to specify elements which is not expected to rollover
    @type String
    @constant
    @private */
this.exclude     = exclude || '';
/** the array of CWJL.ImageSwapper instances.
    @type CWJL.ImageSwapper[]
    @private */
this.swappers    = [];

if (CWJL.env.isDOMReady) {
this.init();
}
}

/* ---------- class methods/props ---------- */

/**
 * an array of instances of this class.
 * @type CWJL.Rollover[]
 */
CWJL.Rollover.instances = [];

/**
 * store an instance created from this class
 * @param {CWJL.Rollover} instance    an instance object to store
 * @return an instance object stored
 * @type CWJL.Rollover
 */
CWJL.Rollover.storeInstance = function(instance) {
if (!instance || !(instance instanceof CWJL.Rollover)) {
throw new TypeError('CWJL.Rollover.storeInstance: first argument must be an instance of CWJL.Rollover');
} else {
$(instance.node).data('CWJL.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 CWJL.Rollover instance
 * @type CWJL.Rollover
 */
CWJL.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('CWJL.Rollover.InstanceID')];
} else {
throw new TypeError('CWJL.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 {CWJL.Rollover} instance    an instance object to delete
 * @return an instance object stored
 * @type CWJL.Rollover
 */
CWJL.Rollover.disposeInstance = function(instance) {
if (!instance || !(instance instanceof CWJL.Rollover)) {
throw new TypeError('CWJL.Rollover.disposeInstance: first argument must be an instance of CWJL.Rollover');
} else if (instance.node) {
CWJL.Rollover.instances.splice($(instance.node).data('CWJL.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 {CWJL.Rollover.Setting} setting    setting data object; an associative array.
 */
CWJL.Rollover.setup = function(targets, setting) {
var setting = $.extend(new CWJL.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 (!CWJL.Rollover.getInstance(node)) {
new CWJL.Rollover(node, setting.statusSet, setting.exclude);
for (var type in setting.handlers) {
$(node).bind(type, setting.handlers, CWJL.Delegate(_fireEvent, this));
}
}
return node;
}

function _fireEvent(e) {
var type    = e.type;
var handler = e.data[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 && handler) {
handler.call(CWJL.Rollover.getInstance(node), node, e);
}
}
}

/* ---------- instance methods ---------- */

/** 
 * initialize, setup nodes.
 * @private
 */
CWJL.Rollover.prototype.init = function(){
var $node = $(this.node);
if (!$node.CWJL_HasElement()) {
throw new ReferenceError('CWJL.Rollover#init: base element to rollover is not given.');
} else if (!CWJL.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 CWJL.ImageSwapper(node, this.statusSet));
}, this);
CWJL.Rollover.storeInstance(this);
}
}

/**
 * dispose this instace
 * @param {Boolean} preventRecursion    'true' to prevent recursion
 */
CWJL.Rollover.prototype.dispose = function(preventRecursion) {
if (!preventRecursion) {
CWJL.Rollover.disposeInstance(this);
} else {
$.each(this, CWJL.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
 */
CWJL.Rollover.prototype.getDefaultStatus = function(index) {
if (typeof index != 'number') {
index = 0;
}
if (!this.swappers[index]) {
throw new ReferenceError('CWJL.Rollover.getDefaultStatus: image swapper is not found.');
} else {
return this.swappers[index].getDefaultStatus();
}
}

/** 
 * get rollover status.
 * @return rollover status
 * @type String
 */
CWJL.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
 */
CWJL.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
 */
CWJL.Rollover.prototype.setStatus = function(status) {
this.swappers.forEach(function(image) {
image.setStatus(status);
});
$(this.node).removeClass(this.cNamePrefix + this.status);
if (this.statusSet[status]) {
$(this.node).addClass(this.cNamePrefix + status);
}
this.status = status;
}



/* -------------------- Class : CWJL.ImageSwapper -------------------- */
/**
 * image src swapper.
 * this is usually used as unit instance of CWJL.Rollover
 * @class single image swapper
 * @constructor
 * @see CWJL.Rollover
 * @param {Element|jQuery|String}   node         image element node (img/input[type="image"])
 * @param {CWJL.Rollover.statusSet} statusSet    associative array of status name and its suffix sign in image filename
 */
CWJL.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 (CWJL.env.isDOMReady) {
this.init();
}
}

/* ---------- class methods/props ---------- */

/**
 * an array of instances of this class.
 * @type CWJL.ImageSwapper[]
 */
CWJL.ImageSwapper.instances = [];

/* ---------- instance methods ---------- */

/** 
 * initialize, setup nodes.
 * @private
 */
CWJL.ImageSwapper.prototype.init = function(){
if (!$(this.node).is('img, input:image')) {
throw new TypeError('CWJL.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] = CWJL.PreloadImage(remain + this.statusSet[i] + suffix);
if (this.statusSet[i] == status) {
this.defaultStatus = this.status = i;
}
}
}
CWJL.ImageSwapper.instances.push(this);
}
}

/** 
 * get image status
 * @return real image status (instead of 'default')
 * @type String
 */
CWJL.ImageSwapper.prototype.getStatus = function () {
return this.status;
}

/** 
 * get default image status
 * @return default image status
 * @type String
 */
CWJL.ImageSwapper.prototype.getDefaultStatus = function () {
return this.defaultStatus;
}

/** 
 * set image status (switch image src by binded status name)
 * @param {String} status    status text
 */
CWJL.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
//}
}
}

/**
 * set src of the image that applied AlphaImageLoader (for WinIE55/60).
 * @param {String} src    image's url to set.
 * @private
 */
CWJL.ImageSwapper.prototype.setStatus_AILoader = function(src) {
if ($(this.node).data('CWJL.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 : CWJL.Rollover.Setting -------------------- */
/**
 * setting data object for {@link CWJL.Rollover.setup}
 * @class setting data object for {@link CWJL.Rollover.setup}
 */
CWJL.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 CWJL.Rollover.Setting.statusSet */
this.statusSet  = { 'normal' : '', 'hover' : 'o' }
/** associative array of pairs of event type name and its event handler function.
    @type CWJL.Rollover.Setting.handlers */
this.handlers   = {
  'mouseover' : function(node) { this.setStatus('hover'  ) }
, 'mouseout'  : function(node) { this.setStatus('default') }
}
}



/* -------------------- AutoSetup -------------------- */

$(function() {
var S = CWJL.settings.Rollover;
if (S.autoSetup.enabled) {
$.each(S.presets, function(expr, setting) { CWJL.Rollover.setup(expr, setting) });
}
});



/* -------------------- for JSDoc toolkit output -------------------- */
/**
 * associative array of status name and its suffix sign in image filename.
 * @name CWJL.Rollover.Setting.statusSet
 * @namespace rollover status set for {@link CWJL.Rollover}, {@link CWJL.ImageSwapper},
 * @example &nbsp;
 *   // example
 *   { 'normal' : '_n' 'stay' : '_s' 'hover'  : '_o' }
 */
/**
 * associative array of pairs of event type name and its event handler function.
 * @name CWJL.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 CWJL.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 (CWJL.settings.common.useBackCompat) {
CWJL.CreateBackCompat({
  'CWJRollover'                 : function(node, statusSet, excludeCName) { return new CWJL.Rollover(node, status, excludeCName ? '.' + excludeCName : '') }
, 'CWJImageSwapper'             : CWJL.ImageSwapper
, 'CWJRolloverSetupByClassName' : function() { throw new ReferenceError('CWJRolloverSetupByClassName : this function is deleted') }
});
}



})(jQuery);
