/**

  This module contains code for rendering various elements of the web site from
  information stored within an XML file "HoylD.xml" in the root of the web site.
  
  @Version 1.0
  @Author  David Hoyle
  @Date    25 Mar 2010

**/

/** A global variable to hold the xml document reference for the menu rendering. **/  
var xmlDoc;
/** A global varaiable to hold information as to whether IE is being used or not. **/
var boolIE;
/** A global varaible to hold the SoftwareID which is to be found and rendered. **/
var strSoftwareID;

/**
  
  This function is an on load event handler for the website's menu system.
  
  @precon  None.
  @postcon Starts the process of rendering the menu system from the XML data
           using DHTML.
  
**/
function loadMenuXML(strName)
{
  var strXMLSource = "HoylD.xml";
  strSoftwareID = strName;
  //load xml file
  // code for IE
  if (window.ActiveXObject)
    {
      boolIE = true;
      xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
      xmlDoc.async = false;
      xmlDoc.load(strXMLSource);
      renderTree();
    }
  else if (document.implementation && document.implementation.createDocument)
    {
      boolIE = false;
      try
        { // Firefox, Mozilla, Opera, etc.
          xmlDoc = document.implementation.createDocument("", "", null);
          xmlDoc.load(strXMLSource);
          xmlDoc.onload = renderTree;
        }
      catch (e)
        { // Chrome
          var xmlhttp = new window.XMLHttpRequest();
          xmlhttp.open("GET", strXMLSource, false);
          xmlhttp.send(null);
          xmlDoc = xmlhttp.responseXML.documentElement;
          renderTreeChrome();
        }
    }
  else
    alert('Your browser cannot handle this script');
}

/**

  This function actually starts the rendering of the menu tree information
  based on the structure of the XML file. This is used for Internet Explorer and
  Firefox, etc.
  
  @precon  None.
  @postcon Actually starts the rendering of the menu tree information
           based on the structure of the XML file.

**/
function renderTree()
{
  var i;
  OutputHTML(document.getElementById("tree"), "");
  for (i = 0; i < xmlDoc.childNodes.length; i++)
    if (xmlDoc.childNodes[i] != null)
      if (xmlDoc.childNodes[i].nodeName == 'Software')
        renderFolders(xmlDoc.childNodes[i], document.getElementById("tree"));
  OutputHTML(document.getElementById("LastUpdated"), 'Last Updated: ' +
    new Date(xmlDoc.lastModified));
  renderSoftware();
}

/**

  This function actually starts the rendering of the menu tree information
  based on the structure of the XML file. This is used for Chrome as the xmlDoc
  starts with the "Software" contain NOT the xml container.

  @precon  None.
  @postcon Actually starts the rendering of the menu tree information
           based on the structure of the XML file.

**/
function renderTreeChrome()
{
  OutputHTML(document.getElementById("tree"), "");
  renderFolders(xmlDoc, document.getElementById("tree"));
  OutputHTML(document.getElementById("LastUpdated"), 'Last Updated: ' +
    new Date(xmlDoc.lastModified));
  renderSoftware();
}

/**

  This function renders the "Folders" within the XML file.
  
  @precon  Folders must be a value childNodes collection and RootList must be
           a valid DOM element under which to insert the menu system.
  @postcon Renders the "Folders" within the XML file.
  
  @param   Folders as a childNodes collection
  @param   RootList as a Element

**/
function renderFolders(Folders, RootList)
{
  for (var i = 0; i < Folders.childNodes.length; i++)
    if (Folders.childNodes[i].nodeType == 1)
      {
        var ListItem = AddElement(RootList, 'div');
        AddAttribute(ListItem, 'class', 'folder');
        var TextItem = AddElement(ListItem, 'div');
        AddAttribute(TextItem, 'class', 'foldertext');
        OutputHTML(TextItem, Folders.childNodes[i].getAttribute('ID'));
        renderPackages(Folders.childNodes[i], ListItem, 0);
      }
}

