// fix Function.apply
if (!Function.apply) Function.prototype.apply = function(thisArg, argArray) {
	// Create a function reference to the calling object
	thisArg.____apply = this;
	// Build a string that will later execute the function 
	var fnApplyString = "thisArg.____apply(";
	
	// Check that the argArray are according to standards. Must be array...
	if (typeof(argArray) == "object" && typeof(argArray.length) != "undefined") {
		var quote, comma;
		for(i = 0; i < argArray.length; i++){
			// Add quotes only for string values
			quote = typeof(argArray[i]) == "string" ? "\"" : "";
			// add a comma as param-separator for all except the last param
			comma = (i+1) < argArray.length ? "," : "";
			// add the param to the string we are building
			fnApplyString += quote + argArray[i] + quote + comma;
		}
	} else if (typeof(argArray) != "undefined") {
		// argArray is not an array, but it is defined, so we notify that the 
		// argArray must be an array when specified (according to ECMAScript specs)
		throw TypeError;
	}
	
	fnApplyString += ")";
	// eval the expression, now using the right object, function and parameters
	var retVal = eval(fnApplyString);
	// clean up our expando property reference
	delete thisArg.____apply;
	// return the value that the function was to return
	return retVal;
};

// fix array methods - I believe I borrowed this from Dean Edwards's IE7 script - same license ;-)
if (![].push) Array.prototype.push = function() {
	for(var i=0; i<arguments.length; i++)
		this[this.length-1] = arguments[i];
	return this.length;
};


if (![].pop) Array.prototype.pop = function() {
	var $item = this[this.length-1];
	this.length--;
	return $item;
};

if (![].shift) Array.prototype.shift = function () {
	return this.splice(0, 1)[0];
};

if (![].splice) Array.prototype.splice = function (s, d) {
	var max = Math.max,
		min = Math.min,
		a = [], // The return value array
		e,  // element
		i = max(arguments.length - 2, 0),   // insert count
		k = 0,
		l = this.length,
		n,  // new length
		v,  // delta
		x;  // shift count

	s = s || 0;
	if (s < 0) {
		s += l;
	}
	s = max(min(s, l), 0);  // start point
	d = max(min(isNumber(d) ? d : l, l - s), 0);    // delete count
	v = i - d;
	n = l + v;
	while (k < d) {
		e = this[s + k];
		if (!isUndefined(e)) {
			a[k] = e;
		}
		k += 1;
	}
	x = l - s - d;
	if (v < 0) {
		k = s + i;
		while (x) {
			this[k] = this[k - v];
			k += 1;
			x -= 1;
		}
		this.length = n;
	} else if (v > 0) {
		k = 1;
		while (x) {
			this[n - k] = this[l - k];
			k += 1;
			x -= 1;
		}
	}
	for (k = 0; k < i; ++k) {
		this[s + k] = arguments[k + 2];
	}
	return a;
};

if (![].unshift) Array.prototype.unshift = function () {
	this.splice.apply(this,
		[0, 0].concat(Array.prototype.slice.apply(arguments)));
	return this.length;
};

/*
	unFocus.Utilities.EventManager, version 1.0b (beta) (2005/12/16)
	Copyright: 2005, Kevin Newman (http://www.unfocus.com/Projects/)
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/
// make sure faux-namespace is available before adding to it
if (typeof unFocus == "undefined") var unFocus = {};
if (!unFocus.Utilities) unFocus.Utilities = {};

/* Class EventManager
	Provides the interface and functionality to a Subscriber/Subscriber Pattern interface for
	classes to easily inherit or use. Event types are passed in during instantiation. I'm not
	sure that's the best way to do it, but that's how it is for now (comments are welcome). */
unFocus.Utilities.EventManager = function(arg) {
	this._listeners = {};
	for (var i = 0; i < arguments.length; i++) {
		this._listeners[arguments[i]] = [];
	}
};

/* Method: addEventListener
	A public method that adds an event listener to be called when the hash changes. */
unFocus.Utilities.EventManager.prototype.addEventListener = function($type, $listener) {
	// check that listener is not in list
	for (var i = 0; i < this._listeners[$type].length; i++)
		if (this._listeners[$type][i] == $listener) return;
	// add listener to appropriate list
	this._listeners[$type].push($listener);
};

