/* 
	Class: BackMonitor

	This class attempts to monitor for browser Back button events and provide
	a means to fire a callback when one is detected.
	
	The callback is fired only once and the monitor will then stop itself.
	
	The monitor works using a URL fragment in the window it is constructed with
	as an argument.  This fragment will overwrite any previously existing one.

	Note: 
	
	BackMonitor assumes a jQuery object is available.
	
	Todo: 
	
	The constructor 'inWindow' argument is intended so that the class may,
	if needed in certain browsers, attempt to operate on a window other than
	the window it was sourced into.  This needs to be tested and fleshed
	out and may be difficult for several reasons to do with both browser
	back button behavior and also in the internal use of the 
	setInterval/clearInterval methods, which expect strings to eval, not
	object/method/variable references.  This makes calling an object method
	from an interval timer less straightforward.
		

*/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*                             Constructor                               */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* 
	Constructor: BackMonitor

	Construct a new monitor instance to operate in the inWindow window.
	
	Arguments:
	
	inWindow - the window object in which this monitor will operate
	inCheckInterval - the interval in millisends between monitor checks.
		If null or not a number, the default value of 500 will be used.
	inMonitorWindowVariableName - the name (as a string) of the window scope
		variable to which this newly constructed instance is being assigned.
	inBackDetectedCallback - a js function callback to execute when a 'back'
		event has been detected.
		
*/
function BackMonitor( 
		inWindow,
		inCheckInterval,
		inMonitorWindowVariableName,
		inBackDetectedCallback )
{

	/*
		The window object to monitor.
	*/
	this.window = inWindow;
	
	/*
		The interval to check for a back button press.
	*/
	this.checkInterval = isNaN( inCheckInterval) ? 500 : inCheckInterval;
	
	/*
		The name of the variable that this instance was assigned to.  This
		is needed because the code used in setInterval/setTimeout type of
		calls is evaluated in window scope and we can't use 'this.checkBack()'
		as the code, we must use inMonitorWindowVariableName + '.checkBack()'
	*/
	this.windowVariableName = inMonitorWindowVariableName;

	/*
		The callback to execute when a 'back' button 'event' is noticed.
	*/
	this.detectedCallback = inBackDetectedCallback;
	
	/*
		The window interval id returned by setInterval()
	*/
	this.timerId = null;
	
	/*
		Need to tag when we actually start for testing lastFragState correctly 
	*/
	this.started = false;
}
	
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*                            Public Methods                             */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* 
	Method: start()
	
	Starts the BackMonitor instance.
	
*/
BackMonitor.prototype.start  = function()
	{
		if ( ! this.started )
		{
			// console.log( new Date() + " BackMonitor start" );
			
			this.setFragment();
			
			this.setTimer();
			
			this.started = true;
		}
		else
		{
			//console.log( new Date() + " BackMonitor already started");
		}
	};

/* 
	Method: stop()
	
	Stops the monitor.
	
*/
BackMonitor.prototype.stop  = function()
	{
		// console.log( new Date() + " BackMonitor stop " + this );
		
		if ( this.timerId != null )
		{
			// console.log( new Date() + " clearing " + this.timerId );
			this.window.clearInterval( this.timerId );
			
			this.timerId = null;
		}
		
		this.removeFragment();
		
		this.started = false;
	};

/* 
	Method: toString()

	Override.  Returns a string representation of this object.
	
*/
BackMonitor.prototype.toString  = function()
	{
		return "[object BackMonitor]";
	};

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*                         Private Methods                               */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* private String : currentLocation()

	Returns the current 'location' of the window object as a string.
*/
BackMonitor.prototype.currentLocation  = function()
	{
		var locAttr = $.browser.msie ? "URL" : "location";
		// return $( document ).attr( locAttr ).toString();
		return window.location.toString();
	};
	
