/* ------------- Global constants and methods ------------- */

var
  // Key Codes
  kcTab   = 9,
  kcEnter = 13,
  kcSpace = 32,
  kcLeft  = 37,
  kcUp    = 38,
  kcRight = 39,
  kcDown  = 40,
  // Node Class Indexes
  clsNormal   = 0,
  clsSelected = 1,
  // Node Image Indexes
  imgSimple    = 0,
  imgCollapsed = 1,
  imgExpanded  = 2,
  // Spec Image Indexes
  imgEmpty             = 0,
  imgLine              = 1,
  imgSimpleNode        = 2,
  imgLastSimpleNode    = 3,
  imgCollapsedNode     = 4,
  imgLastCollapsedNode = 5,
  imgExpandedNode      = 6,
  imgLastExpandedNode  = 7,
  // Other
  arrTrees = [];

try {
  var strTreeScriptPath = GetFilePath(document.getElementById('objTreeScript').src);
  document.write('<link href="' + strTreeScriptPath + 'tree.css" type="text/css" rel=stylesheet>');
} catch(e) {
  alert('objTreeScript object is not found');
}

function GetFilePath(strFile) {
  var strResult = '';
  var arrFolders = strFile.split('/');
  for (var i = 0; i < arrFolders.length - 1; i++)
    strResult += arrFolders[i] + '/';
  return strResult;
}

function Node(strID, strCaption, strURL, strTarget, intImageIndex, strClassName, arrNodes) {
  return(new NodeClass(strID, strCaption, strURL, strTarget, intImageIndex, strClassName, arrNodes));
}

function Tree(strRootCaption, strRootURL, strRootTarget, intRootImageIndex, strRootClassName,
   blnCanCheckAnyEvents, arrNodeImages, arrNodes) {
  return(new TreeClass(strRootCaption, strRootURL, strRootTarget, intRootImageIndex, strRootClassName,
    blnCanCheckAnyEvents, arrNodeImages, arrNodes));
}

/* ------------- Node class ------------- */

function NodeClass(strID, strCaption, strURL, strTarget, intImageIndex, strClassName, arrNodes) {
  this.ID = strID;
  this.Root = this.ID == 'root';
  this.Caption = strCaption;
  this.URL = strURL == '' ? '#' : strURL;
  this.Target = (strTarget == null) || (strTarget == '') ? '_self' : strTarget;
  this.ImageIndex = intImageIndex == null ? -1 : intImageIndex;
  this.ClassName = strClassName == null ? '' : strClassName;
  this.Nodes = arrNodes == null ? [] : arrNodes;
  this.Parent = null;
  this.Tree = null;
  this.Expanded = false;
  this.Last = false;
}

NodeClass.prototype.AppendNode = function (objNode) {
  return this.InsertNode(objNode, (this.Tree ? this : this.Nodes[0]).Nodes.length);
}

NodeClass.prototype.AsHTML = function () {
  var strSpecImage = '';
  var strUniqueID = this.Tree.ID + '_' + this.ID;
  if (!this.Root)
    if (this.HasNodes())
      strSpecImage = '<a href="javascript:arrTrees[' + this.Tree.ID + '].NodeByID(\'' + this.ID +
        '\').InvertState()" hidefocus=true tabindex="-1"><img id=SpecImage' + strUniqueID +
        ' src="' + this.SpecImage() + '" class=clsTreeNodeSpecImage align=absmiddle></a>';
    else
      strSpecImage = '<img src="' + this.SpecImage() + '" class=clsTreeNodeSpecImage align=absmiddle>';
  return(this.ParentsAsHTML() + strSpecImage + '<a href="' + this.URL + '" target=' + this.Target + ' hidefocus=true ' +
    'tabindex="-1" onclick="return arrTrees[' + this.Tree.ID + '].NodeByID(\'' + this.ID + '\').Execute()">' +
    (this.ImageIndex > -1 ? '<img id=Image' + strUniqueID + ' src="' + this.NodeImage() +
    '" class=clsTreeNodeImage align=absmiddle>' : '') + '<span id=Caption' + strUniqueID + ' class="' +
    this.Tree.NodeClasses[this.Tree.SelectedNode == this ? clsSelected : clsNormal] + ' ' + this.ClassName + '">' +
    this.Caption + '</span></a><br><div id=Nodes' + strUniqueID + ' style="display:' +
    (this.Expanded ? '' : 'none') + '">' + this.ChildsAsHTML() + '</div>');
}