/* Method: removeListener
	A public method that removes an event listener. */
unFocus.Utilities.EventManager.prototype.removeEventListener = function($type, $listener) {
	// search for the listener method
	for (var i = 0; i < this._listeners[$type].length; i++) {
		if (this._listeners[$type][i] == $listener) {
			this._listeners.splice(i,1);
			return;
		}
	}
};

/* Method: notifyListeners
	Notifies listeners when an event accurs. */
unFocus.Utilities.EventManager.prototype.notifyListeners = function($type, $data) {
	for (var i = 0; i < this._listeners[$type].length; i++)
		this._listeners[$type][i]($data);
};

/*
	unFocusFlash.Utilities, version 0.5 (2006/04/26)
	Copyright: 2004-2006, Kevin Newman (http://www.unfocus.com/)
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/

if (typeof unFocus == "undefined") var unFocus = {};
if (!unFocus.Flash) unFocus.Flash = {};

unFocus.Flash.Utilities = {
	getSwfReference: function($swfId) {
		var $movieObj, $document=document;
		if ($document.embeds && $document.embeds[$swfId])
			$movieObj = $document.embeds[$swfId];
		/* else if ($window[$swfId]))
			$movieObj = $window[$swfId];
		*/ else if ($document[$swfId])
			$movieObj = $document[$swfId];
		/*else if ($document.getElementById)
			$movieObj = $document.getElementById($swfId);*/
		return $movieObj;
	}
};

/*	unFocusFlashCommunicator, version 0.8 (alpha) (2006/03/10)
	Copyright: 2005, Kevin Newman (http://www.unfocus.com/)
	License: http://creativecommons.org/licenses/LGPL/2.1/ */
/*if (typeof unFocus == "undefined") var unFocus = {};
if (!unFocus.Flash) unFocus.Flash = {};*/

// set up function for IE to use to create the fscommand catcher
if (window.ActiveXObject && window.print && !window.opera)
	// Communicator.vbs
	document.write('<scr'+'ipt type="text/vbscript"\>Sub unFocusCreateFSCommand (n)\nExecuteGlobal "Sub " & n & ',
		'"_FSCommand(ByVal c, ByVal a):" & "call " & n & "_','DoFSCommand(c, a):" & "End Sub"\nEnd Sub</scr'+'ipt\>');

