var
  Tree_Actions = new Array(),
  Tree_ServerSideActionRunning = false,
  Tree_NextActionID = 0,
  Tree_ActiveNode = "0",
  Tree_RegExpPlus = new RegExp("Plus", "i"),
  Tree_RegExpMinus = new RegExp("Minus", "i"),
  Tree_IdMap = new Object(),
  Tree_Restrict = null;
  
var Tree_XmlHttpRequest = null;
var Tree_Interval = window.setInterval("Tree_Internal_OnInterval()", 1000);

function Tree_GetLink(idPath)
{
  var root = document.getElementById("TreeNode" + idPath);
  if (root==null)
    return null;
  var elems = root.getElementsByTagName("A");
  for (var i=0; i<elems.length; i++)
    if (elems[i].getAttribute("treeLink")!=null)
      return elems[i];
  return null;
}

function Tree_GetLinkText(idPath)
{
  var root = document.getElementById("TreeNode" + idPath);
  if (root==null)
    return null;
  var elems = root.getElementsByTagName("SPAN");
  for (var i=0; i<elems.length; i++)
    if (elems[i].getAttribute("name")=="TreeLinkText")
      return elems[i];
  return null;
}

function Tree_GetDescendants(idPath)
{
  var root = document.getElementById("TreeBlock" + idPath);
  if (root==null)
    return null;
  return root.cells[1];
}

function Tree_SwitchImage(idPath, toPlus)
{
  var root = document.getElementById("TreeNode" + idPath);
  if (root==null)
    return;
  var img1 = null;
  var img2 = null;
  var elems = root.getElementsByTagName("IMG");
  for (var i=0; i<elems.length; i++) {
    if (elems[i].name=="TreeSwitch")
      img1 = elems[i];
    if (elems[i].name=="TreeSwitchMinus")
      img2 = elems[i];
  }
  if (img1)
    img1.style.display = toPlus ? "" : "none";
  if (img2)
    img2.style.display = toPlus ? "none" : "";
}

function Tree_Stop()
{
  if (Tree_Actions.length!=0) {
    for (var i = 0; i<Tree_Actions.length; i++) {
      var id = Tree_Actions[i].NodeID;
      var elm = document.getElementById("TreeBlock" + id);
      var dsc = Tree_GetDescendants(id);
      if (elm==null || dsc==null)
        continue;
      elm.Loaded  = null;
      elm.style.display = "none";
      Tree_SwitchImage(id, true);
      dsc.innerHTML = "<span class=Tree_Loading>"+loadingMessage+"<br></span>";
    }
  }
  Tree_ServerSideActionRunning = false;
  Tree_Actions = new Array();
}

function Tree_Refresh(nodeToRefresh, nodeToActivate, open)
{
  Tree_Stop();
  if (!nodeToRefresh)
    nodeToRefresh = "-1";
  var refreshElm = document.getElementById("TreeBlock" + nodeToRefresh);
  var refreshOpen = refreshElm && refreshElm.style.display!="none";

  var id   = nodeToActivate ? nodeToActivate : Tree_ActiveNode;
  var elm  = document.getElementById("TreeBlock" + id);
  if (!open && open!==false)
    open = elm && elm.style.display!="none";
  Tree_RefreshNode(nodeToRefresh, refreshOpen);
  Tree_NodeAction(id, open, true, false);
}

function Tree_RefreshNode(nodeToRefresh, open)
{
  if (!open && open!==false) {
    var elm  = document.getElementById("TreeBlock" + nodeToRefresh);
    open = elm && elm.style.display!="none";
  }
  Tree_Internal_UnloadNode(nodeToRefresh);
  if (open) {
    Tree_Internal_AddGetChildrenAction(nodeToRefresh);
    Tree_OpenNode(nodeToRefresh, true)
  }
}

function Tree_SwitchNode(id)
{
  var elm = document.getElementById("TreeBlock" + id);
  if (elm==null)
    return;
  if (elm.AlwaysOpen) {
    elm.style.display = "none";
    Tree_SwitchImage(id, false);
    return;
  }
  if (elm.style.display=="none") {
    elm.style.display = "block";
    Tree_SwitchImage(id, false);
    if (elm.Loaded==null && elm.Loading==null) {
      elm.Loading = true;
      Tree_Internal_UnloadNode(id);
      Tree_Internal_AddGetChildrenAction(id);
      Tree_Internal_RunActions();
    }
  }
  else {
    elm.style.display = "none";
    Tree_SwitchImage(id, true);
  }
}

function Tree_OpenNode(id, skipLoad)
{
  Tree_NodeAction(id, true, false, skipLoad)
}

