/*====================================*\
|| ################################## ||
|| # iDeal 2.0 Ajax Core            # ||
|| # ------------------------------ # ||
|| # Copyright ©2008 MediaPulse     # ||
|| # ------------------------------ # ||
|| # Revision Alpha                 # ||
|| ################################## ||
\*====================================*/

/**
 * Root "pop up" controller. This class is for menus, tooltips, or other items that appear on
 * mouseover and exit on mouseout. Each instance of this class controls one popup.
 */
iDeal.PopUp = Class.create({
	/**
	 * Is the mouse currently over the popup trigger or it's target?
	 *
	 * @var boolean
	 * @access protected
	 */
	_over: false,
	
	/**
	 * Is the pop up in an open state?
	 *
	 * @var boolean
	 * @access protected
	 */
	_open: false,
	
	/**
	 * The target div that is to become visible on mouseover.
	 *
	 * @var boolean
	 * @access protected
	 */
	_myDiv: null,
	
	/**
	 * The element which fires the mouseover event.
	 *
	 * @var element pointer
	 * @access protected
	 */
	_myHandle: null,
	
	/**
	 * The CSS class of the popup
	 *
	 * @var string
	 * @access protected
	 */
	_class: '',
	
	/**
	 * Amount of time in seconds to wait after mouseout before fading out the popup.
	 *
	 * @var float
	 * @access protected
	 */
	_timeOut: 1,
	
	/**
	 * Duration of the appearance effect.
	 *
	 * @var float
	 * @access protected
	 */
	_fadeIn: .1,
	
	/**
	 * Duration of the fade out effect
	 * 
	 * @var float
	 * @access protected
	 */
	_fadeOut: .3,
	
	/**
	 * Type of effect used to appear the popup
	 * See scriptaclous Library for a complete list of values.
	 *
	 * @var string
	 * @access protected
	 */
	_enterTween: 'Appear',

	/**
	 * Type of effect used to exit the popup.
	 * See scriptaclous Library for a complete list of values.
	 *
	 * @var string
	 * @access protected
	 */
	_exitTween: 'Fade',
	
	/**
	 * Position of the popup relative to the trigger.
	 *
	 * @var string
	 * @access protected
	 */
	_position: 'below',
	
	/**
	 * Horizontal offset from the above.
	 *
	 * @var integer
	 * @access protected
	 */
	_offsetX: 0,
	
	/**
	 * Vertical offset from the position default
	 *
	 * @var integer
	 * @access protected
	 */
	_offsetY: 0,
	
	/**
	 * Flag to show that we are animating the popup. We ignore
	 * all events when this is set true.
	 */
	_transitionInProgress: false,
	
	/**
	 * Require clicking to open the popup?
	 *
	 * @var boolean
	 * @access protected
	 */
	_clickToOpen: false,
	
	/**
	 * Cache for the bound onClick function
	 * See _onClick
	 *
	 * @var bound function object
	 * @access public
	 */
	onClick: null,
	
	/**
	 * Cache for the bound mouseOver function.
	 * See _mouseOver
	 *
	 * @var bound function object
	 * @access public
	 */
	mouseOver: null,
	
	/**
	 * Cache for the bound mouseOut function
	 * See _mouseOut
	 *
	 * @var bound function object
	 * @access public 
	 */
	mouseOut: null,
	
	/**
	 * Cache for the bound _appearComplete function.
	 * See _appearCompleteProto
	 *
	 * @var bound function object
	 * @access protected
	 */
	_appearComplete: null,
	
	/**
	 * Cache for the bound _hideComplete function
	 * see _hideCompleteProto
	 *
	 * @var bound function object
	 * @access protected
	 */
	_hideComplete: null,
	
	/**
	 * Cache for the bound _doTimeOut function
	 * See _doTimeOutProto
	 *
	 * @var bound function object
	 * @access protected (technically public since it is called by setTimeout)
	 */
	_doTimeOut: null,
	
	/**
	 * Do we allow the popup to be dragged?
	 *
	 * @var boolean
	 * @access protected
	 */
	_allowDrag: false,
	
	/**
	 * Do we need to appear after the current transition?
	 *
	 * @var boolean
	 * @access protected
	 */
	_delayedAppear: false,
	
	/**
	 * Do we need to hide after the current transition?
	 *
	 * @var boolean
	 * @access protected
	 */
	_delayedHide: false,
	

	/**
	 * Initialize the popup controller.
	 *
	 * @param hash {
	 *		target - target div of the controller REQUIRED
	 *		class - CSS class of the popup REQUIRED
	 *		fadeIn - time in seconds to fade in
	 *		fadeOut - time in seconds to fade out
	 *		timeOut - time in seconds to wait before fade out after mouse out
	 *		allowDrag - whether pop up is draggable
	 *		enterTween - effect used for appearances
	 *		exitTween - effect used for exits.
	 *		position - position of the popup, relative the target.
	 *		offsetX - offset of the popup on the x axis from its initial position.
	 *		offsetY - offset of the popup on the y axis from its initial position.
	 *	}
	 * @return  void
	 * @access public (constructor)
	 * @throws missingRequiredParam
	 */
	initialize: function ( params ) {
		
		this._setHandle(params.target);			
		this._setClass(params.cssClass);
		this._setParams(params);
		this._setUpPopUp();		
		this._compileBoundFunctions();		
		this._registerEvents();
		this._setDrag();
		
		if (iDeal.debug)
		{
			iDeal.console.log('Pop Up Registered.');
		}	
	},
	
	/**
	 * Set Up the pop up object by moving it to the bottom of the document.
	 * 
	 * @return void
	 * @access protected
	 */
	_setUpPopUp: function()
	{
		// Now get the popup.
		this._myDiv = this._myHandle.next('.'+this._class);
		
		// Pull it out of the DOM
		this._myDiv = this._myDiv.remove();
		
		// Now reinsert at end of body.
		$$('BODY')[0].insert({bottom: this._myDiv});
	},
	
	/**
	 * Set the class that indicates the element to be made a pop up.
	 * 
	 * @param string the class name to attach.
	 * @return void
	 * @access protected
	 */
	_setClass: function( cssClass )
	{
		if (!(this._class = cssClass))
		{
			throw missingRequiredParam;
		}
	},
	
	/**
	 * Set the triggering object that causes the popup to appear.
	 *
	 * @param object to make into a popup.
	 * @return void
	 * @access protected
	 */
	_setHandle: function ( target )
	{
		if (!(this._myHandle = $( target)))
		{
			throw missingRequiredParam;
		}
	},
	
	/**
	 * Setup the draggable.
	 * 
	 * @return void
	 * @access protected
	 */
	_setDrag: function()
	{
		if (this._allowDrag)
		{
			new Draggable(this._myDiv);	
		}	
	},
	
	/**
	 * Cache the bound functions (eases garbage collection, prevents memory leakage)
	 * 
	 * @return void
	 * @access protected
	 */
	_compileBoundFunctions: function()
	{
		this.mouseOver = this._mouseOver.bindAsEventListener(this);
		this.mouseOut = this._mouseOut.bindAsEventListener(this);
		this.onClick = this._onClick.bindAsEventListener(this);
		this._appearComplete = this._appearCompleteProto.bind(this);
		this._hideComplete = this._hideCompleteProto.bind(this);
		this._doTimeOut = this._doTimeOutProto.bind(this);
	},
	
	/**
	 * Set the parameters of this popup.
	 *
	 * @var object parameters.
	 * @return void
	 */
	_setParams: function(params)
	{
		this._fadeIn = (params.fadeIn) ? params.fadeIn : this._fadeIn;
		this._fadeOut = (params.fadeOut) ? params.fadeOut : this._fadeOut;
		this._timeOut = (params.timeOut) ? params.timeOut : this._timeOut;
		this._enterTween = (params.enterTween) ? params.enterTween : this._enterTween;
		this._exitTween = (params.exitTween) ? params.exitTween : this._exitTween;
		this._position = (params.position) ? params.position : this._position;
		this._offsetX = (params.offsetX) ? params.offsetX : this._offsetX;
		this._offsetY = (params.offsetY) ? params.offsetY : this._offsetY;
		this._clickToOpen = (params.clickToOpen) ? params.clickToOpen : this._clickToOpen;
		this._allowDrag = (params.allowDrag) ? params.allowDrag : this._allowDrag;
	},
	
	/**
	 * Register the events to the popup and it's trigger.
	 * 
	 * @return void
	 * @access protected
	 */	 
	_registerEvents: function()
	{
		this._myDiv.observe( 'mouseover', this.mouseOver );
		this._myDiv.observe( 'mouseout', this.mouseOut );
		this._myHandle.observe( 'click', this.onClick );	
		this._myHandle.observe( 'mouseover', this.mouseOver );
		this._myHandle.observe( 'mouseout', this.mouseOut );
	},
	
	/**
	 * Caculate the absolute vertical position of the popup adjusting for offsetY
	 *
	 * @return integer
	 */
	_getStartingPositionY: function()
	{
		switch (this._position)
		{
			case 'above':
				return this._myDiv.getHeight() * -1 + this._offsetY;
			case 'infront':
			case 'left':
			case 'right':
				return 0 + this._offsetY;
			case 'below':
			default:
				return this._myHandle.getHeight() + this._offsetY;
		}
	},

	/**
	 * Caculate the absolute horizontal position of the popup adjusting for offsetX
	 *
	 * @return integer
	 */
	_getStartingPositionX: function()
	{
		switch (this._position)
		{
			case 'left':
				return this._myDiv.getWidth() * -1 + this._offsetX;
			case 'right':
				return this._myHandle.getWidth() + this._offsetX;
			case 'below':
			case 'above':
			case 'infront':
			default:
				return 0 + this._offsetX;
		}
	},
	
	/**
	 * Primitive mouseover function.
	 *
	 * @param event
	 * @return void
	 * @access protected
	 */
	_mouseOver: function ()
	{
		this._over = true;
				
		if (!this._open && !this._clickToOpen)
		{				
			this.appear();	
		}
	},
	
	/**
	 * Primitive onClick function
	 *
	 * @param event
	 * @return void
	 * @access protected
	 */
	_onClick: function ()
	{
		if(!this._open && this._clickToOpen)
		{
			this.appear();
		}
	},
	
	/**
	 * Primitive mouseout function
	 * 
	 * @param event
	 * @return void
	 * @access protected
	 */
	_mouseOut: function ()
	{
		this._over = false;		
		setTimeout( this._doTimeOut, this._timeOut * 1000 );
	},
	
	/**
	 * Resolve the timeout of a mouseout
	 *
	 * @param void
	 * @return void
	 * @access protected
	 */
	_doTimeOutProto: function ()
	{
		if (!this._over && this._open )
		{
			this.hide();	
		}
	},
	
	/**
	 * Appear the popup Function
	 * 
	 * @param void
	 * @return void
	 * @access public
	 */
	appear: function()
	{
		if (!this._transitionInProgress)
		{

			this._transitionInProgress = true;

			this._myDiv.clonePosition(this._myHandle, 
				{ 
					setWidth: false, 
					setHeight: false,
					offsetTop: this._getStartingPositionY(),
					offsetLeft: this._getStartingPositionX()
				}
			);
						
			this._myHandle.addClassName('active');
			
			new Effect[this._enterTween]( this._myDiv, {duration: this._fadeIn, afterFinish: this._appearComplete } );
		
		}
		else
		{
			this._delayedAppear = true;	
		}
	},
	
	/**
	 * Actions to take after the appearance tween is completed. Most of this is
	 * status related cleanup.
	 *
	 * @return void
	 * @access protected
	 */
	_appearCompleteProto: function ()
	{
		this._transitionInProgress = false;
		this._open = true;
		this._delayedAppear = false;
		
		if (this._delayedHide)
		{
			this._delayedHide = false;
			this.hide();
		}
	},
	
	/**
	 * Fade the popup Function
	 * 
	 * @param void
	 * @return void
	 * @access public
	 */
	hide: function()
	{
		if(!this._transitionInProgress)
		{
			this._transitionInProgress = true;
			
			new Effect[this._exitTween]( this._myDiv, {duration: this._fadeOut, afterFinish: this._hideComplete } );
			
		}
		else
		{
			this._delayedHide = true;	
		}
	},
	
	/**
	 * Cleanup after hide tween is completed.
	 *
	 * @return void
	 * @access protected
	 */
	_hideCompleteProto: function()
	{
		this._transitionInProgress = false;
		this._myHandle.removeClassName('active');
		this._open = false;
		this._delayedHide = false;
		
		if (this._delayedAppear)
		{
			this._delayedApear = false;
			this._appear();
		}
	}

});