/**

  This function recursively renders the "Packages" within the XML file.
  
  @precon  Packages must be a value childNodes collection and SubRootList must
           be a valid DOM element under which to insert the menu system.
  @postcon Recursively renders the "Packages" within the XML file.
  
  @param   Packages as a childNodes collection
  @param   SubRootList as a Element
  @param   iLevel as an Integer

**/
function renderPackages(Packages, SubRootList, iLevel)
{
  for (var i = 0; i < Packages.childNodes.length; i++)
    {
      if (Packages.childNodes[i].nodeType == 1 &&
        Packages.childNodes[i].nodeName == 'Package')
        {
          var ListItem = AddElement(SubRootList, 'div');
          AddAttribute(ListItem, 'class', 'package');
          if (iLevel > 0 && !boolIE) /* IE doesn't like STYLE attributes in DIV tags */
            AddAttribute(ListItem, 'style', 'margin-left: 5%');
          var BaseItem = ListItem;
          var strURL = getPackageInfo(Packages.childNodes[i], 'URL');
          if (strURL != '')
            {
              var Anch = AddElement(ListItem, 'a');
              AddAttribute(Anch, 'href', strURL);
              ListItem = Anch;
            }
          strInfo = getSoftwareVersion(Packages.childNodes[i]);
          OutputHTML(ListItem, strInfo + ' ');
          var strUpdateDate = getPackageInfo(Packages.childNodes[i], 'Updated');
          var now = new Date();
          var updated = new Date(Date.parse(strUpdateDate));
          var Months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
            'Sep', 'Oct', 'Nov', 'Dec']
          var strDescription = getPackageInfo(Packages.childNodes[i], 'Description');
          var aDescription = [];
          if (strDescription.length > 0)
            aDescription = strDescription.split(/\s*\n\s*/);
          if (updated > new Date(now - 90 * 24 * 60 * 60 * 1000)) /* 3 months */
            {
              var AImage = AddElement(BaseItem, 'img');
              AddAttribute(AImage, 'src', 'Images/new.gif');
              AddAttribute(AImage, 'border', '0');
              AddAttribute(AImage, 'align', 'absmiddle');
              var span = AddElement(BaseItem, 'span');
              OutputHTML(span, ' (' + updated.getDate() + '\\' +
                Months[updated.getMonth()] + ')');
              if (strDescription != '')
                {
                  var p = AddElement(BaseItem, 'div');
                  AddAttribute(p, 'class', 'description');
                  var str = "";
                  for (var iString = 0; iString < aDescription.length; iString++)
                    if (aDescription[iString] != '')
                      {
                        if (str != '')
                          if (boolIE)
                            str = str + '\n'
                          else
                            str = str + '<br/>';
                        str = str + aDescription[iString];
                      }
                  OutputHTML(p, str);
                }
            }
          else
            {
              if (aDescription.length > 0)
                {
                  var str = "";
                  for (var iString = 0; iString < aDescription.length; iString++)
                    if (aDescription[iString] != '')
                      {
                        if (str != '')
                          str = str + ' ';
                        str = str + aDescription[iString];
                      }
                  AddAttribute(BaseItem, 'title', str);
                }
            }
          renderPackages(Packages.childNodes[i], BaseItem, iLevel + 1);
        }
    }
}

/**

  This function builds a string which represents the versio nnumber of the
  "Package" build from its parts Title, Major, Minor, and BugFix.
  
  @precon  SoftwarePackage must be a valid "Package" node in the XML document.
  @postcon Builds a string which represents the versio nnumber of the
           "Package" build from its parts Title, Major, Minor, and BugFix.
           
  @param   SoftwarePackage as a childNodes collection.
  @return  a String

**/
function getSoftwareVersion(SoftwarePackage)
{
  var BugFixes = ' abcdefghijklmnopqrstuvwxyz';
  var strInfo = getPackageInfo(SoftwarePackage, 'Title');
  var iMajor = getPackageInfo(SoftwarePackage, 'Major');
  var iMinor = getPackageInfo(SoftwarePackage, 'Minor'); 
  var iBugFix = getPackageInfo(SoftwarePackage, 'BugFix');
  if (iMajor != '')
    strInfo = strInfo + ' ' + iMajor; 
  if (iMinor != '')
    strInfo = strInfo + '.' + iMinor; 
  if (iBugFix != '')
    {
      var iIndex = parseInt(iBugFix);
      if (iIndex >= 0)
        if (iIndex < 27)
          strInfo = strInfo + BugFixes.substr(iIndex, 1);
    }
  return strInfo;
}

/**

  This function iterates the passed packing looking for a sub element with the
  node name matching the name passed. If found returns the text within the
  element.
  
  @precon  Package must be a valid Package from the XML file.
  @postcon Iterates the passed packing looking for a sub element with the
           node name matching the name passed. If found returns the text within
           the element.
           
  @param   Package as a childNodes collection
  @param   strName as a string
  @param   a String

**/
function getPackageInfo(Package, strName)
{
  for (var i = 0; i < Package.childNodes.length; i++)
    if (Package.childNodes[i].nodeName == strName)
      return Package.childNodes[i].firstChild.nodeValue;
  return '';
}

