﻿TreeView = function(container, dataSource) {
  this.container = container;
  this.ul = $("<ul class='Root'></ul>")[0];
  $(this.container).append(this.ul);
  this.nodes = [];
  this.elements = [];
  this.dataSource = dataSource;

  $(this.ul).bind("click", this, this.clickHandler);
  $(this.ul).bind("dblclick", this, this.doubleClickHandler);
  $(this.ul).bind("mousedown", this, this.mouseDownHandler);
  $(this.container).bind($.browser.opera ? "keypress" : "keydown", this, this.keydownHandler);

  this.dataSource.get(this, null, function(treeView, node, data) {
    treeView.load(null, data);
  });
}

TreeView.prototype = {
  initialize: function() {
    var element = this.getActiveElement();
    if (element != null)
      try {
      $(element).find("a[class*='NodeAnchor']:first")[0].focus();
    }
    catch (e) { }
  },

  refresh: function(callback) {
    var treeView = this;
    $(this.ul).find("li[nodeId!=null][class*='L0']").each(function(i) {
      var id = $(this).attr("nodeId");
      treeView.removeNode(id);
    });

    this.dataSource.get(this, null, function(treeView, node, data) {
      treeView.load(null, data);
      callback();
    });
  },

  onClick: function(data, fn) {
    $(this).bind("click", data, fn);
  },

  unClick: function(fn) {
    $(this).unbind("click", fn);
  },

  onDoubleClick: function(data, fn) {
    $(this).bind("dblclick", data, fn);
  },

  unDoubleClick: function(fn) {
    $(this).unbind("dblclick", fn);
  },

  onContextMenu: function(data, fn) {
    $(this).bind("contextMenu", data, fn);
  },

  unContextMenu: function(fn) {
    $(this).unbind("contextMenu", fn);
  },

  addNode: function(parentNode, data) {
    if (parentNode != null) {
      if (typeof (parentNode) == "string")
        this.load(parentNode, [data]);
      else
        this.load(parentNode.data.ID, [data]);
    }
    else
      this.load(null, [data]);
  },

  removeNode: function(node) {
    var id;
    var level;
    if (typeof (node) == "string") {
      id = node;
      level = this.nodes[id].level;
    }
    else {
      id = node.data.ID;
      level = node.level;
    }
    var li = this.elements[id];
    var parentLi = null;
    var parents = $(li).parents("li[nodeId!=null]:first");
    if (parents.length > 0)
      parentLi = parents[0];
    if (li == this.getActiveElement()) {
      var result = $(li).prev("li[nodeId!=null]");
      if (result.length > 0)
        this.activateNode($(result[0]).attr("nodeId"), true);
      else if (parentLi != null)
        this.activateNode($(parentLi).attr("nodeId"), true);
    }

    var children = $(li).find("li[nodeId!=null][class*='" + "L" + (level + 1) + "']");
    for (var i = 0; i < children.length; i++)
      this.removeNode($(children[i]).attr("nodeid"));

    $(li).find("a").unbind();

    var parent = li.parentNode;
    parent.removeChild(li);
    this.nodes[id] = null;
    this.elements[id] = null;

    if (parentLi != null && $(parentLi).find("li").length == 0) {
      var result = $(parentLi).find("a[class='Plus']");
      if (result.length > 0) {
        var folderIcon = result[0];
        $(folderIcon).unbind('click', this.onNodeClick);
        $(folderIcon).remove();
      }
    }

    this.updateLastIcon(parentLi, level);
  },

  expandNode: function(node, preloadedData) {
    var id;
    if (typeof (node) == "string")
      id = node;
    else
      id = node.data.ID;
    var li = this.elements[id];

    if (preloadedData == null) {
      if ($(li).attr("loaded") != "true") {
        $(li).attr("loaded", "true");
        $(li).find("a[class*='Plus']").addClass("Loading");
        this.dataSource.get(this, this.nodes[id], function(treeView, node, data) {
          treeView.load(node.data.ID, data);
          $(li).find("a[class*='Plus']").removeClass("Loading");
        });
      }
    }
    else {
      $(li).attr("loaded", "true");
      treeView.load(id, preloadedData);
    }
    $(li).removeClass("Closed").addClass("Opened");
  },

  collapseNode: function(node) {
    var id;
    if (typeof (node) == "string")
      id = node;
    else
      id = node.data.ID;
    var li = this.elements[id];

    $(li).removeClass("Opened").addClass("Closed");
  },

  refreshNode: function(node) {
    var id;
    var level;
    if (typeof (node) == "string") {
      id = node;
      level = this.nodes[id].level;
    }
    else {
      id = node.data.ID;
      level = node.level;
    }

    var li = this.elements[id];
    var children = $(li).find("li[nodeId!=null][class*='" + "L" + (level + 1) + "']");
    for (var i = 0; i < children.length; i++)
      this.removeNode($(children[i]).attr("nodeid"));

    $(li).attr("loaded", "false");
    if ($(li).hasClass("Opened"))
      this.expandNode(id);
  },

  getActiveElement: function() {
    var result = $(this.ul).find("a[class*=Active]:first").parents("li[nodeId!=null]:first");
    if (result.length > 0)
      return result[0];
    else
      return null;
  },

  activateNode: function(node, focus) {
    var id;
    if (typeof (node) == "string")
      id = node;
    else
      id = node.data.ID;

    var li = this.elements[id];

    $(this.ul).find("a[class*=Active]").removeClass("Active");
    var anchor = $(li).find("a[class*='NodeAnchor']:first").addClass("Active");
    if (focus) {
      try {
        anchor[0].focus();
        var container = this.getScrollingContainer(li);
        if (container != null) {

          var delta = $(li).offset().top - $(container).offset().top - parseFloat($(this.ul).css("padding-top"));
          if (delta < 0)
            container.scrollTop += delta;
        }
      }
      catch (e) { }
    }
  },

  getScrollingContainer: function(element) {
    var current = element.parentNode;
    while (current != null && (current.scrollTop == null || current.scrollTop == 0))
      current = current.parentNode;
    return current;
  },

  inactivateNode: function(node) {
    var id;
    if (typeof (node) == "string")
      id = node;
    else
      id = node.data.ID;

    var li = this.elements[id];
    $(li).find("a[class*='NodeAnchor']:first").removeClass("Active");
  },

  getNode: function(value) {
    var id;
    if (typeof (value) == "string")
      id = value;
    else
      id = $(value).attr("nodeId");
    return this.nodes[id];
  },

  getElement: function(value) {
    var id;
    if (typeof (value) == "string")
      id = value;
    else
      id = value.data.ID;
    return this.elements[id];
  },

  getParentNode: function(node) {
    var level;
    var li;
    if (typeof (node) == "string") {
      level = this.nodes[node].level;
      li = this.elements[node];
    }
    else {
      level = node.level;
      li = this.elements[node.data.ID];
    }
    if (level == 0)
      return null;

    var parentLi = $(li).parents("li[nodeId!=null][class*='L" + (level - 1) + "']:first")[0];
    var parentId = $(parentLi).attr("nodeId");
    return this.nodes[parentId];
  },

  getPrevNode: function(node) {
    var id = null;
    if (typeof (node) == "string")
      id = node;
    else
      id = node.data.ID;

    var current = this.elements[id];
    if (current == null || $(current).parents("li[class*='Closed']").length > 0)
      return null;
    else {
      var result = $(current).prev("li[nodeId!=null]");
      if (result.length > 0) {
        var current = result[0];
        while ($(current).hasClass("Opened")) {
          var childs = $(current).find("ul:first").children("li[nodeId!=null]:last");
          if (childs.length > 0)
            current = childs[0];
          else
            break;
        }
        return this.nodes[$(current).attr("nodeId")];
      }
      else {
        result = $(current).parents("li[nodeId!=null]:first");
        if (result.length > 0)
          return this.nodes[$(result[0]).attr("nodeId")];
      }
    }
    return null;
  },

  getNextNode: function(node) {
    var id = null;
    if (typeof (node) == "string")
      id = node;
    else
      id = node.data.ID;

    var current = this.elements[id];
    if (current == null || $(current).parents("li[class*='Closed']").length > 0)
      return null;
    else {
      if ($(current).hasClass("Opened")) {
        var result = $(current).find("li[nodeId!=null]:first");
        if (result.length > 0)
          return this.nodes[$(result[0]).attr("nodeId")];
      }

      result = $(current).next("li[nodeId!=null]");
      if (result.length > 0)
        return this.nodes[$(result[0]).attr("nodeId")];
      else {
        while (true) {
          result = $(current).parents("li[nodeId!=null]:first");
          if (result.length > 0) {
            current = result[0];
            result = $(current).next("li[nodeId!=null]");
            if (result.length > 0)
              return this.nodes[$(result[0]).attr("nodeId")];
          }
          else
            return null;
        }
      }
    }
    return null;
  },

  getChildNodes: function(node) {
    if (typeof (node) == "string")
      var node = this.nodes[node]
    if (node == null)
      return null;
    var level = node.level;
    var li = this.elements[node.data.ID];

    var childElements = $(li).find("li[nodeId!=null][class*='L" + (level + 1) + "']");
    var childNodes = [];
    for (var i = 0; i < childElements.length; i++) {
      var id = $(childElements[i]).attr("nodeId");
      childNodes.push(this.nodes[id]);
    }
    return childNodes;
  },

  load: function(parentId, data) {
    var ul;
    var parentNode = null;
    var length = data.length;
    if (parentId == null)
      ul = this.ul;
    else {
      var li = this.elements[parentId];
      parentNode = this.nodes[parentId];
      var childrenUl = $(li).children("ul");
      if (childrenUl.length > 0)
        ul = childrenUl[0];
      else {
        ul = document.createElement("ul");
        li.appendChild(ul);
        if ($(li).hasClass("Last"))
          $(ul).addClass("Last")
      }
      if (length > 0) {
        var span = li.children[0];
        if ($(span).find("a[class*='Plus']").length == 0)
          this.addFolderIcon(parentNode, li, span);
      }
    }
    var level = parentNode == null ? 0 : parentNode.level + 1;
    for (var i = 0; i < length; i++) {
      var nodeData = data[i];
      if (this.nodes[nodeData.ID] != null)
        continue;
      var li = document.createElement("li");

      var child = new TreeNode(level, nodeData, this);
      this.nodes[nodeData.ID] = child;
      this.elements[nodeData.ID] = li;

      $(li).attr("nodeId", nodeData.ID);
      $(li).addClass("L" + level).addClass("Closed");
      var span = document.createElement("span");
      li.appendChild(span);

      if (nodeData.Folder)
        this.addFolderIcon(child, li, span);

      var hl = document.createElement("i");
      $(hl).addClass("HL");
      span.appendChild(hl);

      var anchor = $("<a class='NodeAnchor' href='#" + nodeData.ID + "' onclick='javascript:return false;'></a>")[0];
      $(anchor).bind(
        "focus",
        { treeView: this, node: nodeData.ID },
        function(event) { event.data.treeView.activateNode(event.data.node, true); });
      if (nodeData.Icon != null) {
        var space = document.createElement("u");
        var b = document.createElement("b");
        $(b).addClass(nodeData.Icon);
        space.appendChild(b);
        anchor.appendChild(space);
      }
      var title = document.createElement("em");
      title.innerHTML = nodeData.Title;
      anchor.appendChild(title);
      span.appendChild(anchor);

      ul.appendChild(li);
    }

    this.updateLastIcon(ul, level);
  },

  onNodeClick: function(event) {
    var node = event.data.node;
    var li = event.data.element;
    var treeView = event.data.treeView;
    if ($(li).hasClass("Opened"))
      treeView.collapseNode(node);
    else
      treeView.expandNode(node);
  },

  addFolderIcon: function(node, li, span) {
    var folderIcon = document.createElement("a");
    $(folderIcon).addClass("Plus");
    $(folderIcon).attr("href", "javascript:;");
    folderIcon.appendChild(document.createElement("b"));
    span.appendChild(folderIcon);

    $(folderIcon).bind('click', { node: node, element: li, treeView: this }, this.onNodeClick);

    $(li).removeClass("Opened").addClass("Closed")
  },

  updateLastIcon: function(parent, level) {
    if (parent == null)
      parent = this.ul;
    $(parent).find("li[nodeId!=null][class*='" + "L" + level + "'][class*='Last']>span").removeClass("LastSpan");
    $(parent).find("li[nodeId!=null][class*='" + "L" + level + "'][class*='Last']").removeClass("Last").children("ul").removeClass("Last");
    $(parent).find("li[nodeId!=null][class*='" + "L" + level + "']:last").addClass("Last").children("ul").addClass("Last");
    $(parent).find("li[nodeId!=null][class*='" + "L" + level + "']:last>span").addClass("LastSpan");
  },

  clickHandler: function(event) {
    event.data.handleEvent(event, "click");
  },

  doubleClickHandler: function(event) {
    event.data.handleEvent(event, "dblclick");
  },

  mouseDownHandler: function(event) {
    if (event.button = 2)
      event.data.handleEvent(event, "contextMenu");
  },

  handleEvent: function(event, eventName) {
    var treeView = event.data;
    var target = event.target;
    if ((target.tagName == "A" && $(target).hasClass("NodeAnchor")) || $(target).parents("a[class*='NodeAnchor']").length > 0) {
      var li = $(target).parents("li[nodeId!=null]:first")[0];
      var node = treeView.getNode(li);
      treeView.activateNode(node.data.ID, true);
      $(treeView).trigger(eventName, node);
    }
  },

  keydownHandler: function(event) {
    var treeView = event.data;
    if (event.keyCode == 38 || event.keyCode == 40) {
      // up || down
      var keyUp = event.keyCode == 38;
      var current = treeView.getActiveElement();
      if (current == null || $(current).parents("li[class*='Closed']").length > 0) {
        var result = $(treeView.ul).find("li[nodeId!=null]:first");
        if (result.length > 0)
          treeView.activateNode($(result[0]).attr("nodeId"), true);
      }
      else {
        var currentNodeId = $(current).attr("nodeId");
        var newNode = keyUp ? treeView.getPrevNode(currentNodeId) : treeView.getNextNode(currentNodeId);
        if (newNode != null)
          treeView.activateNode(newNode.data.ID, true);
      }
      event.preventDefault();
    }
    else if (event.keyCode == 39) {
      // right
      var current = treeView.getActiveElement();
      if (current != null && $(current).parents("li[class*='Closed']").length == 0) {
        var id = $(current).attr("nodeId");
        var node = treeView.nodes[id];
        var li = treeView.elements[id];
        if (node.data.Folder) {
          if ($(li).hasClass("Closed"))
            treeView.expandNode(node);
          else {
            var children = treeView.getChildNodes(node);
            if (children.length > 0)
              treeView.activateNode(children[0], true);
          }
        }
      }
      event.preventDefault();
    }
    else if (event.keyCode == 37 && $(current).parents("li[class*='Closed']").length == 0) {
      // left
      var current = treeView.getActiveElement();
      if (current != null) {
        var id = $(current).attr("nodeId");
        var node = treeView.nodes[id];
        var li = treeView.elements[id];
        var parentNode = treeView.getParentNode(node);
        if (node.data.Folder && !$(li).hasClass("Closed"))
          treeView.collapseNode(node);
        else if (parentNode != null)
          treeView.activateNode(parentNode, true);
      }
      event.preventDefault();
    }
    else if (event.keyCode == 33 || event.keyCode == 34) {
      //pageUp || pageDown
      var pageUp = event.keyCode == 33;
      var current = treeView.getActiveElement();
      if (current == null || $(current).parents("li[class*='Closed']").length > 0) {
        var result = $(treeView.ul).find("li[nodeId!=null]:first");
        if (result.length > 0)
          treeView.activateNode($(result[0]).attr("nodeId"), true);
      }
      else {
        var itemHeight = $(current).find("span:first")[0].scrollHeight;
        if (itemHeight != 0) {
          var container = treeView.getContainer(current);
          var toJump;
          if (container != null) {
            var containerHeight = container.clientHeight;
            var toJump = Math.floor(containerHeight / itemHeight) - 2;
          }
          else
            toJump = 9007199254740992;
          if (toJump > 0) {
            var currentNodeId = $(current).attr("nodeId");
            var newActiveNodeId = currentNodeId;
            while (toJump > 0) {
              var newNode = pageUp ? treeView.getPrevNode(newActiveNodeId) : treeView.getNextNode(newActiveNodeId);
              if (newNode != null)
                newActiveNodeId = newNode.data.ID;
              else
                break;
              toJump--;
            }
            if (newActiveNodeId != currentNodeId)
              treeView.activateNode(newActiveNodeId, true);
          }
        }
      }
      event.preventDefault();
    }
  },

  getContainer: function(element) {
    var current = element.parentNode;
    while (current != null && (current.clientHeight == null || current.clientHeight == 0 || current.clientHeight >= current.scrollHeight))
      current = current.parentNode;
    return current;
  },

  destroy: function() {
    $(this.ul).find("a").unbind();
    $(this.ul).unbind();
    $(this).unbind();
  }
}
