function init()
{
   cleanWhitespace(document.getElementById('tree').parentNode);

   setupLis();

   treeConfig.atViewportTop         = true;
   treeConfig.atViewportBtm         = false;
   treeConfig.currentSpan           = getCookie('currentSpan') ? document.getElementById(getCookie('currentSpan')) : document.getElementsByTagName('span')[0];
   treeConfig.currentSpan.className = treeConfig.currentSpan.getLi().className.match(/folder/) ? treeConfig.currentSpan.className += ' current' : 'current';

   treeConfig.animator = new Animator();
   treeConfig.animator.init(treeConfig.toggleSpeed);

   if (treeConfig.homeIconVisible)
   {
      var homeIcon = document.createElement('img');
         homeIcon.id      = 'homeIcon';
         homeIcon.src     = treeConfig.homeIcon;
         homeIcon.onclick = toggleItem;
      document.getElementById('iconWrapper').insertBefore(homeIcon, document.getElementById('mailIcon').parentNode);
   }

   if (BrowserDetect.browser == 'explorer')
   {
   // hide focus ring in xPloder [for others this is done in CSS]
      var hrefs = document.getElementsByTagName('a');
      for (var i = 0; i < hrefs.length; i++)
         hrefs[i].hideFocus = true;
   }

   if (top.main)     // allow stand alone tree for debugging
   {
   // update main frame or opener according to hi-lited file in the tree or default home location
      top.main.onload =
      function()
      {
         treeConfig.mainLocation = treeConfig.currentSpan.parentNode.href && top.main.location.href != treeConfig.currentSpan.parentNode.href ? treeConfig.currentSpan.parentNode.href : treeConfig.homeLocation;
         top.main.location.href  = treeConfig.mainLocation;
         trackMainLocation();
      }
   }

// keyrepeat is a mess...
   if (BrowserDetect.browser == 'opera' || BrowserDetect.OS == 'mac' && BrowserDetect.browser == 'firefox')
      document.onkeypress = keypressHandler;
   else
      document.onkeydown = function(event) { keypressHandler(event); };    // chrome needs the anon function
   document.getElementById('tree').onclick = function(event) { clickHandler(event); };
}

function setupLis()
{
   if (getCookie('treeState'))
      var treeState = getCookie('treeState').split(',');

   var i, s, u, l = document.getElementsByTagName('li'), counter = 0;
   for (i = 0; i < l.length; i++)
   {
      l[i].getPrevLi = getPrevLi;
      l[i].getNextLi = getNextLi;
      l[i].getSpan   = getSpan;

      s = l[i].getSpan();
         s.toggleState = toggleState;
         s.toggleItem  = toggleItem;
         s.getLi       = getLi;
         s.id          = 'span' + i;

      if (l[i].className.match(/folder/))
      {
         l[i].toggleFolder = toggleFolder;
         l[i].getUl        = getUl;
         l[i].expanded     = false;
         if (BrowserDetect.browser == 'explorer' && BrowserDetect.version < 8)
         {
            if (l[i].id)
            {
               var g = document.createElement('img');
                  g.src = './pix/twistyClosed' + l[i].id.substring(7) + '.gif';
            }
            var gg = g.cloneNode(false);
            l[i].insertBefore(gg, s);
         }

         u = l[i].getUl();
         if (treeConfig.easedToggle)
         {
            l[i].easeToggle  = easeToggle;
            u.expandedHeight = u.offsetHeight;
         }

         s.className = 'closed';                                     // className determines glyph

         if (treeState && treeState[counter++] == '1')
         {
            l[i].expanded = true;
            s.className   = 'open';
         }
         else
         {
            if (treeConfig.easedToggle)
               u.style.height = 0;                                   // prevent miscalculation on first time eased expansion of nested folders
            else
               u.style.display = 'none';
         }
      }
   }
}

function toggleItem()
{
   if (this.parentNode.className && this.parentNode.className.match(/folder/))
   {
      this.parentNode.toggleFolder();
      return;
   }

   var newURL = this.id && this.id.match(/homeIcon/) ? treeConfig.homeLocation : treeConfig.currentSpan.parentNode.href;
   if (top.main)
      top.main.location.href = newURL;
   else if (window.opener)
      window.opener.location.href = newURL;
};

