/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       Balloon (Floating Layer) Controls.
 *
 *    @version 2.2.20100301
 *    @requires jquery.js
 *    @requires bajl.js
 *    @requires bajl.balloon.css
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* -------------------- Class : BAJL.Balloon -------------------- */
/**
 * balloon (typically known as floating layer).
 * @class balloon (typically known as floating layer).
 * @extends BAJL.Observable
 * @constructor
 * @param {BAJL.Balloon.setting} setting    balloon setting object
 */
BAJL.Balloon = function (setting) {
	/** base node of the balloon
	    @type Element
	    @private */
	this.node         = null;
	/** content body node in the balloon.
	    @type Element
	    @private */
	this.contentBody  = null;
	/** flag of activity of the balloon
	    @type Boolean
	    @private */
	this.active       = false;
	/** flag of ballon's position is fixed or not.
	    @type Boolean
	    @private */
	this.isPosFixed   = false;
	/** X-coordinate of the balloon (px)
	    @type Number
	    @private */
	this.posX         = 0;
	/** Y-coordinate of the balloon (px)
	    @type Number
	    @private */
	this.posY         = 0;
	/** X-distance from original balloon position (px)
	    @type Number
	    @private */
	this.offsetX      = 0;
	/** Y-distance from original balloon position (px)
	    @type Number
	    @private */
	this.offsetY      = 0;
	/** use position revising on X-axis?
	    @type Boolean
	    @private */
	this.posReviseX   = false;
	/** use position revising on Y-axis?
	    @type Boolean
	    @private */
	this.posReviseY   = false;
	/** ignore X-axis when {@link #moveToCenter} is called?
	    @type Boolean
	    @private */
	this.ignoreX      = false;
	/** ignore Y-axis when {@link #moveToCenter} is called?
	    @type Boolean
	    @private */
	this.ignoreY      = false;
	/** document.body's className for when the balloon is shown.
		@type String
	    @private
	    @constant */
	this.activedCName = '';

	if (BAJL.env.isDOMReady) {
		this.init(setting);
	}
}

BAJL.Balloon.prototype = new BAJL.Observable;

/**
 * an array of instances of this class.
 * @type BAJL.Balloon[]
 */
BAJL.Balloon.instances = [];

/**
 * store an instance created from this class
 * @param {BAJL.Balloon} instance    an instance object to store
 * @return an instance object stored
 * @type BAJL.Balloon
 */