/**

  This function outputs the given text to the given element. IE does not like
  the use of innerHTML so innerText has been used for IE.
  
  @precon  AElement must be a valid HTML element.
  @postcon Outputs the given text to the given element.
  
  @param   AElement as a Element
  @param   strOutput as a String 

**/
function OutputHTML(AElement, strOutput)
{
  if (boolIE)
    AElement.innerText = strOutput;
  else
    AElement.innerHTML = strOutput;
}

/**

  This function adds an attribute to the given element the long hand way since
  setAttribute() in IE is hopelessly broken.
  
  @precon  ANode must be a valid HTML Element
  @postcon Adds an attribute to the given element the long hand way since
           setAttribute() in IE is hopelessly broken.
           
  @param   ANode as an Element
  @param   strName as a String
  @param   strValue as a String 

**/
function AddAttribute(ANode, strName, strValue)
{
  var a = document.createAttribute(strName);
  a.nodeValue = strValue;
  ANode.setAttributeNode(a);
}

/**

  This function adds a new element (described as a tag text) to the passed
  parent element.
  
  @precon  ANode must be a valid HTML Element.
  @postcon Adds a new element (described as a tag text) to the passed parent 
           element.
           
  @param   ANode as an Element
  @param   strTag as a String 

**/
function AddElement(ANode, strTag)
{
  var elem = document.createElement(strTag);
  ANode.appendChild(elem);
  return elem;
}

/**

  This function start the search for the software version corresponding to the
  software ID stored in strSoftwareID.
  
  @precon  None.
  @postcon Start the search for the software version corresponding to the
           software ID stored in strSoftwareID.

**/
function renderSoftware()
{
  if (strSoftwareID != '')
    FindSoftware(xmlDoc);
}

/**

  This function recursively searches for a package with an ID equal to
  strSoftwareID. If found renders the version number of the software within
  an anchor tag referencing the downloadable zip file of the software. Returns
  true if found.
  
  @precon  rootElement must be a valid childNodes collection in the xml document.
  @postcon Recursively searches for a package with an ID equal to
           strSoftwareID. If found renders the version number of the software
           within an anchor tag referencing the downloadable zip file of the
           software. Returns true if found.
           
  @param   rootElement as an Element
  @return  a Boolean

**/
function FindSoftware(rootElement)
{
  for (var i = 0; i < rootElement.childNodes.length; i++)
    if (rootElement.childNodes[i].nodeType == 1)
    {
      if (rootElement.childNodes[i].getAttribute('ID') == strSoftwareID)
        {
          OutputHTML(document.getElementById("SoftwareTitle"), "");
          var Img = document.createElement('img');
          AddAttribute(Img, 'class', 'verticallymiddle');
          AddAttribute(Img, 'src', 'Images/disk.gif');
          AddAttribute(Img, 'border', '0');
          AddAttribute(Img, 'align', 'middle');
          document.getElementById("SoftwareTitle").appendChild(Img);
          var Anch = document.createElement('a');
          var strVersion = getSoftwareVersion(rootElement.childNodes[i]);
          strVersion = strVersion + ' (';
          var strTmp = getPackageInfo(rootElement.childNodes[i], 'Major');
          if (strTmp != '') 
            strVersion = strVersion + strTmp;
          strTmp = getPackageInfo(rootElement.childNodes[i], 'Minor');
          if (strTmp != '') 
            strVersion = strVersion + '.' + strTmp;
          strTmp = getPackageInfo(rootElement.childNodes[i], 'BugFix');
          if (strTmp != '') 
            strVersion = strVersion + '.' + strTmp;
          strTmp = getPackageInfo(rootElement.childNodes[i], 'Build');
          if (strTmp != '') 
            strVersion = strVersion + '.' + strTmp;
          strVersion = strVersion + ')';
          strTmp = getPackageInfo(rootElement.childNodes[i], 'Updated');
          if (strTmp != '')
            strVersion = strVersion + ' - ' + strTmp;
          OutputHTML(Anch, strVersion);
          document.getElementById("SoftwareTitle").appendChild(Anch);
          var strZIP = getPackageInfo(rootElement.childNodes[i], 'ZIP');
          AddAttribute(Anch, 'href', strZIP);
          return true; 
        }
      else
      {
        if (FindSoftware(rootElement.childNodes[i]))
          return true;
      }
    } 
}