NodeClass.prototype.ChildsAsHTML = function () {
  var strResult = '';
  for (var i = 0; i < this.Nodes.length; i++)
    strResult += this.Nodes[i].AsHTML();
  return strResult;
}

NodeClass.prototype.Collapse = function () {
  this.Expanded = false;
  this.Refresh();
  if ((this.Tree.SelectedNode != null) &&
     (this.NodeByID(this.Tree.SelectedNode.ID) != null))
    this.Select();
  if (this.Tree.OnStateChange != null)
    this.Tree.OnStateChange(this);
  if (this.Tree.OnCollapse != null)
    this.Tree.OnCollapse(this);
}

NodeClass.prototype.CollapseAll = function () {
  if (this.Collapse)
    this.Collapse();
  for (var i = 0; i < this.Nodes.length; i++)
    this.Nodes[i].CollapseAll();
}

NodeClass.prototype.Delete = function () {
  if (!this.Root)
    this.Parent.DeleteNode(this.Index());
}

NodeClass.prototype.DeleteNode = function (intNodeIndex) {
  var objThis = this.Tree ? this : this.Nodes[0];
  if (objThis.Tree.SelectedNode == objThis.Nodes[intNodeIndex])
    objThis.Tree.SelectedNode = null;
  for (var i = intNodeIndex + 1; i < objThis.Nodes.length; i++)
    objThis.Nodes[i - 1] = objThis.Nodes[i];
  objThis.Nodes.length--;
  objThis.InitNodes(objThis.Tree);
}

NodeClass.prototype.Deselect = function () {
  var objCaption = document.getElementById('Caption' + this.Tree.ID + '_' + this.ID);
  if (objCaption != null)
    objCaption.className = this.Tree.NodeClasses[clsNormal] + ' ' + this.ClassName;
  this.Tree.SelectedNode = null;
}

NodeClass.prototype.Execute = function () {
  this.Select();
  if (this.URL != '#') {
    if (this.Tree.OnBeforeExecute != null)
      return this.Tree.OnBeforeExecute(this);
    else
      return true;
  } else {
    this.InvertState();
    return false;
  }
}

NodeClass.prototype.Expand = function () {
  this.Expanded = true;
  this.Refresh();
  if (this.Tree.OnStateChange != null)
    this.Tree.OnStateChange(this);
  if (this.Tree.OnExpand != null)
    this.Tree.OnExpand(this);
}

NodeClass.prototype.ExpandAll = function () {
  if (this.Expand)
    this.Expand();
  for (var i = 0; i < this.Nodes.length; i++)
    this.Nodes[i].ExpandAll();
}

NodeClass.prototype.ExpandedNodesIDs = function () {
  var strResult = '';
  for (var i = 0; i < this.Nodes.length; i++) {
    var objNode = this.Nodes[i];
    if (objNode.HasNodes() && objNode.Expanded) {
      strResult += objNode.ID + ',';
      var s = objNode.ExpandedNodesIDs();
      if (s != '')
        strResult += s + ',';
    }
  }
  return strResult.substr(0, strResult.length - 1);
}

NodeClass.prototype.ExpandWithParents = function () {
  this.Expand();
  var objNode = this.Parent;
  while (objNode.Tree) {
    objNode.Expand();
    objNode = objNode.Parent;
  }
}

NodeClass.prototype.HasNodes = function () {
  return(this.Nodes.length > 0);
}

NodeClass.prototype.Index = function () {
  for (var i = 0; i < this.Parent.Nodes.length; i++)
    if (this.Parent.Nodes[i].ID == this.ID)
      return i;
}

NodeClass.prototype.InitNodes = function (objTree) {
  var intLastNodeIndex = this.Nodes.length - 1;
  for (var i = 0; i <= intLastNodeIndex; i++) {
    var objNode = this.Nodes[i];
    objNode.Tree = objTree;
    objNode.Parent = this;
    objNode.Last = i == intLastNodeIndex;
    objNode.InitNodes(objTree);
  }
}

NodeClass.prototype.InsertNode = function (objNode, intNodeIndex) {
  var objThis = this.Tree ? this : this.Nodes[0];
  objThis.Nodes.length++;
  for (var i = objThis.Nodes.length - 1; i > intNodeIndex; i--)
    objThis.Nodes[i] = objThis.Nodes[i - 1];
  objThis.Nodes[intNodeIndex] = objNode;
  objThis.InitNodes(objThis.Tree);
  return objNode;
}

NodeClass.prototype.InvertState = function () {
  if (this.Expanded)
    this.Collapse();
  else
    this.Expand();
}