// do  initialization stuff
unFocus.Flash.Communicator = function(_movieId, _communicatorSWF) {
  with (unFocus.Flash.Utilities) {
	/* provides reference for methods attatched to other objects 
	(like _CatchFSCommand) it will compress well too ;-) */
	var _this = this,
		_setVariableQueue = [],
		_comDivId = "unFocusFlashCommunicatorDiv",
		//_comSwfId = 'unFocusFlashCommunicatorSwf',
		_comDivRef,
		_connId,
		_movieObj;
	
	//var _movieId = (typeof _movieObj 'String')?_movieObj:_movieObj.getId();
	// a little emulated overloading here
	/*var _movieId;
	if (typeof _HTMLObj === "string") {
		_movieId = _HTMLObj;
	} else if (_HTMLObj.getId) {
		_movieId = _HTMLObj.getId();
		_this.setupHTML(_HTMLObj);
	}*/
	
	/*
	Private Method: _getFlashReference
		Tries to get a reference to the flash element
	*/
	/*function _getFlashReference() {
		// check embeds first, in case static html is used, in which case getElementById would 
		// return a reference to the object tag
		if (document[_movieId]) {
			_movieObj = document[_movieId];
		} else if (document.embeds && document.embeds[_movieId]){
			_movieObj = document.embeds[_movieId];
		} else {
			_movieObj = document.getElementById(_movieId);
		}
		alert(_movieObj);
	}*/
	
	/* Private Method: _routeFSCommand
		Set up FSCommand catch functions - catch, and route fscommand calls.
		:NOTE: "this" keyword is scoped to window, so use the _this ref instead */
	function _routeFSCommand($command, $arguments) {
		// do the command
		// :NOTE: Safari places "FSCommand:" in front of anything sent from flash with fscommand for some reason..
		// This might be due to Safari using getURL with FSCommand as the protocol, which might be causeing some 
		// of the strange behavior with refreshing, and multiple calls. :TODO: look into this to see what versions
		// of Flash and Safari are doing this - also note, this might have been solved in newer Safari and Flash versions.
		if ($command.indexOf('FSCommand:') != -1) $command = $command.substring(10, $command.length);
		
		switch ($command) {
			case "unFocusFlashTestFSCommand":
					/* get reference to the flash Object */
					if (!_movieObj)
						_movieObj = getSwfReference(_movieId);
					// if we are here, then fscommand works!
					_this.setVariable("unFocusJavascriptCommunication","unFocusJavascriptUseFSCommand");
				break;
			case "unFocusFlashSetConnID":
					_connId = $arguments;
					_cycleSetVariableQueue();
				break;
			default:
				// call user's DoFSCommand
				//_DoFSCommand($command, $arguments);
				_this.notifyListeners("FSCommand", [$command,$arguments]);
				// call DoFSCommand (will be ignored by stub method, if not defined by user)
				_this.DoFSCommand($command, $arguments);
		}
	}
	// these all route to _routeFSCommand
	window[_movieId + "_"+"DoFSCommand"] = _routeFSCommand;
	// catch function for IE
	if (typeof unFocusCreateFSCommand != "undefined") unFocusCreateFSCommand(_movieId);
	
	// stub DoFSCommand - to be overwritten by user for classic style DoFSCommand usage.
	_this.DoFSCommand = new Function;

	/* Private Method: _cycleSetVariableQueue
		If a call to setVariable is made before the flash movie and all participants are ready for it,
		it is added to a Queue. This method clears that Queue when things become ready */
	function _cycleSetVariableQueue() {
		var _localQueue = _setVariableQueue;
		_setVariableQueue = []; // empty the array
		if (_localQueue.length) {
			// we may need a delay here, to give the comSwf time to load (or just pile up the comSwfs in the html)
			for (var i = 0; i < _setVariableQueue.length; i++) {
				_this.setVariable(_localQueue[i].$name, _localQueue[i].$value);
			}
		}
	}
	
	/* Method: setVariable
		Sets the variable in flash. This method emulates Macromedia's setVariable method exactly even when it
		isn't supported natively (it does that using the LocalConnection technique). */
	_this.setVariable = function($name, $value) {
		if (!_movieObj)
			_movieObj = getSwfReference(_movieId);
		// when the native function is available, use that
		if (typeof _movieObj.SetVariable != "undefined") {/* && !window.opera */
			// overwrite the emulated (default) setVariable function
			_this.setVariable = function($name, $value) {
				_movieObj.SetVariable($name, $value);
			};
			_this.SetVariable = _this.setVariable;
			//_cycleSetVariableQueue();
			// now run this call
			_this.setVariable($name, $value);
		// check _connId - if there is none, then the following will not work. _connId is sent out from 
		// the swf file when it fails to detect fscommand (and thus fails the test for SetVariable).
		} else if (_connId) {
			///// do setup ...
			// create the div to hold the communication flash movie(s)
			if (!document.getElementById(_comDivId)) { // in case user sets it up manually
				var _comDivRef = document.createElement("div");
				_comDivRef.id = _comDivId;
				_comDivRef.style.position = "absolute";
				_comDivRef.style.top = "-900px";
				//_historyFrame.runtimeStyle.display = 'none';
				document.body.insertBefore(_comDivRef,document.body.firstChild);
			}
			
			// set up the html object to reuse for sending in the data
			var $element = new unFocus.Flash.HTML();
			$element.setSrc(_communicatorSWF);
			$element.setWidth(1);
			$element.setHeight(1);
			$element.setVersion("6");
			$element.setMinorRevision("29");
			$element.setAllowscriptaccess("always");
			
			///// overwrite the original function:
			_this.setVariable = function($name, $value) {
				// :NOTE: this cannot be used until the receiving flash movie passes out the connectionID
				// do localconnection thingy
				$element.setFlashvars("cid="+_connId+"&cmd=setVariable&vName="+$name+"&vValue="+$value);
				
				_comDivRef.innerHTML = $element.getHTML();
			};
			_this.SetVariable = _this.setVariable;
			//_cycleSetVariableQueue();
			// run this call
			_this.setVariable($name, $value);
		} else {
			// add to queue
			_setVariableQueue.push({$name:$name,$value:$value});
		}
	};
	
	/* 
	Method: SetVariable
		Provides an alias to setVariable for consistancy with Flash.
	*/
	_this.SetVariable = _this.setVariable;
	
	/*_this.getHTML = function() {
		return _HTMLObj;
	};*/
	
	// store ref to this object in a static array, for use by LongCallFSCommand
	unFocus.Flash.Communicator._instances.push(_this);
	
	_this._getMovieObj = function() {
		if (!_movieObj)
			_movieObj = getSwfReference(_movieId);
		return _movieObj; // will be different depending on if it's an object or an embed, or a SATAY
	};
  }
};