function Tree_ActivateNode(id, skipLoad)
{ 
  Tree_NodeAction(id, false, true, skipLoad)
}

function Tree_OpenAndActivateNode(id, skipLoad)
{
  Tree_NodeAction(id, true, true, skipLoad)
}

function Tree_NodeAction(id, open, activate, skipLoad)
{
  var node = Tree_GetLink(id);
  var elm  = document.getElementById("TreeBlock" + id);
  if (node==null && !skipLoad) {
    var pa = Tree_Internal_AddEnsureNodeLoadedAction(id);
    var na;
    if (open) {
      na = Tree_Internal_AddOpenNodeAction(id);
      if (pa!=null)
        na.RunAfter = [pa.ActionID];
    }
    if (activate) {
      na = Tree_Internal_AddActivateNodeAction(id);
      if (pa!=null)
        na.RunAfter = [pa.ActionID];
    }
    Tree_Internal_RunActions();
    return;
  }
  while (node==null) {
    var ind = id.lastIndexOf("_");
    if (ind==-1)
      return;
    id = id.substr(0, ind);
    node = Tree_GetLink(id);
    if (node) {
      elm  = document.getElementById("TreeBlock" + id);
      break;
    }
  }
  if (activate) {
    var onode = Tree_GetLink(Tree_ActiveNode);
    if (onode!=null)
      Tree_GetLinkText(Tree_ActiveNode).className = "Tree_InactiveNode";
    Tree_Internal_OpenNodeParents(node);
    Tree_GetLinkText(id).className = "Tree_ActiveNode";
    if (Tree_ActiveNode!=id) {
      try {
        node.focus();
      }
      catch(e) {}
      Tree_ActiveNode = id;
    }
  }
  if (open && elm!=null) {
    if (!activate)
      Tree_Internal_OpenNodeParents(elm);
    if (elm.style.display=="none")
      Tree_SwitchNode(id);
  }
}

function Tree_LoadAdditionalChildren(e, currentCount)
{
  var elem = util.GetEventSrcElement(e); // event.srcElement;
  while (elem && elem.tagName!="TABLE")
    elem = util.GetParentElement(elem);
  if (!elem)
    return;
  while (elem && !(elem.id && elem.id.substr(0, 9)=="TreeBlock"))
    elem = util.GetParentElement(elem);
  if (!elem)
    return;
  var idPath = elem.id.substr(9);
  var action = Tree_Internal_AddGetChildrenAction(idPath);
  action.CurrentCount = currentCount;
  action.EnsureChild = Tree_ActiveNode;
  Tree_Internal_RunActions();
}

// Internal methods

function Tree_Internal_MergeTables(table, newTableHTML)
{
  var activeNode = Tree_ActiveNode;
  
  var parent = util.GetParentElement(table);
  table = parent.removeChild(table);
  parent.innerHTML = newTableHTML;
  
  for (var i=table.rows.length-1; i>=0; i--) {
    var row = table.rows[i];
    var rowId = row.id;
    if (rowId && rowId.indexOf("TreeBlock")==0) {
      var newRow = document.getElementById(rowId);
      if (newRow) {
        var tbody = util.GetParentElement(newRow);
        row = util.GetParentElement(row).removeChild(row);
        row = tbody.insertBefore(row, newRow);
        tbody.removeChild(newRow); //newRow.removeNode(true);
        if (row.rowIndex== util.GetParentElement(util.GetParentElement(row)).rows.length-1)
          row.cells[0].background = null;
      }
    } else if (rowId && rowId.indexOf("TreeNode")==0) {
      var newRow = document.getElementById(rowId);
      if (newRow) {
        var displayTreeSwitch = "";
        var displayTreeSwitchMinus = "none";
        var imgs = row.cells[0].getElementsByTagName("IMG");
        for (var j=0; j<imgs.length; j++) {
          if (imgs[j].name=="TreeSwitch")
            displayTreeSwitch = imgs[j].style.display;
          else if (imgs[j].name=="TreeSwitchMinus")
            displayTreeSwitchMinus = imgs[j].style.display;
        }
        var imgs = newRow.cells[0].getElementsByTagName("IMG");
        for (var j=0; j<imgs.length; j++) {
          if (imgs[j].name=="TreeSwitch")
            imgs[j].style.display = displayTreeSwitch;
          else if (imgs[j].name=="TreeSwitchMinus")
            imgs[j].style.display = displayTreeSwitchMinus;
        }
      }
    }
  }
  
  // FIXME: why to remove table here? it has been removed in the beginning of function.
  //table.removeNode(true); 
  
  Tree_ActivateNode(activeNode, true);
  return;
}