function toggleFolder()
{
   if (!treeConfig.easedToggle || BrowserDetect.browser == 'explorer' && BrowserDetect.version < 8)      // xPloder 8- goes haywire wrt the CSS UL overflow selector
   {
      this.getUl().style.display = this.expanded ? 'none' : 'block';
      this.getUl().style.height  = 'auto';
   }
   else
   {
      if (treeConfig.animator.running)
         return;

      this.easeToggle();
   }
   this.expanded = !this.expanded;

   var currentSpan = this.getSpan();
   if (treeConfig.animateTwisty)
   {
      currentSpan.className = 'inbetween current';
      setTimeout( function() { currentSpan.toggleState() }, 100);
   }
   else
      currentSpan.toggleState();

//   if (this.scrollIntoView)
//      this.scrollIntoView();
}

function toggleState()
{
   treeConfig.currentSpan.className = treeConfig.currentSpan.parentNode.className.match(/folder/) ? treeConfig.currentSpan.parentNode.expanded ? 'open' : 'closed' : null;
   treeConfig.currentSpan           = this;
   treeConfig.currentSpan.className = this.className ? this.className + ' current' : 'current';
}

function easeToggle()
{
   var direction, expandedHeight, parentHeight, temp, tUl, thisUl;

   thisUl = this.getUl();
   if (this.expanded)
   {
      direction      = 'rev';
      expandedHeight = thisUl.offsetHeight;
   }
   else
   {
      direction      = 'fwd';
      expandedHeight = thisUl.expandedHeight;

      function calcExpandedHeight(temp)
      {
         while (temp)
         {
            if (temp.className.match(/folder/))
            {
               if (temp.expanded)
                  calcExpandedHeight(temp.getUl().firstChild);
               else
                  expandedHeight -= temp.getUl().expandedHeight;
            }
            temp = temp.nextSibling;
         }
      }
      calcExpandedHeight(thisUl.firstChild);
   }
   treeConfig.animator.add(thisUl, 0, expandedHeight, 'outquint', 'height', direction);

   temp = this.parentNode.parentNode;
   while (temp.nodeName == 'LI')
   {
      tUl  = temp.getUl();
      temp = temp.parentNode.parentNode;

      parentHeight = this.expanded ? tUl.offsetHeight - expandedHeight : tUl.offsetHeight + expandedHeight;
      treeConfig.animator.add(tUl, tUl.offsetHeight, parentHeight, 'outquint', 'height');
   }

   treeConfig.animator.start();
}

function trackMainLocation()
{
// hrefs in the tree have no anchors
   var realHref = top.main ? top.main.location.href : window.opener  ? window.opener.location.href : null;
   var trueHref = realHref.indexOf('#') < 0 ? realHref : realHref.substring(0, realHref.indexOf('#'));

   if (treeConfig.mainLocation != trueHref)
   {
      treeConfig.mainLocation = trueHref;

      var i, l = document.getElementsByTagName('li');
      for (i = 0; i < l.length; i++)
         if (l[i].className.match(/file/) && l[i].firstChild.href == trueHref)
            break;

   // some hrefs - e.g the root icon - are not in the tree
      if (l[i])
      {
         var temp = l[i];
         while (temp.parentNode.style.display == 'none')
         {
            if (temp.parentNode.parentNode.nodeName != 'LI')
               break;

            temp.parentNode.parentNode.className = 'open';
            temp.parentNode.parentNode.expanded  = true;
            temp.parentNode.style.display        = 'block';
            temp                                 = temp.parentNode.parentNode;
         }
         l[i].getSpan().toggleState();
         storeTreeState();

         if (l[i].parentNode.parentNode.scrollIntoView)
            l[i].parentNode.parentNode.scrollIntoView();
      }
   }

   setTimeout(trackMainLocation, 1000);
}

function clickHandler(event)
{
   event = event || getXploderEvent();

   var target = event.target;
// catch italicized, bolded, &c. file/folder names
   while (target && target.nodeName != 'SPAN')
      target = target.parentNode;

   if (!target)
      return;

   target.toggleState();
   target.toggleItem();

   storeTreeState();
   sendToDevNull(event);
}