unFocus.Flash.Communicator.prototype = new unFocus.Utilities.EventManager("FSCommand");

/* static */
unFocus.Flash.Communicator._instances = [];
unFocus.Flash.Communicator.retrieveSwfId = function($swfName, $connId) {
	
};
unFocus.Flash.Communicator.LongCallFSCommand = function($swfName, $cmd, $args) {
	// loop through all swfs on the page, and figure out which one just called this method.
	// then send in the response.
	var _matches = [],
		_curMovieObj,
		_movieId;
	for (var i =0; i < this._instances.length; i++) {
		_curMovieObj = this._instances[i]._getMovieObj();
		// :TODO: add checks for Object src tags (look up Satay, and standar object tags)
		/*if (_curMovieObj.src && _curMovieObj.src.indexOf($swfName) != -1)
			_matches.push(_curMovieObj);
		else */
		// check if src is the same as requesting swf movie
		if (
			_curMovieObj.hasAttribute &&
			_curMovieObj.hasAttribute("src") &&
			_curMovieObj.getAttribute("src").indexOf($swfName) != -1
		) {
			// get movie id
			if (_curMovieObj.hasAttribute("id"))
				_movieId = _curMovieObj.getAttribute("id");
			else if (_curMovieObj.hasAttribute("name"))
				_movieId = _curMovieObj.getAttribute("name");
			_matches.push(this._instances[i]);
		}
	}
	if (_matches.length === 1)
		_matches[0].setVariable("unFocusJavascriptCommunication","unFocusJavascriptSetMovieId="+_movieId);
	// :TODO: add logic for if there are more than one embedded object with the same src
}
/*unFocus.Flash.Communicator.prototype.setupHTML = function($htmlObj) {
	$htmlObj.addFlashvar("unFocusFlashMovieId",$htmlObj.getId());
};*/


/*
unFocus.History, version 1.9 (alpha) (2006/04/14)
Copyright: 2005-2006, Kevin Newman (http://www.unfocus.com/Projects/HistoryKeeper/)
License: http://creativecommons.org/licenses/LGPL/2.1/
*/
//if (typeof unFocus == "undefined") var unFocus = {};

