if (!this.Rf) this.Rf = {};
(function(ns, $) {

ns.Require('jQuery');
ns.Require('EventDispatcher', ns);

/**
 * (Abstract) Base tooltip class. This class should not be instantiated directly
 * @param {Object} owner jQuery selector of the object to whom the tooltip pertains
 * @param {Object} options Settings used to initialize the tooltip
 */
ns.Tooltip = function(owner, options) {
	ns.EventDispatcher.call(this, options);
	if (owner) this._Owner = $(owner);
	if (!this.ID) this.ID = 'Tooltip' + new Date().valueOf();
};
/**
 * Events that can be dispatched by a tooltip
 */
ns.Tooltip.Events = {
	/**
	 * Dispatched when the tooltip appears on screen
	 */
	Show : 'show',
	/**
	 * Dispatched when the tooltip is removed from the screen
	 */
	Hide : 'hide'
};
/**
 * Positions that the tooltip can be in, relative to its owner
 */
ns.Tooltip.Anchor = {
	/**
	 * ns.Tooltip appears to either the top or the bottom of its owner
	 */
	Vertical : 'vertical',
	/**
	 * ns.Tooltip appears to either the left or the right of its owner
	 */
	Horizontal : 'horizontal'
};
/**
 * The tooltip that is currently being displayed
 */
ns.Tooltip.Current = null;
ns.Tooltip.prototype = new ns.EventDispatcher({
	constructor : ns.Tooltip,
	/**
	 * (Protected) The element to which the tooltip pertains
	 */
	_Owner : null,
	/**
	 * (Protected) The physical element that houses the tooltip
	 */
	_Self : null,
	/**
	 * (Protected) When true, prevents the tooltip's Close method from removing the tooltip
	 */
	_PreventClose : false,
	/**
	 * The tooltip's positioning preference
	 */
	Anchor : ns.Tooltip.Anchor.Vertical,
	/**
	 * Indicates whether or not the tooltip is currently being displayed
	 */
	IsOpen : false,
	/**
	 * The ID to be given to the tooltip. (Autogenerated if null)
	 */
	ID : null,
	/**
	 * Class name to be given to the tooltip. (Defaults to 'tooltip')
	 */
	ClassName : 'tooltip',
	/**
	 * HTML template used to render the tooltip. The tooltip's content will go into the element marked with role="tooltip".
	 */
	Template : '<div><div role="tooltip"></div></div>',
	/**
	 * The z-index used for tooltips
	 */
	ZIndex : 6000,
	/**
	 * The jQuery selector to find the parent element of all tooltips
	 */
	ParentSelector : 'body',
	/**
	 * The duration of the animation used when showing/hiding tooltips
	 */
	AnimationDuration : 150,
	/**
	 * The number of pixels of space between the tooltip and its owner
	 */
	Margin : 5,
	/**
	 * Load's the tooltip's content, and then displays the tooltip
	 */
	Open : function() {
		if (ns.Tooltip.Current) ns.Tooltip.Current.Remove();
		ns.Tooltip.Current = this;
		this.AcquireContent(function(data) { this.Init(data); });

		return this;

	},
	OnShow : null,
	/**
	 * Removes the tooltip from the screen, if allowed
	 */
	Close : function() {
		var me = this;
		if (!me._PreventClose && me.IsOpen) {
			if (ns.Tooltip.Current == me) ns.Tooltip.Current = null;
			me.Dispatch(ns.Tooltip.Events.Hide);
			me._Self.addClass('transitioning').fadeOut(me.AnimationDuration, function() { me.Remove(); });
		}
		return me;
	},

	/**
	 * Removes the tooltip from the DOM
	 */
	Remove : function() {
	    this._Owner.removeAttr('aria-describedby');
		if (this._Self) this._Self.remove();
		delete this._Self;
		this._PreventClose = this.IsOpen = false;
		if (ns.Tooltip.Current == this) ns.Tooltip.Current = null;
	},
	/**
	 * Positions the tooltip relative to its owner and depending on available screenspace
	 */
	Place : function() {
		var me = this,
			ownerP = me._Owner.offset(),
			ownerW = me._Owner.outerWidth(),
			ownerH = me._Owner.outerHeight(),
			myW = me._Self.outerWidth(),
			myH = me._Self.outerHeight(),
			winW,
			winH,
			marginT,
			marginB,
			newClass,
			css = {position:'absolute', 'z-index':me.ZIndex};

		switch (me.Anchor) {
			case ns.Tooltip.Anchor.Horizontal:
				winW = $(window).width();
				css.top = ownerP.top - (myH - ownerH) / 2;
				if (winW / 2 - ownerP.left < 0) {
					newClass = 'right';
					css.left = ownerP.left - myW - me.Margin - 20;
				}
				else {
					newClass = 'left';
					css.left = ownerP.left + ownerW + me.Margin;
				}
				break;
			case ns.Tooltip.Anchor.Vertical:
			default:
				winH = $(window).height(),
				marginT = ownerP.top - document.documentElement.scrollTop,
				marginB = winH - marginT - ownerH;
				css.left = ownerP.left - (myW - ownerW) / 2;
				/*if (marginB > marginT) {
					newClass = 'bottom';
					css.top = ownerP.top + ownerH  + me.Margin;
				}
				else {
					newClass = 'top';
					css.top = ownerP.top - myH - me.Margin;
				}*/
				css.top = ownerP.top - myH - me.Margin;
				break;
		}
		if (css.left < 0) css.left = 0;
		if (css.top < 0) css.top = 0;

		me._Self.addClass(newClass).css(css);
		return me;
	},
	/**
	 * Meant to be overriden by descendant classes. Acquires the content for the tooltip
	 * @param {Function} callback A function that is executed using the acquired data
	 */
	AcquireContent : function(callback) {
		callback.call(this, 'Unimplemented');
		return this;
	},
	/**
	 * Initializes and draws the tooltip
	 * @param {String} data The content to place inside of the tooltip
	 */
	Init : function(data) {
		
		
		var parent = $(this.ParentSelector),
			me = this,
			onmouseover = function(e) { me._PreventClose = true },
			onmouseout = function(e) { me._PreventClose = false; setTimeout(function() { me.Close(); }, 250); };
		
		if (document.getElementById(me.ID)) {
			$('#' + me.ID).remove();
		}
		
		me._Self = $(me.Template);
		me._Self.find('*[role="tooltip"]:first').html(data);
		me._Self.css({position:'absolute'})
				.hide()
				.addClass(me.ClassName)
				.attr('id', me.ID)
				.appendTo(parent)
				.mouseover(onmouseover)
				.mouseout(onmouseout)
				.focus(onmouseover)
				.blur(onmouseout)
				.addClass('transitioning')
				.fadeIn(me.AnimationDuration, function() { if (me && me._Self) me._Self.removeClass('transitioning'); });
		setTimeout(function() {
			if (me._Self && !me.IsOpen) {
				me.Place()._Owner.attr('aria-describedby', me.ID);
				me.IsOpen = true;
				me.Dispatch(ns.Tooltip.Events.Show);
			}
		}, 0);

		return me;
	},
	/**
	 * Sets a tooltip to show and hide on two different events
	 * @param {Number} wait Number of milliseconds to wait before showing the tooltip
	 * @param {String} selector (Optional) jQuery selector of the object(s) to watch. If null, then the tooltip's owner
	 * @param {String} showEvent Type of the event that causes the tooltip to open
	 * @param {String} hideEvent Type of the event that causes the tooltip to hide
	 */
	WatchEvent : function(wait, selector, showEvent, hideEvent) {
		var target = selector ? $(selector) : this._Owner,
			me = this,
			isPrimed = false;
		target.bind(showEvent, function() {
			isPrimed = true;
			setTimeout(function() { if (isPrimed) me.Open(); }, wait);
		}).bind(hideEvent, function() {
			isPrimed = false;
			me.Close();
		});
		return me;
	},
	/**
	 * Sets a tooltip to show on focus and hide on blur
	 * @param {Number} wait (Optional) Number of milliseconds to wait before showing the tooltip
	 * @param {String} selector (Optional) jQuery selector of the object(s) to watch. If null, then the tooltip's owner.
	 */
	WatchFocus : function(wait, selector) {
		return this.WatchEvent(wait ? wait : 0, selector, 'focus', 'blur');
	},
	/**
	 * Sets a tooltip to show on mouseover and hide on mouseout
	 * @param {Number} wait (Optional) Number of milliseconds to wait before showing the tooltip
	 * @param {String} selector (Optional) jQuery selector of the object(s) to watch. If null, then the tooltip's owner.
	 */
	WatchHover : function(wait, selector) {
		return this.WatchEvent(wait ? wait : 250, selector, 'mouseover', 'mouseout');
	}
});

})(Rf, jQuery);