NodeClass.prototype.NextNode = function (blnSkipChilds) {
  if (!blnSkipChilds && this.HasNodes() && this.Expanded)
    return this.Nodes[0];
  var intNodeIndex = this.Index();
  if (intNodeIndex < this.Parent.Nodes.length - 1)
    return this.Parent.Nodes[intNodeIndex + 1];
  else if (this.Parent.Tree)
    return this.Parent.NextNode(true);
  else
    return null;
}

NodeClass.prototype.NodeByID = function (strID) {
  var objResult = null;
  for (var i = 0; i < this.Nodes.length; i++) {
    var objNode = this.Nodes[i];
    if (objNode.ID == strID) {
      objResult = objNode;
      break;
    } else {
      objResult = objNode.NodeByID(strID);
      if (objResult != null)
        break;
    }
  }
  return objResult;
}

NodeClass.prototype.NodeByURL = function (strURL) {
  var objResult = null;
  for (var i = 0; i < this.Nodes.length; i++) {
    var objNode = this.Nodes[i];
    if (objNode.URL == strURL) {
      objResult = objNode;
      break;
    } else {
      objResult = objNode.NodeByURL(strURL);
      if (objResult != null)
        break;
    }
  }
  return objResult;
}

NodeClass.prototype.NodeImage = function () {
  if (this.ImageIndex == -1)
    return '';
  var varImage = this.Tree.NodeImages[this.ImageIndex];
  if (varImage.length == 3) {
    if (this.HasNodes())
      return varImage[this.Expanded ? imgExpanded : imgCollapsed];
    else
      return varImage[imgSimple];
  } else
    return varImage;
}

NodeClass.prototype.ParentsAsHTML = function () {
  var strResult = '';
  var objNode = this.Parent;
  while (objNode.Tree) {
    strResult = '<img src="' + this.Tree.SpecImages[objNode.Last ? imgEmpty : imgLine] +
      '" class=' + (objNode.Root ? 'clsTreeRootEmptyImage' :
      (objNode.Last ? 'clsTreeEmptyImage' : 'clsTreeLineImage')) + ' align=absmiddle>' + strResult;
    objNode = objNode.Parent;
  }
  return strResult;
}

NodeClass.prototype.PriorNode = function () {
  var intNodeIndex = this.Index();
  if (intNodeIndex > 0) {
    var objNode = this.Parent.Nodes[intNodeIndex - 1];
    while (objNode.HasNodes() && objNode.Expanded)
      objNode = objNode.Nodes[objNode.Nodes.length - 1];
    return objNode;
  } else if (this.Parent.Tree)
    return this.Parent;
  else
    return null;
}

NodeClass.prototype.Refresh = function () {
  if (!this.HasNodes())
    return;
  var strImage, strUniqueID = this.Tree.ID + '_' + this.ID;
  var objSpecImage = document.getElementById('SpecImage' + strUniqueID);
  if (objSpecImage != null) {
    strImage = this.SpecImage();
    if (objSpecImage.src.indexOf(strImage) == -1)
      objSpecImage.src = strImage;
  }
  if (this.ImageIndex > -1) {
    var objImage = document.getElementById('Image' + strUniqueID);
    if (objImage != null) {
      strImage = this.NodeImage();
      if (objImage.src.indexOf(strImage) == -1)
        objImage.src = strImage;
    }
  }
  var objNodes = document.getElementById('Nodes' + strUniqueID);
  if (objNodes != null)
    objNodes.style.display = this.Expanded ? '' : 'none';
}

NodeClass.prototype.Select = function () {
  if (this.Tree.SelectedNode != null)
    this.Tree.SelectedNode.Deselect();
  var objCaption = document.getElementById('Caption' + this.Tree.ID + '_' + this.ID);
  if (objCaption != null) {
    objCaption.className = this.Tree.NodeClasses[clsSelected] + ' ' + this.ClassName;
    objCaption.parentNode.focus();
  }
  this.Tree.SelectedNode = this;
  if (this.Tree.OnSelect != null)
    this.Tree.OnSelect(this);
}

NodeClass.prototype.SpecImage = function () {
  var intIndex;
  if (this.HasNodes())
    intIndex = this.Expanded ? (this.Last ? imgLastExpandedNode : imgExpandedNode) :
      (this.Last ? imgLastCollapsedNode : imgCollapsedNode);
  else
    intIndex = this.Last ? imgLastSimpleNode : imgSimpleNode;
  return this.Tree.SpecImages[intIndex];
}

/* ------------- Tree class ------------- */