/*
	Class: unFocus.History
		A singleton with subscriber interface (<unFocus.Utilities.EventManager>) 
		that keeps a history and provides deep links for Flash and AJAX apps
*/
unFocus.History = (function() {

// use a closure to avoid poluting the global scope, and to discourage reinstantiation (like a singleton)
function Keeper() {
	// bool: initialize - whether or not the class has been initialized
	var _this = this,
		// set the poll interval here.
		_pollInterval = 200, _intervalID,
		// get the initial Hash state
		_currentHash;

	/*
	method: _getHash
		A private method that gets the Hash from the location.hash property.
	 
	returns:
		a string containing the current hash from the url
	*/
	var _getHash = function() {
		return location.hash.substring(1);
	};
	_currentHash = _getHash();
	/*
	method: _setHash
		A private method that sets the Hash on the location string (the current url).
	*/
	function _setHash($newHash) {
		window.location.hash = $newHash;
	}
	
	/*
	method: _watchHash
		A private method that is called every n miliseconds (<_pollInterval>) to check if the hash has changed.
		This is the primary Hash change detection method for most browsers. It doesn't work to detect the hash
		change in IE 5.5+ or various other browsers. Workarounds like the iframe method are used for those 
		browsers (IE 5.0 will use an anchor creation hack).
	*/
	function _watchHash() {
		var $newHash = _getHash();
		if (_currentHash != $newHash) {
			_currentHash = $newHash;
			_this.notifyListeners("historyChange", $newHash);
		}
	}
	// set the interval
	if (setInterval) _intervalID = setInterval(_watchHash, _pollInterval);
	
	/*
	method: getCurrentBookmark
		A public method to retrieve the current history string
	
	returns:
		The current History Hash
	*/
	
	
	_this.getCurrent = function() {
		return _currentHash;
	};


/*
_this.getCurrent = function() {
      return escape(_currentHash);
   };
*/	

	/* 
	Method: _createAnchor
		Various browsers may need an achor to be present in the dom for the hash to actually be set,
		so we add one every time a history entry is made.
	*/
	function _createAnchor($newHash) {
		if (!_checkAnchorExists($newHash)) {
			var $anchor = document.createElement("a");
			$anchor.setAttribute("name", $newHash);
			if (/MSIE/.test(navigator.userAgent) && !window.opera)
				$anchor = document.createElement('<a name="'+$newHash+'">'+$newHash+"</a>");
			with ($anchor.style) {
				position = "absolute";
				display = "block";
				top = getScrollY()+"px";
				left = getScrollX()+"px";
			}
			//$anchor.style.display = 'none';
			//$anchor.innerHTML = $newHash;
			document.body.insertBefore($anchor,document.body.firstChild);
			//document.body.appendChild($anchor);
		}
	}
	function _checkAnchorExists($name) {
		var $anchors = document.anchors;
		for (var i = 0; i < $anchors.length; i++)
			if ($anchors[i].name == $name)
				return true;
		return false;
	}
	// Keeps IE 5.0 from scrolling to the top every time a new history is entered.
	// Also retains the scroll position in the history (doesn't seem to work on IE 5.5+).
	if (typeof self.pageYOffset == "number") {
		function getScrollY() {
			return self.pageYOffset;
		}
	} else if (document.documentElement && document.documentElement.scrollTop) {
		function getScrollY() {
			return document.documentElement.scrollTop;
		}
	} else if (document.body) {
		function getScrollY() {
			return document.body.scrollTop;
		}
	}
	// clone getScrollY to getScrollX
	eval(String(getScrollY).toString().replace(/Top/g,"Left").replace(/Y/g,"X"));
	
	// stub methods, to prevent errors on unsupported browsers
	_this.addHistory = function(){};

	/**
	 * These are the platform specific interface methods. Since some platforms (most notably, IE 5.5+)
	 * require almost completely different techniques to create history entries, browser detection is
	 * used and the appropriate method is created. It would be nice to use object or feature detection
	 * here, but these workarounds deal mostly with very specific bugs and other oddities in the 
	 * various implementations. So browser sniffing it is.
	 */
	// Safari
	if (/Safari/.test(navigator.appVersion)) {
		_getHash = function() {
			var _location = location.toString();
			var i = _location.indexOf("#");
			if (i < 0) return "";
			return _location.substring(i+1);
		};
	}
	/*if (navigator.appVersion.indexOf("Safari") != -1) {
		
		var _windowHistoryLength = history.length,
			_historyArray = [],
			_recentlyAdded = false;
		// set initial history entry
		_historyArray[_windowHistoryLength] = _currentHash;
		
		_this.addHistory = function($newHash) { // adds history and bookmark hash
			if (_currentHash != $newHash) {
				_createAnchor($newHash);
				_currentHash = $newHash;
				_setHash($newHash); // :NOTE: this doesn't update history.length right away
				_windowHistoryLength = history.length+1;
				_historyArray[_windowHistoryLength] = $newHash;
				//_recentlyAdded = true;
				_this.notifyListeners("historyChange",$newHash);
			}
		};
		
		var _watchHistoryLength = function() {
				var historyText = "The history is " + history.length + " long";
			window.console.log(historyText);
			
			if (!_recentlyAdded) { // :NOTE: for some reason the first time this is called, it can't tell that anything has changed.
				var _historyLength = history.length;
				if (_historyLength != _windowHistoryLength) {// && _historyArray[_historyLength]
					_windowHistoryLength = _historyLength;
					
					var $newHash = _historyArray[_windowHistoryLength];
					if (_currentHash != $newHash) {
						_currentHash = $newHash;
						_this.notifyListeners("historyChange", $newHash);
					}
				}
			} else _recentlyAdded = false;
		};
		
		// since it doesn't work, might as well cancel the location.hash check (checking location.href might work)
		clearInterval(_intervalID);
		// watch the history.length prop for changes
		_intervalID = setInterval(_watchHistoryLength, _pollInterval);
		
	// IE 5.5+ Windows
	} else*/ if (typeof ActiveXObject != "undefined" && window.print && 
			   !window.opera && navigator.userAgent.match(/MSIE (\d\.\d)/)[1] >= 5.5) {
		/* iframe references */
		var _historyFrameObj, _historyFrameRef;
		
		/*
		method: _createHistoryFrame
			
			This is for IE only for now.
		*/
		function _createHistoryFrame() {
			var $historyFrameName = "unFocusHistoryFrame";
			_historyFrameObj = document.createElement("iframe");
			_historyFrameObj.setAttribute("name", $historyFrameName);
			_historyFrameObj.setAttribute("id", $historyFrameName);
			// :NOTE: _Very_ experimental
			_historyFrameObj.setAttribute("src", 'javascript:;');
			_historyFrameObj.style.position = "absolute";
			_historyFrameObj.style.top = "-900px";
			document.body.insertBefore(_historyFrameObj,document.body.firstChild);
			// get reference to the frame from frames array (needed for document.open)
			// :NOTE: there might be an issue with this according to quirksmode.org
			// http://www.quirksmode.org/js/iframe.html
			_historyFrameRef = frames[$historyFrameName];
			
			// add base history entry
			_createHistoryHTML(_currentHash, true);
		}
		
		/*
		method: _createHistoryHTML
			This is an alternative to <_setHistoryHTML> that is used by IE (and others if I can get it to work).
			This method will create the history page completely in memory, with no need to download a new file
			from the server.
		*/
		function _createHistoryHTML($newHash) {
			with (_historyFrameRef.document) {
				open("text/html");
				write("<html><head></head><body onl",
					'oad="parent.unFocus.History._updateFromHistory(\''+$newHash+'\');">',
					$newHash+"</body></html>");
				close();
			}
		}
		
		/*
		method: _updateFromHistory
			A private method that is meant to be called only from HistoryFrame.html.
			It is not meant to be used by an end user even though it is accessable as public.
		*/
		_this._updateFromHistory = function() {
			// hides the first call to the method, and sets up the real method for the rest of the calls
			_this._updateFromHistory = function($hash) {
				_currentHash = $hash;
				_this.notifyListeners("historyChange", $hash);
			};
		};
		//if (navigator.userAgent.match(/MSIE (\d\.\d)/)[1] < 5.5) {
			_this.addHistory = function($newHash) {
				// do initialization stuff on first call
				_createHistoryFrame();
				
				// replace this function with a slimmer one on first call
				_this.addHistory = function($newHash) { // adds history and bookmark hash
					if (_currentHash != $newHash) {
						// IE will create an entry if there is an achor on the page, but it
						// does not allow you to detect the state change, so we skip inserting an Anchor
						_currentHash = $newHash;
						// sets hash and notifies listeners
						_createHistoryHTML($newHash);
					}
				};
				// call the first call
				_this.addHistory($newHash);
			};
			// anonymouse method - subscribe to self to update the hash when the history is updated
			_this.addEventListener("historyChange", function($hash) { _setHash($hash) });
		//} else { /* IE 5.0 */ }
		
	} else /*if (!/Safari/.test(navigator.userAgent))*/ {
		_this.addHistory = function($newHash) { // adds history and bookmark hash
			// on first call, make an anchor for the root history entry
			_createAnchor(_currentHash);
			// replace with slimmer versions...
			_this.addHistory = function($newHash) {
				if (_currentHash != $newHash) {
					_createAnchor($newHash);
					_currentHash = $newHash;
					_setHash($newHash);
					_this.notifyListeners("historyChange",$newHash);
				}
			};
			// ...do first call
			_this.addHistory($newHash);
		};
	}
}
Keeper.prototype = new unFocus.Utilities.EventManager("historyChange");

return new Keeper();

})();
