/**
page name jquery.history.js

 * jQuery History Plugin (balupton edition) - Easy and simple history handler for AJAX (bookmarks, forward back buttons)
 * Copyright (C) 2008-2009 Benjamin Arthur Lupton
 * http://plugins.jquery.com/project/jquery_history
 *
 * This file is part of jQuery History Plugin (balupton edition).
 * 
 * jQuery History Plugin (balupton edition) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * jQuery History Plugin (balupton edition) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with jQuery History Plugin (balupton edition).  If not, see <http://www.gnu.org/licenses/>.
 *
 * @name jqsmarty: jquery.history.js
 * @package jQuery History Plugin (balupton edition)
 * @version 1.0.0-final
 * @date June 19, 2009
 * @category jquery plugin
 * @author Benjamin "balupton" Lupton {@link http://www.balupton.com}
 * @copyright (c) 2008-2009 Benjamin Arthur Lupton {@link http://www.balupton.com}
 * @license GNU Affero General Public License - {@link http://www.gnu.org/licenses/agpl.html}
 * @example Visit {@link http://jquery.com/plugins/project/jquery_history_bal} for more information.
 * 
 * 
 * I would like to take this space to thank the following projects, blogs, articles and people:
 * - jQuery {@link http://jquery.com/}
 * - jQuery UI History - Klaus Hartl {@link http://www.stilbuero.de/jquery/ui_history/}
 * - Really Simple History - Brian Dillard and Brad Neuberg {@link http://code.google.com/p/reallysimplehistory/}
 * - jQuery History Plugin - Taku Sano (Mikage Sawatari) {@link http://www.mikage.to/jquery/jquery_history.html}
 * - jQuery History Remote Plugin - Klaus Hartl {@link http://stilbuero.de/jquery/history/}
 * - Content With Style: Fixing the back button and enabling bookmarking for ajax apps - Mike Stenhouse {@link http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps}
 * - Bookmarks and Back Buttons {@link http://ajax.howtosetup.info/options-and-efficiencies/bookmarks-and-back-buttons/}
 * - Ajax: How to handle bookmarks and back buttons - Brad Neuberg {@link http://dev.aol.com/ajax-handling-bookmarks-and-back-button}
 *
 **
 ***
 * CHANGELOG
 **
 * v1.0.0-final, June 19, 2009
 * - Been stable for over a year now, pushing live.
 * 
 * v0.1.0-dev, July 24, 2008
 * - Initial Release
 * 
 */
 