function Tree_Internal_OnResponse(s)
{
  if (s.indexOf("<sync")==0) {
    var vidIndex = s.indexOf('versionId="');
    if (vidIndex==-1)
      return;
    var lastQuotIndex = s.indexOf('"', vidIndex+11);
    if (lastQuotIndex==-1)
      return;
    var versionId = s.substring(vidIndex+11, lastQuotIndex);
    if (versionId!=null && versionId!="" && versionId!=parent.GetVersionID())
      parent.OnSync(versionId);
    return;
  }
  
  var preString = '<actions id="';
  var si = s.indexOf(preString);
  if (si==-1)
    return;
  si += preString.length;
  var ei = s.indexOf('"', si);
  var idString = s.substring(si, ei);
  var ids = idString.split(',');
  
  preString = '<response id="';
  postString = '</response>';
  var currentPreString;
  var response;
  var action;
  for(var i=0; i<ids.length; i++) {
    currentPreString = preString+ids[i];
    si = s.indexOf(currentPreString, ei);
    if (si==-1)
      break;
    si += currentPreString.length + 2; // 2 = '">'.length;
    ei = s.indexOf(postString, si);
    if (ei==-1)
      break;
    response = s.substring(si, ei);
    ei += postString.length;
    
    action = Tree_Internal_GetAction(ids[i]);
    if (action) {
      action.RunningOnServer = false;
      action.Response = response;
    }
  }
  Tree_ServerSideActionRunning = false;
  Tree_Internal_RunActions();
}

function Tree_Internal_OpenNodeParents(elm)
{
  elm = elm.parentNode;
  while (elm!=null) {
    if (elm.id!=null && elm.id.toString().substring(0,9)=="TreeBlock") {
      var id  = elm.id.substring(9);
      if (elm.style.display=="none") {
        elm.style.display = "block";
        Tree_SwitchImage(id, false);
      }
    }
    elm = elm.parentNode;
  }
}

function Tree_Internal_UnloadNode(id)
{
  Tree_RemoveIdMap(id);
  var elm = document.getElementById("TreeBlock" + id);
  var dsc = Tree_GetDescendants(id);
  if (dsc==null)
    return;
  elm.Loaded  = null;
  dsc.innerHTML = "<span class=Tree_Loading>"+loadingMessage+"<br></span>";
}

function Tree_Internal_GetActionIndex(actionId)
{
  for (var i = 0; i<Tree_Actions.length; i++) {
    var a = Tree_Actions[i];
    if (a.ActionID==actionId)
      return i;
  }
  return -1;
}

function Tree_Internal_GetAction(actionId)
{
  var i = Tree_Internal_GetActionIndex(actionId);
  if (i>=0)
    return Tree_Actions[i];
  else
    return null;
}

function Tree_Internal_RemoveAction(actionId)
{
  var i = Tree_Internal_GetActionIndex(actionId);
  if (i<0)
    return;
  for (var k = i; k<Tree_Actions.length-1; k++)
    Tree_Actions[k] = Tree_Actions[k+1];
  Tree_Actions.length = Tree_Actions.length-1;
}

function Tree_Internal_AddNodeAction(id)
{
  var action = {}
  action.ActionType = "None";
  action.ActionID   = Tree_NextActionID++;
  action.NodeID     = id;
  Tree_Actions[Tree_Actions.length] = action;
  return action;
}

function Tree_Internal_AddOpenNodeAction(id)
{
  var action = {}
  action.ActionType   = "OpenNode";
  action.ActionID     = Tree_NextActionID++;
  action.NodeID       = id;
  Tree_Actions[Tree_Actions.length] = action;
  return action;
}

function Tree_Internal_AddActivateNodeAction(id)
{
  var action = {}
  action.ActionType   = "ActivateNode";
  action.ActionID     = Tree_NextActionID++;
  action.NodeID       = id;
  Tree_Actions[Tree_Actions.length] = action;
  return action;
}

function Tree_Internal_AddGetChildrenAction(id)
{
  var action = {}
  action.ActionType   = "GetChildren";
  action.ActionID     = Tree_NextActionID++;
  action.NodeID       = id;
  action.CurrentCount = 0;
  action.EnsureChild  = null;
  Tree_Actions[Tree_Actions.length] = action;
  return action;
}

function Tree_Internal_AddEnsureNodeLoadedAction(idPath)
{
  var ids = idPath.split("_");
  var cp = "";
  var pcp = "-1";
  var paction = null;
  for (var i=0; i<ids.length; i++) {
    cp += (i==0 ? "" : "_") + ids[i];
    if (document.getElementById("TreeNode" + cp)==null) {
      var action = Tree_Internal_AddGetChildrenAction(pcp);
      action.ReloadMode = true;
      action.EnsureChild = idPath;
      if (paction!=null)
        action.RunAfter = [paction.ActionID];
      paction = action;
    }
    pcp = cp;
  }
  return paction;
}