function TreeClass(strRootCaption, strRootURL, strRootTarget, intRootImageIndex, strRootClassName,
   blnCanCheckAnyEvents, arrNodeImages, arrNodes) {
  this.ID = arrTrees.length;
  this.OnBeforeExecute = null;
  this.OnCollapse = null;
  this.OnExpand = null;
  this.OnSelect = null;
  this.OnStateChange = null;
  this.CanCheckAnyEvents = blnCanCheckAnyEvents;
  this.NodeImages = arrNodeImages;
  this.Nodes = [Node('root', strRootCaption, strRootURL, strRootTarget, intRootImageIndex, strRootClassName, arrNodes)];
  this.NodeClasses = ['clsTreeNormalNode', 'clsTreeSelectedNode'];
  this.SelectedNode = null;
  this.SpecImages = ['empty.gif', 'line1.gif', 'line2.gif', 'line3.gif', 'line4.gif', 'line5.gif', 'line6.gif', 'line7.gif'];
  for (var i = 0; i < this.SpecImages.length; i++)
    this.SpecImages[i] = strTreeScriptPath + 'images/' + this.SpecImages[i];
  this.InitNodes(this);
  arrTrees[this.ID] = this;
}

TreeClass.prototype.CollapseThroughSelectedNode = function () {
  if (this.SelectedNode != null)
    this.SelectedNode.CollapseAll();
}

TreeClass.prototype.ExpandNodesByIDs = function (arrIDs) {
  for (var i = 0; i < arrIDs.length; i++) {
    var objNode = this.NodeByID(arrIDs[i]);
    if (objNode != null)
      objNode.ExpandWithParents();
  }
}

TreeClass.prototype.ExpandThroughSelectedNode = function () {
  if (this.SelectedNode != null)
    this.SelectedNode.ExpandAll();
}

TreeClass.prototype.ProcessKey = function (objEvent) {
  var intKeyCode = objEvent.which ? objEvent.which : objEvent.keyCode;
  if (intKeyCode == kcTab) {
    window.focus();
    return true;
  }
  var objNode = this.SelectedNode;
  if (objNode == null)
    if ((intKeyCode == kcDown) && this.HasNodes()) {
      this.Nodes[0].Select();
      return true;
    } else
      return false;
  switch (intKeyCode) {
    case kcLeft:
      objNode.Collapse();
      return true;
    case kcRight:
      objNode.Expand();
      return true;
    case kcUp:
      objNode = objNode.PriorNode();
      if (objNode != null)
        objNode.Select();
      return true;
    case kcDown:
      objNode = objNode.NextNode();
      if (objNode != null)
        objNode.Select();
      return true;
    case kcSpace:
      objNode.InvertState();
      return true;
    case kcEnter:
      objNode.Execute();
      return true;
  }
  return false;
}

TreeClass.prototype.Render = function () {
  var strTree = '<nobr>' + this.ChildsAsHTML() + '</nobr>';
  var objTree = document.getElementById('Tree' + this.ID);
  if (objTree != null)
    objTree.innerHTML = strTree;
  else {
    var strEvent = 'return !arrTrees[' + this.ID + '].ProcessKey(objEvent)';
    document.write('<div id=Tree' + this.ID + ' class=clsTree' +
      (!this.CanCheckAnyEvents ? ' onkeydown="var objEvent = event;' + strEvent + '"' : '') + '>' + strTree + '</div>');
    if (this.CanCheckAnyEvents)
      eval('document.onkeydown = function (e) {var objEvent = e ? e : event;' + strEvent + '}');
  }
}

TreeClass.prototype.AppendNode = NodeClass.prototype.AppendNode;
TreeClass.prototype.ChildsAsHTML = NodeClass.prototype.ChildsAsHTML;
TreeClass.prototype.CollapseAll = NodeClass.prototype.CollapseAll;
TreeClass.prototype.DeleteNode = NodeClass.prototype.DeleteNode;
TreeClass.prototype.ExpandAll = NodeClass.prototype.ExpandAll;
TreeClass.prototype.ExpandedNodesIDs = NodeClass.prototype.ExpandedNodesIDs;
TreeClass.prototype.HasNodes = NodeClass.prototype.HasNodes;
TreeClass.prototype.InitNodes = NodeClass.prototype.InitNodes;
TreeClass.prototype.InsertNode = NodeClass.prototype.InsertNode;
TreeClass.prototype.NodeByID = NodeClass.prototype.NodeByID;
TreeClass.prototype.NodeByURL = NodeClass.prototype.NodeByURL;