function keypressHandler(event)
{
   event = event || getXploderEvent();
   if (event.keyCode == 13 || event.keyCode > 33 && event.keyCode < 41)
   {
      var currentLi = treeConfig.currentSpan.getLi();
      switch(getKey(event.keyCode))
      {
         case 'up':       goUp(currentLi).toggleState();       break;
         case 'pageup':   goPgup(currentLi).toggleState();     break;
         case 'home':     goHome(currentLi).toggleState();     break;
         case 'down':     goDown(currentLi).toggleState();     break;
         case 'pagedown': goPgdn(currentLi).toggleState();     break;
         case 'end':      goEnd(currentLi).toggleState();      break;
         case 'left':     goLeft(currentLi);                   break;
         case 'right':    goRight(currentLi);                  break;
         case 'enter':    treeConfig.currentSpan.toggleItem(); break;
         default:         sendToDevNull(event);
      }

      storeTreeState();
      sendToDevNull(event);
   }
}

function goUp(currentLi)
{
   var currentSpan = currentLi.getPrevLi().getSpan();

   if (getPosY(currentSpan) < getPageYOffset())
   {
      window.scrollTo(getPageXOffset(), getPosY(currentSpan));
      treeConfig.atViewportTop = true;
      treeConfig.atViewportBtm = false;
   }
// rollover
   if (parseInt(currentLi.getSpan().id.substring(4), 10) < parseInt(currentSpan.id.substring(4), 10))
   {
      window.scrollTo(0, getPageHeight());
      treeConfig.atViewportTop = false;
      treeConfig.atViewportBtm = true;
   }

   return currentSpan;
}

function goPgup(currentLi)
{
/* 'top.tree' is renamed 'top.treeFrame' since Safari acks the '.' notation for DOM elements[!]
 * and the top UL has an id of 'tree'; so Safari thinks top.tree is an UL element and confuses
 * util.js' getInnerWidth & getInnerHeight functions by passing an UL rather than a frame
 */
   if (treeConfig.atViewportTop)
      window.scrollTo(getPageXOffset(), getPageYOffset() - getInnerHeight(top.treeFrame));

   treeConfig.atViewportTop = true;
   treeConfig.atViewportBtm = false;

   var temp, i = 0, spans = document.getElementsByTagName('span');
   do
      temp = spans[i++];
   while (getPosY(temp) < getPageYOffset());

   return temp;
}

function goHome(currentLi)
{
   window.scrollTo(0, 0);
   treeConfig.atViewportTop = true;
   treeConfig.atViewportBtm = false;

   if (currentLi.parentNode.parentNode.nodeName != 'LI')
      return currentLi.parentNode.firstChild.getSpan();

   while (currentLi.parentNode.parentNode.parentNode.nodeName == 'UL')
      currentLi = currentLi.parentNode.parentNode.parentNode.firstChild;

   return currentLi.getSpan();
}

function goDown(currentLi)
{
   var currentSpan = currentLi.getNextLi().getSpan();

   if (getPageYOffset() + getInnerHeight(top.treeFrame) < getPosY(currentSpan) + currentSpan.offsetHeight)
   {
      window.scrollTo(getPageXOffset(), getPosY(currentSpan) + currentSpan.offsetHeight - getInnerHeight(top.treeFrame));
      treeConfig.atViewportTop = false;
      treeConfig.atViewportBtm = true;
   }
// rollover
   if (parseInt(currentLi.getSpan().id.substring(4), 10) > parseInt(currentSpan.id.substring(4), 10))
   {
      window.scrollTo(0, 0);
      treeConfig.atViewportTop = true;
      treeConfig.atViewportBtm = false;
   }

   return currentSpan;
}

function goPgdn(currentLi)
{
   if (treeConfig.atViewportBtm)
      window.scrollTo(getPageXOffset(), getPosY(currentLi));

   treeConfig.atViewportTop = false;
   treeConfig.atViewportBtm = true;

   var currentSpan, spans = document.getElementsByTagName('span'), i = spans.length - 1;
   do
   {
      do
         currentSpan = spans[i--];
      while (currentSpan.offsetHeight == 0);       // skip spans nested in uls with display: none
   }
   while (getPageYOffset() + getInnerHeight() < getPosY(currentSpan) + currentSpan.offsetHeight);

   return currentSpan;
}