function Tree_Internal_EvaluateEnsureChildId(parentId, nodeId)
{
  var tmp = parentId + "_";
  var tmpLength = tmp.length;
  if (nodeId.substr(0, tmpLength)==tmp) {
    var id = nodeId.substr(tmpLength);
    var pos = id.indexOf("_");
    if (pos>0)
      id = id.substr(0, pos);
    return id;
  }
}

function Tree_Internal_RunActions()
{
  if (Tree_ServerSideActionRunning)
    return;
  var finished = false;
  var query = "";
  while (!finished) {
    finished = true;
    var i = 0;
    while (i<Tree_Actions.length) {
      var a = Tree_Actions[i];
      if (a.RunAfter!=null) {
        var canRun = true;
        for (var k = 0; k<a.RunAfter.length; k++) {
          var raActionId = a.RunAfter[k];
          if (Tree_Internal_GetAction(raActionId)!=null) {
            canRun = false;
            break;
          }
        }
        if (!canRun) {
          i++;
          continue;
        }
        else 
          a.RunAfter = null;
      }
      switch (a.ActionType) {
      case "GetChildren":
        if (a.RunningOnServer==null) {
          query += "&action="+a.ActionType+","+a.ActionID+","+a.NodeID;
          var exParams = "";
          if (a.CurrentCount && a.CurrentCount>0) exParams += ",oldCount="+a.CurrentCount;
          if (a.EnsureChild) {
            var ensureChildId = Tree_Internal_EvaluateEnsureChildId(a.NodeID, a.EnsureChild);
            if (ensureChildId && ensureChildId>0)
              exParams += ",ensureChild="+ensureChildId;
          }
          if (Tree_Restrict) exParams += ",restrict=" + Tree_Restrict;
          if (exParams.length>0)
            query += encodeURIComponent(exParams);
          a.RunningOnServer = true;
        }
        else if (a.RunningOnServer==false) {
          if (Tree_Internal_RunAction(a)) {
            Tree_Internal_RemoveAction(a.ActionID);
            finished = false;
          }
          else
            i++;
        }
        else 
          i++;
        break;
      default:
        if (Tree_Internal_RunAction(a)) {
          Tree_Internal_RemoveAction(a.ActionID);
          finished = false;
        }
        else
          i++;
        break;
      }
    }
  }
  if (query!="") {
    Tree_ServerSideActionRunning = true;
    var versionID = (parent!=null && parent.GetVersionID) ? parent.GetVersionID() : null;
    var url = "TreeActionServer.aspx";
    url = (versionID!=null) ? url + "?__versionId=" + versionID +"&a" :
      url + "?a";
    url += query;

    Tree_XmlHttpRequest = util.CreateXmlHttpRequest();
    Tree_XmlHttpRequest.open("GET", url, true);
    Tree_XmlHttpRequest.onreadystatechange = function() {
      if (Tree_XmlHttpRequest.readyState==4 && Tree_XmlHttpRequest.responseText)
        Tree_Internal_OnResponse(Tree_XmlHttpRequest.responseText);
    }
    Tree_XmlHttpRequest.send(null);
  }
}

function Tree_Internal_RunAction(action)
{
  var canRemove = true;
  switch (action.ActionType) {
  case "GetChildren":
    var elm = document.getElementById("TreeBlock"+action.NodeID);
    var dsc = Tree_GetDescendants(action.NodeID);
    if (elm!=null && dsc!=null) {
      if (action.ReloadMode || elm.Loaded==null) {
        elm.Loaded = true;
        elm.Loading = null;
        dsc.innerHTML = action.Response;
        var ao = true;
        
      var children = util.GetChildren(dsc);
      if (children != null)
        for (var i=0; i<children.length; i++) {
          if (children[i].tagName=="TABLE") {
            ao = false;
            break;
          }
        }
        if (ao) {
          dsc.innerHTML = "";
          elm.AlwaysOpen = true;
          Tree_SwitchNode(action.NodeID);
        }
      } else if (action.CurrentCount && action.CurrentCount!=0){
        var children = util.GetChildren(dsc);
        for (var i=0; i<children.length; i++) {
          var child = children[i];
          if (child.tagName=="TABLE") {
            Tree_Internal_MergeTables(child, action.Response);
            break;
          }
        }
      }
    }
    else {
      //throw "Invalid operation";
    }
    if (action.Response)
      action.Response = null;
    break;
  case "GetParents":
    if (action.RunAfter==null)
      action.RunAfter = new Array();
    var enla = Tree_Internal_AddEnsureNodeLoadedAction(action.NodeID);
    if (enla!=null)
      action.RunAfter[action.RunAfter.length] = enla.ActionID;
    if (action.RunAfter.length==0)
      action.RunAfter = null;
    else
      canRemove = false;
    break;
  case "ActivateNode":
    Tree_ActivateNode(action.NodeID, true);
    break;
  case "OpenNode":
    Tree_OpenNode(action.NodeID, true);
    break;
  }
  return canRemove;
}

