if (!window.unFocus) var unFocus = {};

unFocus.EventManager = function() {
        this._listeners = {};
        for (var i = 0; i < arguments.length; i++) {
                this._listeners[arguments[i]] = [];
        }
};

unFocus.EventManager.prototype = {
        /*
        Method: addEventListener
                Adds an event listener to the specified type.

        Parameters:
                $name           - The event name.
                $listener       - The function to be called when the event fires.
        */
        addEventListener: function($name, $listener) {
                // check that listener is not in list
                for (var i = 0; i < this._listeners[$name].length; i++)
                        if (this._listeners[$name][i] == $listener) return;
                // add listener to appropriate list
                this._listeners[$name].push($listener);
        },
        /*
        Method: removeEventListener
                Removes an event listener.
        
        Parameters:
                $name           - The event name.
                $listener       - The function to be removed.
        */
        removeEventListener: function($name, $listener) {
                // search for the listener method
                for (var i = 0; i < this._listeners[$name].length; i++) {
                        if (this._listeners[$name][i] == $listener) {
                                this._listeners.splice(i,1);
                                return;
                        }
                }
        },
        /* Method: notifyListeners
                Notifies the listeners of an event.
        
        Parameters:
                $name   - The name of event to fire.
                $data   - The object to pass to the subscribed method (the Event Object).
        */
        notifyListeners: function($name, $data) {
                for (var i = 0; i < this._listeners[$name].length; i++)
                        this._listeners[$name][i]($data);
        }
};

unFocus.FlashPlayerInfo = (function() {
        // private vars
        var _installed = false,
                _beta = false,
                _version = 0,
                _majorRevision = 0,
                _minorRevision = 0,
                _betaVersion = 0,
                _versionRaw = "",
                _playerType = "",
                _releaseCode = "";
        
        // detection work
        if (navigator.plugins && navigator.plugins.length > 0) {
                _versionRaw = navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"];
                if (_versionRaw) {
                        _versionRaw = _versionRaw.description;
                        _installed = true;
                        _playerType = "PlugIn";
                        if (/Shockwave Flash/.test(_versionRaw)) {
                                _version = _versionRaw.match(/Shockwave Flash (\d+)\.(\d+)/);
                                _majorRevision = _version[2];
                                if (/r\d+/.test(_versionRaw)) {
                                        _releaseCode = "r";
                                        _minorRevision = _versionRaw.match(/r(\d+)/)[1];
                                }
                                _version = _version[1];
                                if (/[abd]\d+/.test(_versionRaw)) { // I'm not sure what other letters would be here, but I've encountered b, d and a (alpha) so far
                                        _betaVersion = _versionRaw.match(/([abd])(\d+)/);
                                        _releaseCode = _betaVersion[1];
                                        _beta = true;
                                        _betaVersion = _betaVersion[2];
                                }
                        } else _version = 1;
                }
        } else if (window.ActiveXObject) {
                // src: Player.vbs 
                var _ax;
                function _getActiveXObject($objectString) {
                        try {
                                _ax = new ActiveXObject($objectString);
                                return true;
                        } catch (e){
                                return false;
                        }
                }
                var _versionTemp;
                
                function _parseVersion() {
                        var _versionTemp = _ax.GetVariable("$version");
                        _versionRaw = _versionTemp;
                        _versionTemp = _versionTemp.split(",");
                        _version = _versionTemp[0].match(/\d+/);
                        _majorRevision = _versionTemp[1];
                        _minorRevision = _versionTemp[2];
                        _betaVersion = _versionTemp[3];
                        if (_versionTemp[3]>0) _beta = true;
                        // if the last number is 0, assume this is a release version
                        else _releaseCode = "r";
                }
                
                // we have to detect around Flash 6, since it can crash some versions of IE
                if (_getActiveXObject("ShockwaveFlash.ShockwaveFlash.7"))
                        _parseVersion();
                else if (_getActiveXObject("ShockwaveFlash.ShockwaveFlash.6")) {
                        // tread lightly
                        try {
                                // will throw error if < 6.0.47 (info glombed from Adobe's Detection Kit - thanks!)
                                _ax.AllowScriptAccess = "always";
                                // now safe to call GetVariable
                                _parseVersion();
                        } catch (e) {
                                _version = 6;
                                _minorRevision = 0; // cannot be safely detected
                        }
                } else if (_getActiveXObject("ShockwaveFlash.ShockwaveFlash.5"))
                        _parseVersion();
                else if (_getActiveXObject("ShockwaveFlash.ShockwaveFlash.4"))
                        _version = 4;
                else if (_getActiveXObject("ShockwaveFlash.ShockwaveFlash.3"))
                        _version = 3;
                else if (_getActiveXObject("ShockwaveFlash.ShockwaveFlash"))
                        // Tested on Windows 95 with flash player 2 - using the ".2" at the end doesn't work, but this does ;-)
                        // :BUG: This doesn't seem to work when FP2 is installed in WinXP
                        _version = 2;
                if (_version) {
                        _installed = true;
                        _playerType = "ActiveX";
                }
        } else if (/WebTV/.test(navigator.userAgent)) { // WebTV
                _playerType = "WebTV";
                _versionRaw = navigator.userAgent.match(/WebTV\/(\d\.\d)/)[1];
                if (_versionRaw > 2.5) _version = 4;
                else if (_versionRaw == 2.5) _version = 3;
                else _version = 2;
        }
        
        // public/priveleged Getters
        var FlashPlayerInfo = {
                isInstalled: function() {
                        return _installed;
                },
                isBeta: function() {
                        return _beta;
                },
                getVersion: function() {
                        return _version;
                },
                getMajorRevision: function() {
                        return _majorRevision;
                },
                getMinorRevision: function() {
                        return _minorRevision;
                },
                getBetaVersion: function() {
                        return _betaVersion;
                },
                getVersionRaw: function() {
                        return _versionRaw;
                },
                // for backward compat - deprecated, will be removed in next version
                getPluginType: function() {
                        return this.getPlayerType();
                },
                getPlayerType: function() {
                        return _playerType;
                },
                getReleaseCode: function() {
                        return _releaseCode;
                }
        };
        return FlashPlayerInfo;
})();