BAJL.Balloon.storeInstance = function(instance) {
	if (!instance || !(instance instanceof BAJL.Balloon)) {
		throw new TypeError('BAJL.Balloon.storeInstance: first argument must be an instance of BAJ.Balloon');
	} else {
		$(instance.node).data('BAJL.Balloon.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.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.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.Balloon.InstanceID')];
	} else {
		throw new TypeError('BAJL.Balloon.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.Balloon} instance    an instance object to delete
 * @return an instance object stored
 * @type BAJL.Balloon
 */
BAJL.Balloon.disposeInstance = function(instance) {
	if (!instance || !(instance instanceof BAJL.Balloon)) {
		throw new TypeError('BAJL.Balloon.disposeInstance: first argument must be an instance of BAJ.Balloon');
	} else if (instance.node) {
		BAJL.Balloon.instances.splice($(instance.node).data('BAJL.Balloon.InstanceID'), 1, undefined);
		instance.dispose(true);
	}
}

/**
 * initialize balloon, prepare base node
 * @param {BAJL.Balloon.setting} setting    balloon setting object
 * @private
 */
BAJL.Balloon.prototype.init = function(setting) {
	this.node = $(document.createElement('ins')).addClass('BAJLBalloon').get(0);
	this.hide();

	if (typeof setting == 'object' && setting) {
		this.addClass     (setting.className                     );
		this.setId        (setting.id                            );
		this.applyTemplate(setting.template  , setting.cbodyExpr );
		this.setContent   (setting.content                       );
		this.appendTo     (setting.appendTo || document.body     );
		this.setOffset    (setting.offsetX   , setting.offsetY   );
		this.setPosRevise (setting.posReviseX, setting.posReviseY);
		this.moveTo       (setting.posX      , setting.posY      );
		this.moveToCenter (setting.ignoreX   , setting.ignoreY   );

		this.activedCName = setting.activedCName || '';
	} else {
		this.appendTo(document.body);
		this.applyTemplate();
	}

	BAJL.Balloon.storeInstance(this);
}

/**
 * dispose this instace
 * @param {Boolean} preventRecursion    'true' to prevent recursion
 */
BAJL.Balloon.prototype.dispose = function(preventRecursion) {
	if (!preventRecursion) BAJL.Balloon.disposeInstance(this);

//	@@@@@@@@@@@@@@@@@@@@ this makes jQuery confusing... @@@@@@@@@@@@@@@@@@@@
//	$(this.node).remove();     
//	$.each(this, BAJL.Delegate(function(prop) {
//		delete this[prop];
//		if (typeof this[prop] == 'function') this[prop] = new Function;
//	}, this));
}

/**
 * get activity state of the balloon.
 * @return true if the balloon is active (visible)
 * @type Boolean
 */
BAJL.Balloon.prototype.isActive = function() {
	return this.active;
}

/**
 * append the balloon base node to the specified element node.
 * @param {Element|jQuery|String} target    expression of target element to append
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.appendTo = function(target) {
	if ($(target).eq(0).BAJL_HasElement()) {
		$(this.node).appendTo(target);
		this.moveTo(0, 0);
		this.moveToCenter();
	}
	return this;
}

/**
 * add specified class name to the base node of the balloon.
 * @param {String} className     class name to add
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.addClass = function(className) {
	if (typeof className == 'string') {
		$(this.node).addClass(className);
	}
	return this;
}

/**
 * remove specified class name to the base node of the balloon.
 * @param {String} className     class name to add
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.removeClass = function(className) {
	if (typeof className == 'string') {
		$(this.node).removeClass(className);
	}
	return this;
}

/**
 * set fragment-id to the base node of the balloon.
 * @param {String} id    id-string to add
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.setId = function(id) {
	if (typeof id == 'string') {
		$(this.node).attr('id', id);
	}
	return this;
}

/**
 * set offset value (distance from original balloon position).
 * @param {Number} [x]    offset on X-axis (px)
 * @param {Number} [Y]    offset on Y-axis (px)
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.setOffset = function(x, y) {
	if (typeof x == 'number') this.offsetX = x;
	if (typeof y == 'number') this.offsetY = y;
	this.moveTo(); // revise position if if necessary.
	return this;
}

/**
 * set position revisiong setting.
 * @param {Boolean} [x]    if true, it uses X-axis when position revising
 * @param {Boolean} [Y]    if true, it uses Y-axis when position revising
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.setPosRevise = function(x, y) {
	if (typeof x == 'boolean') this.posReviseX = x;
	if (typeof y == 'boolean') this.posReviseY = y;
	this.moveTo(); // revise position if if necessary.
	return this;
}

/**
 * make the balloon's position to be fixed, and provides the way to ignore position-fix at X-scrolling and Y-scrolling.
 * with feature of ignoring position-fix at X or Y axis, and hiding block during scrolling.
 * @param {Boolean} [ignoreX]     if true, don't fix the position at X-scrolling
 * @param {Boolean} [ignoreY]     if true, don't fix the position at Y-scrolling
 * @param {Boolean} [autoHide]    if true, hide position-fixed block during scrolling
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.setPositionFixed = function(ignoreX, ignoreY, autoHide) {
	this.isPosFixed = true;
	// @@@@@@@@@@@@@@ need to work @@@@@@@@@@@@@@

	return this;
}

/**
 * apply template to the balloon.
 * @param {String} [template]     a template represented by HTML-string; if nonspecified, the template structure is removed from the balloon.
 * @param {String} [cbodyExpr]    an expression to find content body element in the teplate; this is required when the first argument is given.
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.applyTemplate = function(template, cbodyExpr) {
	var content = this.getContent();

	if (template === undefined || template === null) {
		this.contentBody = $(this.node).empty().append(content).get(0);
	} else if (!cbodyExpr || typeof cbodyExpr != 'string') {
		throw new TypeError('BAJL.Balloon.applyTemplate: "cbodyExpr" argument is required, the argument must be a string.');
	} else if (!$('<ins></ins>').append(template).find(cbodyExpr).BAJL_HasElement()) {
		throw new ReferenceError('BAJL.Balloon.applyTemplate: content body element node is not found in the template.');
	} else {
		this.contentBody = $(this.node).empty().append(template).find(cbodyExpr).append(content).get(0);
	}

	return this;
}

/**
 * get content of the balloon as an array of element nodes.
 * @return an array of element nodes.
 * @type Node[]
 */
BAJL.Balloon.prototype.getContent = function() {
	return (this.contentBody) ? $(this.contentBody).contents().get() : [];
}

/**
 * set content of the balloon.
 * @param {NodeList|Element|Element[]|jQuery|String} [content]    content to add to balloon
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.setContent = function(content) {
	return this.clearContent(Boolean(content)).addContent(content);
}

/**
 * add content to the balloon.
 * @param {NodeList|Element|Element[]|jQuery|String} [content]    content to add to balloon
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.addContent = function(content) {
	var before = this.getGeometry();
	$(this.contentBody).append(content);
	var after  = this.getGeometry();

	if (before.width != after.width || before.height != after.height) {
		this.doCallbackByName('onResize');
		this.moveTo(); // revise position if necessary.
	}

	this.doCallbackByName('onContentChange');
	return this;
}

/**
 * remove all contents from the balloon.
 * @param {Boolean} [noCallback]    if true, it doesn't callback.
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.clearContent = function(noCallback) {
	var hadContent = this.hasContent();
	$(this.contentBody).empty();

	if (!noCallback && hadContent) {
		this.doCallbackByName('onContentChange');
	}
	return this;
}

/**
 * does the balloon the content (in the balloon body)?
 * @return true if the balloon has the content (in the balloon body).
 * @type Boolean
 */
BAJL.Balloon.prototype.hasContent = function() {
	return (this.getContent().length > 0);
}

/**
 * get balloon geometry
 * @return BAJL.Balloon.geometry object.
 * @type BAJL.Balloon.geometry
 */
BAJL.Balloon.prototype.getGeometry = function() {
	return {
		posX    : this.posX,
		posY    : this.posY,
		offsetX : this.offsetX,
		offsetY : this.offsetY,
		width   : this.node.offsetWidth,
		height  : this.node.offsetHeight
	};
}

/**
 * show the balloon at specific coordinate.
 * @param {Number} [x]    X-coordinate to popup (px) (posX prop value is used in the case of no appointment)
 * @param {Number} [y]    Y-coordinate to popup (px) (posY prop value is used in the case of no appointment)
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.show = function(x, y) {
	// set document status to 'balloon is shown'
	$(document.body).addClass(this.activedCName);

	this.moveTo(x, y);
	$(this.node).css({ visibility : 'visible', display : 'block' });
	if (!this.active) {
		this.active = true;
		this.doCallbackByName('onShow');
	}
	return this;
}

/**
 * resize the balloon.
 * @param {Number} [width=-1]     new width  (border-box width ) of the balloon, 0 means 'auto', -1 means 'no change'
 * @param {Number} [height=-1]    new height (border-box height) of the balloon, 0 means 'auto', -1 means 'no change'
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.resizeTo = function(width, height) {
	if (typeof width  != 'number') width  = -1;
	if (typeof height != 'number') height = -1;
	if (width  >= 0) this.node.style.width  = (width  == 0) ? 'auto' : width  + 'px';
	if (height >= 0) this.node.style.height = (height == 0) ? 'auto' : height + 'px';

	var offsetWidth  = this.node.offsetWidth;
	var offsetHeight = this.node.offsetHeight;
	var reviseWidth  = (width  >= 0) ? 2 * width  - offsetWidth  : -1;
	var reviseHeight = (height >= 0) ? 2 * height - offsetHeight : -1;

	if (offsetWidth  != width  && reviseWidth  >= 0) this.node.style.width  = reviseWidth  + 'px';
	if (offsetHeight != height && reviseHeight >= 0) this.node.style.height = reviseHeight + 'px';

	this.doCallbackByName('onResize');
	this.moveTo(); // revise position if if necessary.
	return this;
}

/**
 * move balloon a specified number of pixels relative to its current coordinates
 * this method does not make the balloon shown.
 * @param {Number} [x]    X-coordinate to move (px) (posX prop value is used in the case of no appointment)
 * @param {Number} [y]    Y-coordinate to move (px) (posY prop value is used in the case of no appointment)
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.moveBy = function(x, y) {
	x = (typeof x == 'number') ? x : 0;
	y = (typeof y == 'number') ? y : 0;
	this.moveTo(this.posX + x, this.posY + y);
	return this;
}

/**
 * move balloon to specified coordinates.
 * this method does not make the balloon shown.
 * @param {Number} [x]    X-coordinate to move (px) (posX prop value is used in the case of no appointment)
 * @param {Number} [y]    Y-coordinate to move (px) (posY prop value is used in the case of no appointment)
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.moveTo = function(x, y) {
	var oldX = this.posX;
	var oldY = this.posY;
	if (typeof x == 'number') this.posX = x;
	if (typeof y == 'number') this.posY = y;

	this.revisePosition();

	this.node.style.left = (this.posX + this.offsetX) + 'px';
	this.node.style.top  = (this.posY + this.offsetY) + 'px';

	if (oldX != this.posX || oldY != this.posY) {
		this.doCallbackByName('onMove');
	}
	return this;
}

/**
 * move balloon to center of the browser window.
 * this method does not make the balloon shown.
 * @param {Boolean} [ignoreX]    if true, ignore centering on X-axis
 * @param {Boolean} [ignoreY]    if true, ignore centering on Y-axis
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.moveToCenter = function(ignoreX, ignoreY) {
	var geom = {};
	var base = $(this.node).parent().get(0) || document.body;
	if (base === document.body) {
		geom = BAJL.GetGeometry();
		geom = $.extend(null, geom, { parentW : geom.windowW, parentH : geom.windowH });
	} else {
		geom = { parentW : $(base).outerWidth(), parentH : $(base).outerHeight() };
	}

	if (typeof ignoreX == 'boolean') this.ignoreX = ignoreX;
	if (typeof ignoreY == 'boolean') this.ignoreY = ignoreY;

	var posX = (!this.ignoreX) ? (geom.parentW - $(this.node).outerWidth ()) / 2 : this.posX; if (posX < 0) posX = 0;
	var posY = (!this.ignoreY) ? (geom.parentH - $(this.node).outerHeight()) / 2 : this.posY; if (posY < 0) posY = 0;
	var scrX = (this.isPosFixed || !geom.scrollX) ? 0 : geom.scrollX;
	var scrY = (this.isPosFixed || !geom.scrollY) ? 0 : geom.scrollY;

	this.moveTo(posX + scrX, posY + scrY);
	return this;
}

/**
 * reviseing the balloon position to be in the window frame.
 * @private
 */
BAJL.Balloon.prototype.revisePosition = function() {
	if (this.posReviseX || this.posReviseY) {
		var geom       = BAJL.GetGeometry();
		var MacIE      = (BAJL.ua.isIE     && BAJL.ua.isMac);
		var IE6        = (BAJL.ua.isIE     && BAJL.ua.version <  7  );
		var IE7        = (BAJL.ua.isIE     && BAJL.ua.version >= 7  );
		var Safari2    = (BAJL.ua.isSafari && BAJL.ua.version <  522);
		var edgeRight  = (IE7 || Safari2 || MacIE || geom.pageH <= geom.windowH) ? 0 : geom.scrollBar;
		var edgeBottom = (IE7 || Safari2 || MacIE || geom.pageW <= geom.windowW) ? 0 : geom.scrollBar;

		if (IE6) {
			edgeRight   = geom.scrollBar + 4;
			edgeBottom += 4;
		}

		var reviseX = (geom.windowW + geom.scrollX) - (this.posX + this.offsetX + this.node.offsetWidth  + edgeRight );
		var reviseY = (geom.windowH + geom.scrollY) - (this.posY + this.offsetY + this.node.offsetHeight + edgeBottom);
		var posX    = (this.posX < geom.scrollX) ? geom.scrollX : this.posX + (reviseX < 0 ? reviseX : 0);
		var posY    = (this.posY < geom.scrollY) ? geom.scrollY : this.posY + (reviseY < 0 ? reviseY : 0);

		if (this.posReviseX) this.posX = posX;
		if (this.posReviseY) this.posY = posY;
	}
}

/**
 * make the balloon hidden.
 * @return BAJL.Balloon instance
 * @type BAJL.Balloon
 */
BAJL.Balloon.prototype.hide = function(){
//	$(this.node).hide();
	$(this.node).css({ visibility : 'hidden', display : 'block' });
	if (this.activedCName) {
		$(document.body).removeClass(this.activedCName);
	}
	if (this.active) {
		this.active = false;
		this.doCallbackByName('onHide');
	}
	return this;
}

/**
 * process callback.
 * @param {String} name    callback name (preferred to start with 'on')
 * @private
 */
BAJL.Balloon.prototype.doCallbackByName = function(name) {
	this.doCallback(name, this.getGeometry());
}

if (BAJL.settings.common.useBackCompat) {
	/** @deprecated use {@link #doCallbackByName} method instead of this method. */
	BAJL.Observable.prototype.doCallBackByName = function() { return this.doCallbackByName.apply(this, arguments) }
}



/* -------------------- for JSDoc toolkit output -------------------- */
/**
 * setting for initialize of {@link BAJL.Balloon}
 * @name BAJL.Balloon.setting
 * @namespace setting for initialize of {@link BAJL.Balloon}
 * @property {NodeList|Element|Element[]|jQuery|String} [content=""]    initial content of the balloon
 * @property {String}  [className=""]        additional className of base node of the balloon
 * @property {String}  [id=""]               fragment-id of base node of the balloon
 * @property {String}  [template]            a template represented by HTML-string
 * @property {String}  [cbodyExpr]           an expression to find content body element in the teplate; this is required when the 'template' property is given.
 * @property {Number}  [posX=0]              initial X-coordinate of the balloon (px)
 * @property {Number}  [posY=0]              initial Y-coordinate of the balloon (px)
 * @property {Number}  [offsetX=0]           X-distance from original balloon position (px)
 * @property {Number}  [offsetY=0]           Y-distance from original balloon position (px)
 * @property {Boolean} [posReviseX=false]    use position revising on X-axis?
 * @property {Boolean} [posReviseY=false]    use position revising on Y-axis?
 * @property {Boolean} [ignoreX=false]       ignore centering on X-axis?
 * @property {Boolean} [ignoreY=false]       ignore centering on Y-axis?
 * @property {String}  [activedCName=""]     document.body's className for when the menu is shown
 */
/**
 * an associative array of geometry of {@link BAJL.Balloon}.
 * @name BAJL.Balloon.geometry
 * @namespace geometry object of {@link BAJL.Balloon}.
 * @property {Number} posX       X-coordinate of the balloon (px)
 * @property {Number} posY       Y-coordinate of the balloon (px)
 * @property {Number} offsetX    X-distance from original balloon position (px)
 * @property {Number} offsetY    Y-distance from original balloon position (px)
 * @property {Number} width      width (border-box width) of the balloon (px)
 * @property {Number} height     height (border-box height) of the balloon (px)
 */
/**
 * callback functions for {@link BAJL.Balloon}
 * @name BAJL.Balloon.callback
 * @namespace callback functions for {@link BAJL.Balloon}
 */
/**
 * a callback for when the balloon is shown
 * @name BAJL.Balloon.callback.onShow
 * @function
 * @param {BAJL.Balloon.geometry} geom    an associative array of balloon geometry
 */
/**
 * a callback for when the balloon is moved position
 * @name BAJL.Balloon.callback.onMove
 * @function
 * @param {BAJL.Balloon.geometry} geom    an associative array of balloon geometry
 */
/**
 * a callback for when the balloon's size is changed
 * @name BAJL.Balloon.callback.onResize
 * @function
 * @param {BAJL.Balloon.geometry} geom    an associative array of balloon geometry
 */
/**
 * a callback for when the balloon is hidden
 * @name BAJL.Balloon.callback.onHide
 * @function
 * @param {BAJL.Balloon.geometry} geom    an associative array of balloon geometry
 */
/**
 * a callback for when the balloon's content is changed.
 * @name BAJL.Balloon.callback.onContentChange
 * @function
 * @param {BAJL.Balloon.geometry} geom    an associative array of balloon geometry
 */



/* -------------------- for backward compatibilities -------------------- */

if (BAJL.settings.common.useBackCompat) {
	BAJL.CreateBackCompat({
		  'BABalloon' : BAJL.Balloon
	});
}



})(jQuery);