function Tree_Internal_OnInterval()
{
  if (Tree_ServerSideActionRunning)
    return;
  if (Tree_Actions.length!=0)
    Tree_Internal_RunActions()
  else { // fTreeResponseFrame
  }
}

function Tree_AddIdMap(id, idPath)
{
  if (Tree_IdMap[id]==null)
    Tree_IdMap[id] = new Array();
  Tree_IdMap[id].push(idPath);
}

function Tree_RemoveIdMap(idPath)
{
  var idPathPrefix = idPath + "_";
  var len = idPathPrefix.length;
  for (prop in Tree_IdMap) {
    var map = Tree_IdMap[prop];
    var newMap = new Array();
    for (var i=0; i<map.length; i++) {
      var str = map[i];
      if (str.substring(0, len)!=idPathPrefix)
        newMap.push(str);
    }
    Tree_IdMap[prop] = newMap;
  }
}

function Tree_OnSelectNode(e)
{
  var node = util.GetEventSrcElement(e);
  while (node!=null && node.id.toString().substr(0, 8)!="TreeNode")
    node = node.parentNode;
  if (node==null)
    return;
  var idPath = node.id.toString().substr(8);
  Tree_ActivateNode(idPath, false);
  var allowed = node.NotAllowed==null;
  if (parent!=null && parent.Tree_OnSelect!=null)
    parent.Tree_OnSelect(node.getAttribute("targetID") /* idPath */, allowed, idPath);
}

function Tree_OnSwitchNode(e)
{
  var node = util.GetEventSrcElement(e);
  while (node!=null && node.id.toString().substr(0, 8)!="TreeNode")
    node = node.parentNode;
  if (node==null)
    return;
  var idPath = node.id.toString().substr(8);
  Tree_SwitchNode(idPath);
}

function Tree_UpdateNodeContent(idPath)
{
  var elm  = document.getElementById("TreeBlock" + idPath);
  var open = elm && elm.style.display!="none"
  if (elm)
    elm.AlwaysOpen = false;
  Tree_Internal_UnloadNode(idPath);
  if (open)
    Tree_SwitchNode(idPath);
//  Tree_NodeAction(idPath, open, false, false);
}

function Tree_OnObjectsChanged(changed, contentChanged, removed)
{
  var activeNode = Tree_ActiveNode;
  for (var i=0; i<changed.length; i++) {
    var list = Tree_IdMap[changed[i].toString()];
    if (list!=null && list.length>0) {
      var idPath = list[0];
      idPath = idPath.substr(0, idPath.lastIndexOf("_"));
      if (!idPath)
        continue;
      var treeNode = document.getElementById("TreeNode"+idPath);
      if (!treeNode)
        continue;
      var parentId = treeNode.targetID;
      contentChanged.push(parentId);
    }
  }
  for (var i=0; i<removed.length; i++) {
    var list = Tree_IdMap[removed[i].toString()];
    if (list!=null && list.length>0) {
      var idPath = list[0];
      idPath = idPath.substr(0, idPath.lastIndexOf("_"));
      if (!idPath)
        continue;
      var treeNode = document.getElementById("TreeNode"+idPath);
      if (!treeNode)
        continue;
      var parentId = treeNode.targetID;
      contentChanged.push(parentId);
    }
  }
  var hash = {};
  for (var i=0; i<contentChanged.length; i++) {
    var list = Tree_IdMap[contentChanged[i]];
    if (list!=null) {
      for (var j=0; j<list.length; j++) {
        var newHash = {};
        var idPath = list[j];
        var bAdd = true;
        for (prop in hash) {
          if (prop.substr(0, idPath.length+1)!=idPath+"_")
            newHash[prop] = true;
          if (idPath.substr(0, prop.length+1)==prop+"_")
            bAdd = false;
        }
        if (bAdd)
          newHash[idPath] = true;
        hash = newHash;
      }
    }
  }
  for (prop in hash)
    Tree_UpdateNodeContent(prop);
  Tree_OpenAndActivateNode(activeNode, false);
}