// Start of our jQuery Plugin
(function($)
{  // Create our Plugin function, with $ as the argument (we pass the jQuery object over later)
  // More info: http://docs.jquery.com/Plugins/Authoring#Custom_Alias
  
  // Debug
  if (typeof console === 'undefined') {
    console = typeof window.console !== 'undefined' ? window.console : {};
  }
  console.log      = console.log       || function(){};
  console.debug    = console.debug     || console.log;
  console.warn    = console.warn      || console.log;
  console.error    = console.error      || function(){var args = [];for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); } alert(args.join("\n")); };
  console.trace    = console.trace      || console.log;
  console.group    = console.group      || console.log;
  console.groupEnd  = console.groupEnd    || console.log;
  console.profile    = console.profile    || console.log;
  console.profileEnd  = console.profileEnd  || console.log;
  
  // Declare our class
  $.HistoryClass = function ( )
  {  // This is the handler for our constructor
    this.construct();
  };
  
  // Define our class
  $.extend($.HistoryClass.prototype,
  {  // Our Plugin definition
    
    // -----------------
    // Options
    
    // -----------------
    // Variables
    
    hash:    '',
    $window:  null,
    $iframe:  null,
    handlers:  {
      generic:  [],
      specific:  {}
    },
    
    // --------------------------------------------------
    // Functions
    
    format: function ( hash ) {
      return hash.replace(/^.+?#/g,'').replace(/^#?\/?|\/?$/g, '');
    },
    
        getState: function ( )
    {  // Get the current state
      return $.History.format($.History.hash);
        },
    setState: function ( hash ) {
      hash = hash || $.History.getHash();
      return ($.History.hash = $.History.format(hash));
    },
    
    getHash: function ( ) {
      var hash = window.location.hash || location.hash;
      return $.History.format(hash);
    },
    setHash: function ( hash ) {
      hash = $.History.format(hash);
      //hash = hash.replace(/^\/?|\/?(\?)|\/?$/g,'/$1');
      if ( typeof window.location.hash !== 'undefined' ) {
        window.location.hash = hash;
      } else {
        location.hash = hash;
      }
    },
    
    go: function(state)
    {  // Go to a specific state
      // Update IE<8 History
      if ( $.browser.msie && parseInt($.browser.version, 10) < 8 )
      {  // We are IE<8
        $.History.$iframe.contentWindow.document.open();
        $.History.$iframe.contentWindow.document.close();
        $.History.$iframe.contentWindow.document.location.hash = state;
      }
      
      // Update the hash
      state = state || '#';
      var hash = $.History.getHash();
      if ( hash !== state )
      {  // Update
        $.History.setHash(state);
      }
      
      // Trigger the change
      $.History.$window.trigger('hashchange');
    },
    
    hashchange: function ( e )
    {  // Triggered when the hash changes, either automaticly (event) or manually (trigger)
      
      // Get Hash
      var hash = $.History.getHash();
      var state = $.History.getState();
      
      // Prevent IE 8 from fireing this twice
      if ( (!$.History.$iframe && state === hash) || ($.History.$iframe && $.History.hash === $.History.$iframe.contentWindow.document.location.hash) )
      {  // For some reason this works
        return false;
      }
      
      // Check
      if ( state === hash )
      {  // Nothing to do
        return false;
      }
      
      // Update the hash
      $.History.setState(hash);
      
      // Fire the handler
      $.History.trigger();
      
      // All done
      return true;
    },
    
    bind: function ( state, handler )
    {  // Add a handler to listen on the history
      if ( handler )
      {  // We have a state specific handler
        // Prepare
        if ( typeof $.History.handlers.specific[state] === 'undefined' )
        {  // Make it an array
          $.History.handlers.specific[state] = [];
        }
        // Push new handler
        $.History.handlers.specific[state].push(handler);
      }
      else
      {  // We have a generic handler
        handler = state;
        $.History.handlers.generic.push(handler);
      }
      // Done
      return true;
    },
    
    trigger: function ( state )
    {  // Fire the state handler
      // Prepare
      if ( typeof state === 'undefined' )
      {  // Use current
        state = $.History.getState();
      }
      var i, n, handler, list;
      // Fire specific
      if ( typeof $.History.handlers.specific[state] !== 'undefined' )
      {  // We have specific handlers
        list = $.History.handlers.specific[state];
        for ( i = 0, n = list.length; i < n; ++i )
        {  // Fire the specific handler
          handler = list[i];
          handler(state);
        }
      }
      // Fire generics
      list = $.History.handlers.generic;
      for ( i = 0, n = list.length; i < n; ++i )
      {  // Fire the specific handler
        handler = list[i];
        handler(state);
      }
      // Done
      return true;
    },
    
    // --------------------------------------------------
    // Constructors
    
    construct: function ( options )
    {  // Construct our Plugin
      
      // Modify the document
      $(document).ready(function()
      {  // On document ready
      
        // Prepare the document
        $.History.domReady();
      });
      
      // All done
      return true;
    },
    
    domReadied: false,
    domReady: function ( )
    {  // We are good
      
      // Runonce
      if ( $.History.domRedied ) return;
      $.History.domRedied = true;
      
      // Define window
      $.History.$window = $(window);
      
      // Apply the hashchange function
      $.History.$window.bind('hashchange', this.hashchange);
      
      // Pump it somwhere else
      setTimeout($.History.hashchangeLoader, 200);
      
      // All done
      return true;
    },
    
    hashchangeLoader: function () {
      
      // More is needed for non IE8 browsers
      if ( !($.browser.msie && parseInt($.browser.version) >= 8) )
      {  // We are not IE8
      
        // State our checker function, it is used to constantly check the location to detect a change
        var checker;
        
        // Handle depending on the browser
        if ( $.browser.msie )
        {  // We are still IE
        
          // Append and $iframe to the document, as $iframes are required for back and forward
          // Create a hidden $iframe for hash change tracking
          $.History.$iframe = $('<iframe id="jquery-history-iframe" style="display: none;"></$iframe>').prependTo(document.body)[0];
          
          // Create initial history entry
          $.History.$iframe.contentWindow.document.open();
          $.History.$iframe.contentWindow.document.close();
          
          // Check for initial state
          var hash = $.History.getHash();
          if ( hash )
          {  // Apply it to the iframe
            $.History.$iframe.contentWindow.document.location.hash = hash;
          }
          
          // Define the checker function (for bookmarks)
          checker = function ( ) {
            var iframeHash = $.History.format($.History.$iframe.contentWindow.document.location.hash);
            if ( $.History.getState() !== iframeHash )
            {  // Back Button Change
              $.History.setHash($.History.$iframe.contentWindow.document.location.hash);
            }
            var hash = $.History.getHash();
            if ( $.History.getState() !== hash )
            {  // The has has changed
              $.History.go(hash);
            }
          };
        }
        else
        {  // We are not IE
        
          // Define the checker function (for bookmarks, back, forward)
          checker = function ( ) {
            var hash = $.History.getHash();
            if ( $.History.getState() !== hash )
            {  // The has has changed
              $.History.go(hash);
            }
          };
        }
        
        // Apply the checker function
        setInterval(checker, 200);
      }
      else {
        // We are IE8
        var hash = $.History.getHash();
        if (hash) {
          $.History.$window.trigger('hashchange');
        }
      }
      
      // Done
      return true;
    }
  
  }); // We have finished extending/defining our Plugin
 
  // --------------------------------------------------
  // Finish up
  
  // Instantiate
  if ( typeof $.History === 'undefined' )
  {  // 
    $.History = new $.HistoryClass();
  }
 
// Finished definition
 
})(jQuery); // We are done with our plugin, so lets call it with jQuery as the argument