function goEnd(currentLi)
{
   window.scrollTo(0, getPageHeight());
   treeConfig.atViewportTop = false;
   treeConfig.atViewportBtm = true;

   while (currentLi.parentNode.parentNode.parentNode.nodeName == 'UL')
      currentLi = currentLi.parentNode.parentNode;
   currentLi = currentLi.parentNode.lastChild;

   while (currentLi.expanded)
      currentLi = currentLi.getUl().lastChild;

   return currentLi.getSpan();
}

function goLeft(currentLi)
{
   var currentSpan = treeConfig.currentSpan;

   if (currentLi.parentNode.parentNode.nodeName != 'LI' && !currentLi.expanded)
      return currentSpan;

   if (currentLi.expanded)
      currentLi.toggleFolder();
   else
   {
      currentSpan = currentLi.parentNode.parentNode.getSpan();
      currentSpan.toggleState();
   }

   if (currentSpan.offsetTop < getPageYOffset())
      scrollTo(getPageXOffset, getPosY(currentSpan));

   return currentSpan;
}

function goRight(currentLi)
{
   if (currentLi.className.match(/folder/) && !currentLi.expanded)
      currentLi.toggleFolder();

   return treeConfig.currentSpan;
}

function getPrevLi()
{
   var nodeToReturn;

   if (this.previousSibling == null)
   {
      if (this.parentNode.parentNode.nodeName == 'LI')
         return this.parentNode.parentNode;

      nodeToReturn = this.parentNode.lastChild;
      if (nodeToReturn.className.match(/folder/))
         while (nodeToReturn.getUl && nodeToReturn.getUl() && nodeToReturn.expanded)
            nodeToReturn = nodeToReturn.getUl().lastChild;

      return nodeToReturn;
   }

   if (this.previousSibling.className.match(/folder/) && this.previousSibling.expanded)
   {
      nodeToReturn = this.previousSibling;
      while (nodeToReturn.getUl && nodeToReturn.getUl() && nodeToReturn.expanded)
         nodeToReturn = nodeToReturn.getUl().lastChild;

      return nodeToReturn;
   }

   return this.previousSibling;
}

function getNextLi()
{
   if (this.expanded)
      return this.getUl().firstChild;

   var nodeToReturn = this;
   while (nodeToReturn.nextSibling == null && nodeToReturn.parentNode.nodeName == 'UL')
      nodeToReturn = nodeToReturn.parentNode.parentNode;

   if (nodeToReturn.nodeName == 'LI')
      return nodeToReturn.nextSibling;
   else
      return nodeToReturn.firstChild.firstChild;
}

function getUl()
{
   var i = 0, t = this.childNodes[i];
   while(t && t.nodeName != 'UL')
      t = this.childNodes[i++];
   return t || null;
}

function getLi()
{
   var t = this.parentNode;
   while(t.nodeName != 'LI')
      t = t.parentNode;
   return t || null;
}

function getSpan()
{
   var i = 0, t = this.childNodes[i];
   while (t && t.nodeName != 'SPAN')
      t = this.childNodes[i++];
   if (!t)     // 'this' is a 'file' li, which has an 'a' element
   {
      var i = 0, t = this.firstChild.childNodes[i];
      while (t && t.nodeName != 'SPAN')
         t = this.firstChild.nextSibling.childNodes[i++];
   }
   return t || null;
}

function storeTreeState()              // called after tree display mutations since Opera refuses to ack onunload
{
   delCookie('currentSpan');
   try { setCookie('currentSpan', treeConfig.currentSpan.id); } catch(e) {}

   var treeState = '';
   var lis = document.getElementsByTagName('li');
   for (var i = 0; i < lis.length; i++)
      if (lis[i].className.match(/folder/))
      // only register folders whose parent is expanded
         (lis[i].parentNode.id.match(/tree/) || lis[i].parentNode.parentNode.expanded) && lis[i].expanded ? treeState += '1,' : treeState += '0,';
   delCookie('treeState');
   setCookie('treeState', treeState);
}

   addLoadEvent(init);
