/*
 * Content-seperated javascript tree widget
 * Copyright (C) 2005 SilverStripe Limited
 * Feel free to use this on your websites, but please leave this message in the files
 * http://www.silverstripe.com/blog
 */

/*
 * Ajax-related modifications by Daniel R Somerfield at Outside in
 * Copyright (C) 2007 Outside In
 * http://www.outsidein.org
 */

/*
 * ajaximrpg 5.00
 * AJAX Instant Messenger
 * Copyright (C) 2006-2008, 2010-2012 Daniel Howard
 * Do not remove this notice
 */

/**
 * Tree control.
 */

//TODO: clean up graphics
//TODO: build a tree from scratch
//TODO: load nodes on demand, dynamic tree
//TODO: handle case where label is taller than images

/**
 * A tree control.
 */
function Tree(el) {
   this.el = el;

   /**
    * A tree node inner class.
    */
   this.TreeNode = function() {
      this.closed = false; // if folder, is it open or closed?
      this.el = null; // HTML element
      this.label = 'root'; // HTML for label
      this.object = null; // Tree object that owns this node
      this.parent = null; // TreeNode object that owns this node
      this.tree = []; // children which are TreeNode objects
      /**
       * Return the JSON text for this subtree.
       * 
       * @return The JSON text.
       */
      this.toJSONString = function() {
         // tricky: recursive function local var
         var impl = function(tree) {
            var s = '{';
            var a = { "closed": tree.closed, "label": tree.label };
            var b = a.toJSONString();
            s += b.substring(1, b.length-1)+',"tree":[';
            for (var c=0; c < tree.tree.length; ++c) {
               if (c >= 1) {
                  s += ',';
               }
               s += impl(tree.tree[c]);
            }
            s += ']}';
            return s;
         };
         return impl(this);
      };
      /**
       * Replace this subtree with the tree in the
       * JSON string.
       * 
       * @param s The JSON string.
       */
      this.parseJSON = function(s) {
         var json = s.parseJSON();
         var tree = this.object.jsonNodeToTreeNode(json);
         if (this.parent == null) {
            this.el.tree = tree;
            this.el.innerHTML = this.object.getTreeHtml(this.el.tree);
            this.object.updateTreeElements(this.el, this.el.tree);
         } else {
            var child = -1;
            for (var c=0; c < this.parent.tree.length; c++) {
               if (this.parent.tree[c] == this) {
                  child = c;
               }
            }
            this.parent.tree[child] = tree;
            this.parent.tree[child].parent = this.parent;
            this.parent.el.innerHTML = this.object.getTreeHtml(this.parent);
            this.object.updateTreeElements(this.parent.el, this.parent);
         }
      };
      /**
       * Insert a new label or tree into this subtree.
       * 
       * @param i The index to insert the new label before.
       * @param label The new label.
       */
      this.insert = function(i, label) {
         var node = new this.object.TreeNode();
         node.label = label;
         node.object = this.object;
         node.parent = this;
         this.tree.splice((i == -1)? this.tree.length: i, 0, node);
         if (this.el != null) {
            this.el.innerHTML = this.object.getTreeHtml(this);
            this.object.updateTreeElements(this.el, this);
         }
      };
      /**
       * Remove a child subtree from this subtree.
       * 
       * @param i The index of the subtree to remove.
       */
      this.remove = function(i) {
         this.tree.splice(i, 1);
         if (this.el != null) {
            this.el.innerHTML = this.object.getTreeHtml(this);
            this.object.updateTreeElements(this.el, this);
         }
      };
   };

   /**
    * Create JSON node tree from <ul> element.
    * 
    * JSON node tree is simpler than a TreeNode tree.
    * 
    * @arguments el - The <ul> element.
    * 
    * @author Daniel Howard
    */
   this.ulTagToJsonNode = function(el) {
      var node = { "closed": false, "label": "", "tree": [] };
      if (el.tagName.toLowerCase() == 'ul') {
         var children = el.childElements();
         for (var c=0; c < children.length; ++c) {
            node.tree.push(this.ulTagToJsonNode(children[c]));
         }
      } else if (el.tagName.toLowerCase() == 'li') {
         var ul = null;
         var children = el.childElements();
         for (var c=0; c < children.length; ++c) {
            if (children[c].tagName.toLowerCase() == 'ul') {
               ul = children[c];
               break;
            }
         }
         if (ul != null) {
            node = this.ulTagToJsonNode(ul);
            ul.remove();
            node.closed = (el.className == 'closed')? true: false;
         }
         node.label = el.innerHTML;
      }
      return node;
   };

   /**
    * Create a TreeNode tree from a JSON node tree.
    * 
    * JSON node tree is simpler than a TreeNode tree.
    * 
    * @arguments tree - The JSON node tree.
    * 
    * @author Daniel Howard
    */
   this.jsonNodeToTreeNode = function(tree) {
      var node = new this.TreeNode();
      node.object = this;
      node.label = tree.label;
      node.closed = tree.closed;
      for (var c=0; c < tree.tree.length; ++c) {
         node.tree[c] = this.jsonNodeToTreeNode(tree.tree[c]);
         node.tree[c].parent = node;
      }
      return node;
   },

   /**
    * Get HTML for tree branch with nodes opened/closed.
    * 
    * @arguments node - The node that describes the tree.
    * 
    * @author Daniel Howard
    */
   this.getTreeHtml = function(node) {
      var html = '';
      if (node == node.object.el.tree) {
         for (var i=0; i < node.tree.length; ++i) {
            html += '<span class="treenode">' + this.getTreeHtml(node.tree[i]) + '</span>';
         }
         return html;
      }
      var back = node;
      while (back.parent.parent != null) {
         // do dotted line on the left of this node
         var img = 'i.png';
         if (back.parent.tree[back.parent.tree.length-1] == back) {
            img = 'blank.png';
         }
         // do dotted lines for this node
         if (back == node) {
            if (node.tree.length == 0) {
               if (back.parent.tree[back.parent.tree.length-1] == back) {
                  img = 'l.png';
               } else {
                  img = 't.png';
               }
            } else {
               img = null;
            }
         }
         // create dotted line
         if (img != null) {
            html = '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/'+img+'" />' + html;
         }
         // if right below the root node, do dotted lines for root node
         if (back.parent.parent.parent == null) {
            var img = 'i.png';
            if (this.el.tree.tree[this.el.tree.tree.length-1] == back.parent) {
               img = 'blank.png';
            }
            html = '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/'+img+'" />' + html;
         }
         back = back.parent;
      }

      // add the HTML for the document or plus/minus sign and folder
      if (node.tree.length == 0) {
         html += '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/doc.png" /><span class="treetext">'+node.label+'</span><br />';
      } else if (node.closed) {
         html += '<a style="outline: none;" href="#" onclick="this.parentNode.treeToggle(this.parentNode); return false;">';
         html += '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/plus.png" /></a>';
         html += '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/close.png" />';
         html += '<span class="treetext">'+node.label+'</span><br />';
      } else {
         html += '<a style="outline: none;" href="#" onclick="this.parentNode.treeToggle(this.parentNode); return false;">';
         html += '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/minus.png" /></a>';
         html += '<img style="vertical-align: bottom; border: 0px;" src="scriptaculous/contrib/tree/open.png" />';
         html += '<span class="treetext">'+node.label+'</span><br />';
         // show the children of the open folder
         for (var i=0; i < node.tree.length; ++i) {
            html += '<span class="treenode">' + this.getTreeHtml(node.tree[i]) + '</span>';
         }
      }
      return html;
   };

   /**
    * Add needed data and methods to newly created HTML tree elements.
    * 
    * @arguments el - The element (and children) to update.
    *            node - The node associated with the element.
    * 
    * @author Daniel Howard
    */
   this.updateTreeElements = function(el, node) {
      if (node == node.object.el.tree) {
         for (var i=0; i < node.tree.length; ++i) {
            this.updateTreeElements(el.childElements()[i], node.tree[i]);
         }
      }
      if (el != null) {
         el.treeNode = node;
         el.treeToggle = function(el) {
            el.treeNode.closed = el.treeNode.closed? false: true;
            el.innerHTML = el.treeNode.object.getTreeHtml(el.treeNode);
            el.treeNode.object.updateTreeElements(el, el.treeNode);
         };
      }
      node.el = el;
      var children = [];
      if (el != null) {
         var allChildren = el.childElements();
         for (var c=0; c < allChildren.length; ++c) {
            if (allChildren[c].hasClassName('treenode')) {
               children.push(allChildren[c]);
            }
         }
      }
      for (var i=0; i < node.tree.length; ++i) {
         var childEl = (i < children.length)? children[i]: null;
         this.updateTreeElements(childEl, node.tree[i]);
      }
   };

   // convert the <ul> tag into TreeNode's
   el.tree = this.ulTagToJsonNode(el);
   el.tree = this.jsonNodeToTreeNode(el.tree);
   // create HTML tree elements from data
   el.innerHTML = this.getTreeHtml(el.tree);
   // add new data and methods to newly created HTML tree elements
   this.updateTreeElements(el, el.tree);
   return this;
}

// make all <ul class="tree"> tags into tree controls
document.observe("dom:loaded", function() {
   var uls = $$('ul.tree');
   for (var i=0; i < uls.length; ++i) {
      new Tree(uls[i]);
   }
});