unFocus.SwfCommunicator = function(_movieId, _communicatorSWF) {
        var getSwfReference = unFocus.SwfUtilities.getSwfReference;
        /* 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;
        
        /* 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 */
        // :TODO: spin this out as a Utility class (Queue Class)
        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.SwfTools.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;
        
        // store ref to this object in a static array, for use by LongCallFSCommand
        unFocus.SwfCommunicator._instances.push(_this);
        
        _this._getMovieObj = function() {
                if (!_movieObj)
                        _movieObj = getSwfReference(_movieId);
                        alert(_movieObj.src);
                return _movieObj; // will be different depending on if it's an object or an embed, or a SATAY
        };
};

unFocus.SwfCommunicator.prototype = new unFocus.EventManager("FSCommand");

/* static */
unFocus.SwfCommunicator._instances = [];
/*unFocus.SwfCommunicator.retrieveSwfId = function($swfName, $connId) {
        
};*/
unFocus.SwfCommunicator.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,
                instances = unFocus.SwfCommunicator._instances;
                
        for (var i =0; i < instances.length; i++)
        {
                _curMovieObj = 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(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.SwfHTML = function() {
        this._properties = {};
        this._params = {};
        this._flashvars = "";
        this._flashvarPairs = {};
        this._version = 0;
        this._majorRevision = 0;
        this._minorRevision = 0;
        this._betaVersion = 0;
        this._src = "";
};
// setters check against valid values http://www.adobe.com/go/tn_12701
unFocus.SwfHTML.prototype = {
        // Method: setSrc
        //      Sets the Src of the swf file.
        setSrc: function($src) {
                this._src = $src;
        },
        // Method: setWidth
        //      Sets the Width of the swf file.
        setWidth: function($width) {
                this._properties.width = $width;
        },
        setHeight: function($height) {
                this._properties.height = $height;
        },
        setId: function($id) {
                this._properties.id = $id;
        },
        getId: function() {
                return this._properties.id;
        },
        setName: function($name) {
                this._properties.name = $name;
        },
        
        setMovie: function($src) {
                this.setSrc($src);
        },
        setSwliveconnect: function($swliveconnect) {
                if (typeof $swliveconnect == "boolean")
                        this._params.swliveconnect = $swliveconnect;
                else
                        throw new Error("InvalidValue", "Valid Values for swliveconnect: true, false");
        },
        setPlay: function($play) {
                if (typeof $play == "boolean")
                        this._params.play= $play;
                else
                        throw new Error("InvalidValue", "Valid Values for play: true, false");
        },
        setLoop: function($loop) {
                if (typeof $loop == "boolean")
                        this._params.loop= $loop;
                else
                        throw new Error("InvalidValue", "Valid Values for loop: true, false");
        },
        setMenu: function($menu) {
                if (typeof $menu == "boolean")
                        this._params.menu= $menu;
                else
                        throw new Error("InvalidValue", "Valid Values for menu: true, false");
        },
        setQuality: function($quality) {
                switch ($quality) {
                        case "low":
                        case "medium":
                        case "high":
                        case "autolow":
                        case "autohigh":
                        case "best":
                                this._params.quality = $quality; /* low, medium, high, autolow, autohigh, best */
                                break;
                        default:
                                throw new Error("InvalidValue", "Valid Values for quality: low, medium, high, autolow, autohigh, best");
                }
        },
        setScale: function($scale) {
                switch ($scale) {
                        case "showall":
                        case "noborder":
                        case "exactfit":
                        case "noscale":
                                this._params.scale = $scale; /* showall, noborder, exactfit, noscale (missing from the documentation) */
                                break;
                        default:
                                throw new Error("InvalidValue", "Valid Values for scale: showall, noborder, exactfit, noscale");
                }
        },
        setAlign: function($align) { // :NOTE: There is a conflict here - you can set align on the html element, as well as for the movie. Hhow does this sort out for embed?
                switch ($align) {
                        case "l":
                        case "t":
                        case "r":
                        case "b":
                                this._params.align = $align; /* l, t, r, b (defaults to center, which isn't in the list) */
                                break;
                        default:
                                throw new Error("InvalidValue", "Valid Values for align: l, t, r, b");
                }
        },
        setSAlign: function($salign) {
                switch ($salign) {
                        case "l":
                        case "t":
                        case "r":
                        case "b":
                        case "tl":
                        case "tr":
                        case "bl":
                        case "br":
                                this._params.salign = $salign;
                                break;
                        default:
                                throw new Error("InvalidValue", "Valid Values for salign: l, t, r, b, tl, tr, bl, br");
                }
        },
        setWMode: function($wmode) {
                switch($wmode) {
                        case "window":
                        case "opaque":
                        case "transparent":
                                this._params.wmode = $wmode;
                                break;
                        default:
                                throw new Error("InvalidValue", "Valid Values for wmode: window, opaque, transparent");
                }
        },
        setBGColor: function($bgcolor) {
                // /^#[\dA-Fa-f]{6}$/
                if (/^#[\dA-F]{6}$/.test($bgcolor))
                        this._params.bgcolor = $bgcolor; /* #RRGGBB, hexadecimal RGB value */
                else
                        throw new Error("InvalidValue", "Valid Values for bgcolor: a valid html color hex value (#0099FF)");
        },
        setBase: function($base) {
                this._params.base = $base;
        },
        setFlashvars: function($flashvars) {
                this._flashvars = $flashvars;
        },
        addFlashvar: function($name, $value) {
                if ($value)
                        this.setFlashvars($name + "=" + escape($value) + "&" + this._flashvars);
        },
        
        // http://www.macromedia.com/cfusion/knowledgebase/index.cfm?id=tn_16494
        setAllowscriptaccess: function($allowscriptaccess) {
                switch ($allowscriptaccess) {
                        case "never":
                        case "always":
                                this._params.allowscriptaccess = $allowscriptaccess;
                                break;
                        default:
                                throw new Error("InvalidValue", "Valid Values for allowscriptaccess: never, always");
                }
        },
        
        // for version stuffs
        setVersion: function($version) {
                this._version = $version;
        },
        setMajorRevision: function($majorRevision) {
                this._majorRevision = $majorRevision;
        },
        setMinorRevision: function($minorRevision) {
                this._mainorRevision = $minorRevision;
        },
        setBetaVersion: function($betaVersion) {
                this._betaVersion = $betaVersion;
        },
        
        // misc
        setAdditionalParam: function($name, $value) {
                this._params[$name] = $value;
        },
        setAdditionalProperty: function($name, $value) {
                this._properties[$name] = $value;
        },
        
        // new allowFullScreen prop
        setAllowFullScreen: function($allowFullScreen) {
                if (typeof $allowFullScreen == "boolean")
                        this._params.allowFullScreen= $allowFullScreen;
                else
                        throw new Error("InvalidValue", "Valid Values for allowFullScreen: true, false");
        },
        
        /**
         * Generates the platform specific HTML for the flash movie
         *
         * @return String containing the platform specific HTML for the flash movie
         */
        getHTML: function() {
                // initialize local variables
                var $key, $html, $ActiveX = window.ActiveXObject && window.print && !window.opera;
                
                if (this._src) {
                        if ($ActiveX)
                                this._params.movie = this._src;
                        else
                                this._properties.src = this._src;
                }
                
                if ($ActiveX) {
                        $html = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="';
                        // solve https popup issue http://www.adobe.com/go/tn_16588
                        $html += /^https/.test(window.location)?"https":"http";
                        $html += '://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version='+this._version+","+this.majorRevision+","+this._minorRevision+","+this._betaVersion+'"';
                } else
                        $html = "<embed";
                
                // output  properties
                for ($key in this._properties)
                        $html += ' ' + $key + '="' + this._properties[$key]+'"';
        
                if ($ActiveX) {
                        $html += ">";
                        for ($key in this._params)
                                $html += '<param name="' + $key + '" value="' + this._params[$key] + '">';
                        if (this._flashvars)
                                $html += '<param name="flashvars" value="' + this._flashvars + '">';
                } else {
                        for ($key in this._params)
                                $html += " " + $key + '="' + this._params[$key]+'"';
                        if (this._flashvars)
                                $html += ' flashvars="' + this._flashvars + '"';
                }
                
                // outputs the rest of the platform specific stuff
                if ($ActiveX)
                        $html += '"></object>';
                else
                        $html += '" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>';
                return $html;
        }
};

if (!unFocus.SwfUtilities) unFocus.SwfUtilities = {
        getSwfReference: function(swfId) {
                var $movieObj;
                if (document.embeds && document.embeds[swfId])
                        $movieObj = document.embeds[swfId];
                 else if (document[swfId])
                        $movieObj = document[swfId];
                 /*else if (window[$swfId])
                        $movieObj = $window[$swfId];
                else if (document.getElementById)
                        $movieObj = document.getElementById($swfId);*/
                return $movieObj;
        }
};

unFocus.SwfHistory = function($communicator) {
        // add the initial state to the movie :deprecated: this will now happen automatically when
        // the communication framework tests for fscommand
        //$communicator.getHTML().addFlashvar("unFocusHistoryUpdate",History.getCurrent());
        
        // subscribe to the Communicator
        $communicator.addEventListener("FSCommand", function($data) {
                switch($data[0]) {
                        case "History.setTitle":
                                document.title = $data[1];
                        break;
                        case "History.addHistory":
                                unFocus.History.addHistory($data[1]);
                }
        });
        
        // subscribe to the History Keeper
        unFocus.History.addEventListener("historyChange", function($hash) {
                $communicator.setVariable("unFocusHistoryUpdate",$hash);
        });

};
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);
       };
       // get initial hash
       _currentHash = _getHash();
       
       /*
       method: _setHash
               A private method that sets the Hash on the location string (the current url).
       */
       var _setHash = function($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: _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. This has a side effect in many browsers,
               where if the scroll position of the page is changed, in between history states, this causes
               most browsers to remember the position! It's a bonus.
       */
       function _createAnchor($newHash) {
               if (!_checkAnchorExists($newHash)) {
                       var $anchor;
                       if (/MSIE/.test(navigator.userAgent) && !window.opera)
                               $anchor = document.createElement('<a name="'+$newHash+'">'+$newHash+"</a>");
                       else
                               $anchor = document.createElement("a");
                       $anchor.setAttribute("name", $newHash);
                       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);
               }
       }
       // simplified function contributed by Micah Goulart
       function _checkAnchorExists($name) {
               if (document.getElementsByName($name).length > 0)
                       return true;
       }
       // 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"));
       
       /*
       method: getCurrentBookmark
               A public method to retrieve the current history string
       
       returns:
               The current History Hash
       */
       _this.getCurrent = function() {
               return _currentHash;
       };
       
       /*
       method: addHistory
               A public method to add a new history, and set the deep link. This method should be given a string.
               It does no serialization.
       
       returns:
               Boolean - true if supported and set, false if not
       */
       function addHistory($newHash) {
               if (_currentHash != $newHash) {
                       _createAnchor($newHash);
                       _currentHash = $newHash;
                       _setHash($newHash);
                       _this.notifyListeners("historyChange",$newHash);
               }
               return true;
       }
       _this.addHistory = function($newHash) { // adds history and bookmark hash
               _createAnchor(_currentHash);
               // replace with slimmer versions...
               _this.addHistory = addHistory;
               // ...do first call
               return _this.addHistory($newHash);
       };

       /**
        * These are the platform specific override methods. Since some platforms (IE 5.5+, Safari)
        * require almost completely different techniques to create history entries, browser detection is
        * used and the appropriate method is created. The bugs these fixes address are very tied to the
        * specific implementations of these browsers, and not necessarily the underlying html engines.
        * Sometimes, bugs related to history management can be tied even to a specific skin in browsers
        * like Opera.
        */
       // Safari 2.04 and less (and WebKit less than 420 - these hacks are not needed by the most recent nightlies)
       // :TODO: consider whether this aught to check for Safari or WebKit - is this a safar problem, or a does it
       // happen in other WebKit based software? OmniWeb (WebKit 420+) seems to work, though there's a sync issue.
       if (/WebKit\/\d+/.test(navigator.appVersion) && navigator.appVersion.match(/WebKit\/(\d+)/)[1] < 420) {
               // this will hold the old history states, since they can't be reliably taken from the location object
               var _unFocusHistoryLength = history.length,
                       _historyStates = {}, _form,
                       _recentlyAdded = false;
               
               // Setting the hash directly in Safari seems to cause odd content refresh behavior.
               // We'll use a form to submit to a #hash location instead. I'm assuming this works,
               // since I saw it done this way in SwfAddress (gotta give credit where credit it due ;-) ).
               function _createSafariSetHashForm() {
                       _form = document.createElement("form");
                       _form.id = "unFocusHistoryForm";
                       _form.method = "get";
                       document.body.insertBefore(_form,document.body.firstChild);
               }
               
               // override the old _setHash method to use the new form
               _setHash = function($newHash) {
                       _historyStates[_unFocusHistoryLength] = $newHash;
                       _form.action = "#" + _getHash();
                       _form.submit();
               };
               
               // override the old _getHash method, since Safari doesn't update location.hash (fixed in nightlies)
               _getHash = function() {
                       return _historyStates[_unFocusHistoryLength];
               };
               
               // set initial history entry
               _historyStates[_unFocusHistoryLength] = _currentHash;
               
               function addHistorySafari($newHash) {
                       if (_currentHash != $newHash) {
                               _createAnchor($newHash);
                               _currentHash = $newHash;
                               _unFocusHistoryLength = history.length+1;
                               _recentlyAdded = true;
                               _setHash($newHash);
                               _this.notifyListeners("historyChange",$newHash);
                               _recentlyAdded = false;
                       }
                       return true;
               }
               
               // provide alternative addHistory
               _this.addHistory = function($newHash) { // adds history and bookmark hash
                       // on first call, make an anchor for the root history entry
                       _createAnchor(_currentHash);
                       // setup the form fix
                       _createSafariSetHashForm();
                       
                       // replace with slimmer version...
                       // :TODO: rethink this - it's adding an extra scope to the chain, which might
                       // actually cost more at runtime than a simple if statement. Can this be done
                       // without adding to the scope chain? The replaced scope holds no values. Does
                       // it keep it's place in the scope chain?
                       _this.addHistory = addHistorySafari;
                       
                       // ...do first call
                       return _this.addHistory($newHash);
               };
               function _watchHistoryLength() {
                       if (!_recentlyAdded) {
                               var _historyLength = history.length;
                               if (_historyLength != _unFocusHistoryLength) {
                                       _unFocusHistoryLength = _historyLength;
                                       
                                       var $newHash = _getHash();
                                       if (_currentHash != $newHash) {
                                               _currentHash = $newHash;
                                               _this.notifyListeners("historyChange", $newHash);
                                       }
                               }
                       }
               };
               
               // since it doesn't work, might as well cancel the location.hash check
               clearInterval(_intervalID);
               // watch the history.length prop for changes instead
               _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.
               */
                       // hides the first call to the method, and sets up the real method for the rest of the calls
               function updateFromHistory($hash) {
                       _currentHash = $hash;
                       _this.notifyListeners("historyChange", $hash);
               }
               _this._updateFromHistory = function() {
                       _this._updateFromHistory = updateFromHistory;
               };
               //if (navigator.userAgent.match(/MSIE (\d\.\d)/)[1] < 5.5) {
                       function addHistoryIE($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);
                               }
                               return true;
                       };
                       _this.addHistory = function($newHash) {
                               // do initialization stuff on first call
                               _createHistoryFrame();
                               
                               // replace this function with a slimmer one on first call
                               _this.addHistory = addHistoryIE;
                               // call the first call
                               return _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 */ }
       
       }
}
Keeper.prototype = new unFocus.EventManager("historyChange");


return new Keeper();

})();


unFocus.SwfHTML.prototype.writeToDocument=function(a){a.write(this.getHTML())};unFocus.SwfHTML.prototype.outputToInnerHTML=function(a){a.innerHTML=this.getHTML()};unFocus.SwfHTML.prototype.outputToOuterHTML=function(a){a.outerHTML=this.getHTML()};