/**
 * Reusable JavaScript toggles
 *    @param    wrapperId       string    Id of HTML tag holding toggles and links
 *    @param    exceptions      array     Ids of toggle boxes that should remain visible
 *    @param    onBeforeToggle  function  Executed before anything is toggled
 *    @param    onAfterToggle   function  Executed after this is toggled
 *    @return   void
 */
function ToggleGroup(wrapperId, exception, onBeforeToggle, onAfterToggle) {
  if (!this.isSupported) {return;}
  
  this.wrapperId      = wrapperId;
  this.exceptions     = typeof(exception) == "string" ? exception : "";
  this.links          = [];
  this.toggleIds      = [];
  this.onBeforeToggle = (typeof(onBeforeToggle) == "function") ? onBeforeToggle : function() {return true;};
  this.onAfterToggle  = (typeof(onAfterToggle) == "function") ? onAfterToggle : function() {};
  this.currentIndex   = 0;
  
  var allLinks = this.GetNode(this.wrapperId).getElementsByTagName("a");
  var i = 0;
  var end = allLinks.length;
  var toggleLast = 0;
  var linkLast = 0;
  var anchor = this.GetIdFromHref(new String(window.location));
  var anchorIndex = -1;
  
  while (i < end) {
    if (allLinks[i].className == this.linkClassNameOff || allLinks[i].className == this.linkClassNameOn) {
      toggleLast = this.toggleIds.push( this.GetIdFromHref(allLinks[i].href) ) - 1;
      linkLast = this.links.push( allLinks[i] ) - 1;
      allLinks[i].onclick = this.GetOnclickFn(this);
      if (this.toggleIds[toggleLast] == this.exception) {
        this.currentIndex = toggleLast;
      }
      // Toggle all boxes off
      this.Toggle(toggleLast, false);
    }
    i++;
  }
  
  anchorIndex = this.GetIndex( this.GetIdFromHref(new String(window.location)), this.toggleIds );
  
  if (!this.foundInURL && anchorIndex > -1) {
    // URL references a box in this group
    this.currentIndex = anchorIndex;
    this.foundInURL = true;
  }
  
  // Toggle current box on
  this.Toggle(this.currentIndex, true);
  
}
ToggleGroup.prototype = {
  
  foundInURL          : false,
  isSupported         : document.getElementById && (Array.push || Array.prototype.push) ? true : false,
  linkClassNameOff    : "toggleLinkOff",
  linkClassNameOn     : "toggleLinkOn",
  toggleClassNameOff  : "toggleOff",
  toggleClassNameOn   : "toggleOn",
  
  /**
   * Called in window.onunload to remove node references that could cause
   * memory leaks in some browsers.
   *    @param    void
   *    @return   void
   */
  Destroy : function() {
    if (!this.isSupported) {return;}
    
    var i = 0;
    var end = this.links[i].length;
    
    while(i < end) {
      this.links[i].onclick = null;
      this.links[i] = null;
      i++;
    }
  },
  
  /**
   * Called when a toggle link is clicked
   *    @param    href      string  The href attribute of the clicked link.
   *    @return   boolean   False to cancel the link click.
   */
  DoToggle : function(href) {
    this.ToggleAll( [this.GetIdFromHref(href)] );
    return false;
  },
  
  /**
   * Returns the Id referenced in the URL's anchor
   *    @param    url      string  URL to be parsed
   *    @return   string   Id referenced in the URL anchor
   */
  GetIdFromHref : function(url) {
    var anchorLoc = url.indexOf('#');
    return (anchorLoc > -1) ? url.substring(anchorLoc + 1, url.length) : "";
  },
  
  /**
   * Returns the index at which a value is found in an array.
   *    @param    needle    variable  Value to search for
   *    @param    haystack  array     Array to search
   *    @return   integer   Array index if found, -1 if not found
   */
  GetIndex : function(needle, haystack) {
    var i = 0;
    var end = haystack.length;
    while (i < end) {
      if (needle == haystack[i]) {
        return i;
      }
      i++;
    }
    return -1;
  },
  
  /**
   * Returns a DOM node reference for the Id passed. If a DOM node reference
   * is passed, then it is returned.
   *    @param    id      string  HTML tag Id
   *    @return   object  DOM node reference.
   */
  GetNode : function(id) {
    return (typeof(id) == "string") ? document.getElementById(id) : id;
  },
  
  /**
   * Returns the function toggle links execute onclick
   *    @param    instance  object  Reference to the instance of this class
   *    @return   function  Function executed when a toggle link is clicked
   */
  GetOnclickFn : function(instance) {
    return function() {
      return instance.DoToggle(this.href);
    };
  },
  
  /**
   * Searches an array for a certain value
   *    @param    needle    variable  Value to search for
   *    @param    haystack  array     Array to search
   *    @return   boolean   True if found, false otherwise
   */
  InArray : function(needle, haystack) {
    for (var i=0, end=haystack.length; i<end; i++) {
      if (haystack[i] == needle) {
        return true;
      }
    }
    return false;
  },
  
  /**
   * Tests a variable to see if it's an array
   *    @param    x         variable  Variable to test.
   *    @return   boolean   True if an array, false otherwise.
   */
  IsArray : function(x) {
    return (typeof(x) == "object" && x.constructor == Array);
  },
  
  /**
   * Toggles a link and box on or off.
   *    @param    i         integer   Index of this.toggleIds
   *    @param    toggleOn  boolean   True to toggle this on, false for off
   *    @return   void
   */
  Toggle : function(i, toggleOn) {
    if (toggleOn) {
      this.links[i].className = this.linkClassNameOn;
      this.GetNode( this.toggleIds[i] ).className = this.toggleClassNameOn;
    } else {
      this.links[i].className = this.linkClassNameOff;
      this.GetNode( this.toggleIds[i] ).className = this.toggleClassNameOff;
    }
  },
  
  /**
   * Toggle all boxes off, except the toggle box Ids passed. If the
   * onBeforeToggle function returns false, then nothing is toggled.
   *    @param    exceptions  array   Toggle box Ids that should be turned on.
   *    @return   void
   */
  ToggleAll : function(exceptions) {
    var i = 0;
    var end = this.toggleIds.length;
    var currentToggleId = null;
    var currentToggleIndex = -1;
    
    if ( !this.onBeforeToggle() ) {return;}
    
    while (i < end) {
      if ( !this.InArray(this.toggleIds[i], exceptions) ) {
        this.Toggle(i, false);
      } else {
        this.Toggle(i, true);
        currentToggleId = this.toggleIds[i];
        currentToggleIndex = i;
      }
      i++;
    }
    
    this.onAfterToggle(currentToggleId, currentToggleIndex);
  }
  
};


/**
 * Global object to manage all toggle boxes. Use of this object is optional,
 * but is nice because it automates the calls to each ToggleGroup.Destroy()
 * function calls when you add ToggleGroups.DestroyAll() to the
 * window.onunload event. This prevents memory leaks in some browsers.
 */
var ToggleGroups = {
  
  groups : [],
  
  /**
   * Add a toggle group to the page. Takes the same parameters as the
   * ToggleGroup class constuctor above.
   *    @return   integer   Index in ToggleGroups.groups array where new group is inserted.
   */
  Add : function(wrapperId, exception, onBeforeToggle, onAfterToggle) {
    return this.groups.push( new ToggleGroup(wrapperId, exception, onBeforeToggle, onAfterToggle) ) - 1;
  },
  
  /**
   * To be called in window.onunload event to prevent memory leaks in some
   * browsers for all toggle groups created using the ToggleGroups.Add()
   * function.
   *    @param    void
   *    @return   void 
   */
  DestroyAll : function() {
    var i = 0;
    var end = this.groups.length;
    
    while (i < end) {
      this.groups[i].Destroy();
      this.groups[i] = null;
      i++;
    }
  }
  
};