/* private void : checkBack()

	Tries to detect a history change.  When detected this method will
	execute the callback routine that was provided to the instance during
	construction.  It will then stop the monitoring activity.
	
	This method assumes that the monitor has been started, and therefore that
	if the window does not contain our URL fragment that the user has hit 'back'.
*/
BackMonitor.prototype.checkBack  = function()
	{
		/*
			If the monitor is started and there is no fragment then
			the user has hit the back button.
			
			When that happens, execute the callback, if any, and stop.
		*/
		if ( this.started )
		{
			if ( ! this.windowHasFragment() )
			{
				// console.log( new Date() + ' BACK detected' );
				
				if ( typeof this.detectedCallback === 'function' )
				{
					this.detectedCallback();
				}
				
				this.stop();
			}
			else
			{
				// console.log( new Date() + " no back detected "+this );
			}
		}
		else
		{
			// not started?
			// console.log( new Date() + " errant checkBack() called - we are not started. "+this );
		}
		
	};

/* private void : setTimer( inBackMonitorVariableName )

	Function to set the backWatchTimerId of the window interval call
	which watches for 'back' button activity.
	
	Needs to check to see if there is already a backWatchTimerId defined
	and cancel that one if so, replacing it with the new one.

	Having this done as a top level function avoids a closure
	in the fbPageOptions callbacks
*/
BackMonitor.prototype.setTimer  = function()
	{
		// console.log( new Date() + " BackMonitor set timer" );
		
		if ( this.timerId != null )
		{
			// console.log( new Date() + " first, clear previous timerId: " + this.timerId );
			clearTimerId( this.timerId );
		}
		
		this.timerId = this.window.setInterval( 
							this.windowVariableName + ".checkBack()",
							this.checkInterval );
							
		// console.log( new Date() + " setInterval( '" + this.windowVariableName + ".checkBack()" + "', " + this.checkInterval + ") -> backWatchTimerId: " + this.timerId );
	};

/* private boolean: windowHasFragment()

	Returns true if the window currently has the URL fragment in its location. 
*/
BackMonitor.prototype.windowHasFragment  = function()
	{
		var location = this.currentLocation();
		
		return location.indexOf( BackMonitor.FRAGMENT ) > -1;
	};

/* private void: setFragment()

	Set our url fragment - clear out any previous if there.  Hacky, veeeewwy hacky.
*/
BackMonitor.prototype.setFragment  = function()
	{
		var curLocation = this.currentLocation();
		var newLocation = curLocation.replace( /#[^&\?]*/, '');
		newLocation += "#" + encodeURIComponent(BackMonitor.FRAGMENT);
		
		if ( newLocation !== curLocation )
		{
			// console.log( new Date() + " change loc from " + curLocation + " to " + newLocation );
			this.window.location.href = newLocation;
		}
	};

/* private void: removeFragment()

	Remove our URL fragment if it exists.  This will set the URL fragment to an
	empty '#' in order to avoid a browser page reload.
*/
BackMonitor.prototype.removeFragment  = function()
	{
		var curLocation = this.currentLocation();
		
		var newLocation = curLocation;

		if ( BackMonitor.MATCH_FRAGMENT_REGEXP.test( curLocation) )
		{
			// console.log( "cur: " + curLocation );
			newLocation = curLocation.replace( BackMonitor.MATCH_FRAGMENT_REGEXP, '');
			// console.log( "new: " + newLocation );

			// don't remove the existence of a fragment - some browsers will reload the page
			// if you do.  So set it to an innocuous value
			newLocation += "#";
		
		}
		
		if ( newLocation !== curLocation )
		{
			// console.log( new Date() + " change loc from " + curLocation + " to " + newLocation );
			this.window.location.href = newLocation;
		}
	};

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*                         Static Variables                              */
/* The class's function has now been defined, this provides a window     */
/* object to which we may assign variables, be they literals, objects,   */
/* methods.  This provides a pseudo-namespace scoping scheme that we can */
/* use to put 'static' items.                                            */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
	The URL fragment we use to track window.location changes
*/
BackMonitor.FRAGMENT = "b.mon.tag";

/*
	The regexp we can use to find the URL fragment.
*/
BackMonitor.MATCH_FRAGMENT_REGEXP = 
	new RegExp( "#" + encodeURIComponent( BackMonitor.FRAGMENT ) );	

// - - - E N D   C L A S S - - - - - - - - - - - - - - - - - - - - - - - - - - 
