// TODO: MODIFICARE IL CODICE IN MODO DA NON ASSUMERE CHE LE PROPRIETA' DEGLI
// OGGETTI VENGANO ENUMERATE NELL'ORDINE DI DEFINIZIONE.
//
// L'oggetto JSON 'nodes' che viene passato alla funzione createTree deve essere
// un array { [ node1, node2, ..., nodeN ] } per mantenere l'ordinamento originale
// degli elementi passati al suo interno
//
// Gli oggetti che non sono utilizzati come hash table, in particolare node.children
// devono essere definiti come Array.
//
// Poichè diverse librerie (ad es. prototype) aggiungono attributi ai tipi nativi
// (String, Array, ecc.) NON devono essere utilizzati cicli foreach per le enumerazioni
// degli Array: in caso contrario, oltre agli elementi dell'array, verrebbero enumerate
// anche le proprietà aggiuntive.

String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

var XTreeInstances = {};

/**
 * name : il nome univoco che identifica l'istanza dell'albero
 * options:
 *    checkbox: true/false    abilita/disabilita i checkbox per i nodi dell'albero
 *    showroot: true/false    visualizza/nasconde l'elemento radice predefinito
 */
function XTree (name, options) {

   this.name = name;
   XTreeInstances[name] = this;

   this.images_path = 'img/';
   
   this.showTreeLines = true;
   this.showTreeIcons = true;

   if(options != null) {
      for(var opt in options) {
         this[opt] = options[opt];
      }
   }

   this.root = { id: 0, pid: 0, text: 'root', lastChildId: 0, childrenCount: 0 };

   this.nodes = {};

   this.selectedNode = null;

   this.images = {
      plus: this.images_path + 'plus.gif',
      plus_bottom: this.images_path + 'plus_bottom.gif',
      minus: this.images_path + 'minus.gif',
      minus_bottom: this.images_path + 'minus_bottom.gif',
      folder: this.images_path + 'folder.gif',
      folder_open: this.images_path + 'folder_open.gif',
      default_root: this.images_path + 'default_root.gif',
      vertical_line: this.images_path + 'vertical_line.gif',
      join: this.images_path + 'join.gif',
      join_bottom: this.images_path + 'join_bottom.gif',
      leaf: this.images_path + 'leaf.gif',
      empty: this.images_path + 'empty.gif'      
   };

   var preloaded_images = new Array();
   for(var img in this.images) {
      preloaded_images[img] = new Image();
      preloaded_images[img].src = this.images[img];
   }
}

XTree.prototype.getNodeIconSrc = function (imageName, nodeType) {
   var img = nodeType != null ? nodeType + '_' : '';
   return this.images_path + img + imageName + '.gif';
}

XTree.prototype.createNodeIconElement = function (imageName, imageId, nodeType, customImagePath) {
   var img = '';
   if(customImagePath != null) {
      img = customImagePath;
   }
   else {
      img = nodeType != null ? nodeType + '_' : '';
      img += imageName + '.gif';
   }
   var id = (imageId != null) ? 'id="' + imageId + '" ' : '';
   return '<img ' + id + 'src="' + this.images_path + img + '" alt="" />';
}

XTree.prototype.createIconElement = function (imageName, imageId) {
   var id = (imageId != null) ? 'id="' + imageId + '" ' : '';
   return '<img ' + id + 'src="' + this.images[imageName] + '" alt="" />';
}

XTree.prototype.createToggleIconElement = function (id, bottom) {
   return '<a href="javascript: XTreeInstances[\'' + this.name + '\'].toggleNode(\'' + id + '\');"><img id="j_' + id + '" src="' +
      this.images['plus'+(bottom?'_bottom':'')] +
      '" alt="" /></a>';
}

XTree.prototype.isLastSibling = function (nodeId) {
   if(nodeId == 0) {
      return true;
   }
   else if(this.nodes[nodeId].pid == 0) {
      return (this.root.lastChildId == nodeId);
   }
   var parentId = this.nodes[nodeId].pid;
   return (this.nodes[parentId].lastChildId == nodeId);
}

XTree.prototype.sortChildren = function (parent) {
   var a = new Array();
   var id = 0;
   var children = {};
   
   var nops = new Array(); // possono esistere anche nodi senza ps che verranno semplicemente accodati
   var psNotPresent = new Array(); // il ps di un nodo potrebbe non essere in lista
   /*
    * Nota: utilizzando un ordinamento basato su prev_sibling, il passaggio di un insieme di nodi
    * incompleto impedisce la ricostruzione dell'ordine originale.
    */   
   for(var n in parent.children) {
      if(!this.nodes[n].hasOwnProperty('ps')) {
         nops.push(n);
      }
      else if(parent.children[this.nodes[n].ps] === undefined) {
         psNotPresent.push(n);
      }
   }

   while(a.length < (parent.childrenCount-nops.length)) {
      var found = false;
      for(var n in parent.children) {
         var node = this.nodes[n];
         if(node.ps == id) {
            a.push(n);
            id = n;
            found = true;
            break;
         }
      }
      if(!found) {
         a.push(n);
         id = psNotPresent.pop();
      }
   }
   
   for(var i=0; i<nops.length; i++) {
      a.push(nops[i]);
   }

   for(var i=0; i<a.length; i++) {
       children[a[i]] = this.nodes[a[i]];
   }

   parent.children = children;
   parent.lastChildId = a[a.length-1];
}

XTree.prototype.addNode = function (parentNode, recursedNodes) {
   var parent = (parentNode==0) ? this.root : this.nodes[parentNode];

   // se è definito node.ps (previous sibling), la lista parent.children
   // deve essere ordinata
   if(parent.childrenCount > 1) {
      for(var iter in parent.children) {
         if(parent.children[iter].hasOwnProperty('ps')) {
            this.sortChildren(parent);
            break;
         }
      }
   }

   var c = 0;
   for (var n in parent.children) {
      if(c++ == parent.childrenCount) break;

      var isLastSibling  = this.isLastSibling(n);
      var hasChildren = this.nodes[n].childrenCount > 0;

      for (var j=0; j<recursedNodes.length; j++) {
         document.write(this.createIconElement((recursedNodes[j]?'vertical_line':'empty')));
      }

      recursedNodes.push(!isLastSibling);

      if(this.showTreeLines) {
         if (hasChildren) {
            document.write(this.createToggleIconElement(n, isLastSibling));
         } else {
            document.write(this.createIconElement('join'+(isLastSibling?'_bottom':'')));
         }
      }

      if(this.checkbox) {
         this.nodes[n].was_checked = this.nodes[n].checked; // save original value
         var checked = this.nodes[n].checked ? 'checked="checked"' : '';
         document.write('<input type="checkbox" autocomplete="off" onclick="javascript: XTreeInstances[\'' + this.name + '\'].check(\'' + n + '\');" class="checkbox" id="ck_' + n + '" name="ck_' + n + '" ' + checked + '/>');
      }

      // Start link
      var href = (this.nodes[n].href != null) ? this.nodes[n].href : '#';
      var a_style = (this.nodes[n].color != null) ? 'style="color:' + this.nodes[n].color + ';" ' : '';
      document.write('<a href="' + href + '" id="a_' + n + '" ' + a_style + 'onclick="javascript: XTreeInstances[\'' + this.name + '\'].click(\'' + n + '\');">');

      if(this.showTreeIcons) {
         var customImage = null;
         if(this.nodes[n].icon != null) {
            customImage = this.nodes[n].icon;
         }
         var img = hasChildren ? 'folder' : 'leaf';
         document.write(this.createNodeIconElement(img ,'i_' + n, this.nodes[n].type, customImage));
      }

      document.write(this.nodes[n].text);

      // End link
      document.write('</a><br />');

      // drill
      if (hasChildren) {
         document.write('<div id="d_' + n + '" style="display: none;">');
         this.addNode(n, recursedNodes);
         document.write("</div>");
      }

      recursedNodes.pop();
   }
}

XTree.prototype.createTree = function (nodes, startNode) {
   // nodes ->
   // { nodeId: { pid: parentNodeId, test: nodeText }, ... }

   this.nodes = nodes;

   var length = 0;
   for(var i in this.nodes) {
      /**************** BERETTADOWNLOAD PATCH (the root has parent id = 1) ****************/
      if(this.nodes[i].pid == 1) {
         this.nodes[i].pid = 0;
      }
      
      if(this.nodes[i].pid != 0) {
         if(this.nodes[this.nodes[i].pid] === undefined) {
            continue;
         }
         var parent = this.nodes[this.nodes[i].pid];
         parent.lastChildId = i;
         if(parent.children === undefined) {
            parent.children = {};
            parent.children[i] = this.nodes[i];
            parent.childrenCount = 1;
         }
         else {
            parent.children[i] = this.nodes[i];
            parent.childrenCount++;
         }
      }
      else {
         if(this.root.children === undefined) {
            this.root.children = {};
         }
         this.root.children[i] = this.nodes[i];
         this.root.lastChildId = i;
         this.root.childrenCount++;
      }
      if(this.nodes[i].childrenCount === undefined) {
         this.nodes[i].childrenCount = 0;
      }
      length++;
   }
   this.nodes.length = length;

   if (this.nodes.length > 0) {

      if (startNode == null) {
         startNode = 0;
      }

      if (startNode !=0) {
         document.write('<a href="#" id="a_' + startNode + '">' + this.createIconElement('folder_open') + this.nodes[startNode].text + "</a><br />");
      }
      else if(this.showroot) {
         document.write(this.createIconElement('default_root') + "root<br />");
      }

      var recursedNodes = new Array();
      this.addNode(startNode, recursedNodes);
   }
}

XTree.prototype.isNodeOpened = function (nodeId) {
   var div = document.getElementById('d_' + nodeId);
   return div.style.display != 'none';
}

XTree.prototype.toggleNode = function (nodeId) {
   if (this.isNodeOpened(nodeId)) { this.closeNode(nodeId); }
   else { this.openNode(nodeId); }
}

XTree.prototype.openNode = function (nodeId) {
   if(this.showTreeLines) {
      var join   = document.getElementById('j_' + nodeId);
      var bottom  = this.isLastSibling(nodeId);
      join.src = this.images[(bottom ? 'minus_bottom' : 'minus')];
   }
   
   if(this.showTreeIcons) {
      var icon = document.getElementById('i_' + nodeId);
      icon.src = this.getNodeIconSrc('folder_open', this.nodes[nodeId].type);
   }
   
   var div = document.getElementById('d_' + nodeId);
   div.style.display = '';
}

XTree.prototype.closeNode = function (nodeId) {
   if(this.showTreeLines) {
      var join   = document.getElementById('j_' + nodeId);
      var bottom  = this.isLastSibling(nodeId);
      join.src = this.images[(bottom ? 'plus_bottom' : 'plus')];
   }
   
   if(this.showTreeIcons) {
      var icon = document.getElementById('i_' + nodeId);
      icon.src = this.getNodeIconSrc('folder', this.nodes[nodeId].type);
   }
   
   var div = document.getElementById('d_' + nodeId);
   div.style.display = 'none';
}

XTree.prototype.showNode = function (nodeId) {
   if(this.nodes[nodeId].pid != 0 && !this.isNodeOpened(this.nodes[nodeId].pid)) {
      this.openNode(this.nodes[nodeId].pid);
      this.showNode(this.nodes[nodeId].pid);
   }
}

XTree.prototype.click = function (nodeId) {
   var anchor = document.getElementById('a_' + nodeId);
   var className = anchor.className.trim();
   if(className.indexOf('selected') == -1) {
      /*
      for(var i=0; i<this.selectedNodes.length; i++) {
         var a = document.getElementById('a_' + this.selectedNodes[i]);
         a.className = a.className.replace(/selected/, '').trim();
      }
      this.selectedNodes.length = 0;
      */

      if(this.selectedNode != null) {
         var a = document.getElementById('a_' + this.selectedNode);
         a.className = a.className.replace(/selected/, '').trim();
         this.selectedNode = null;
      }

      anchor.className = className + ' selected';

      //this.selectedNodes.push(nodeId);

      this.selectedNode = nodeId;
      this.showNode(nodeId);

      if(this.nodes[nodeId].childrenCount > 0) {
         this.openNode(nodeId);
      }
      if(this.onSelectNode) {
         this.onSelectNode(nodeId, this.nodes[nodeId].type);
      }
   }
}

XTree.prototype.check = function (nodeId) {
   var ck = document.getElementById('ck_' + nodeId);

   if(ck.checked) {
      var pid = this.nodes[nodeId].pid;
      while(pid != 0) {
         this.setCheck(pid, true);
         pid = this.nodes[pid].pid;
      }

      var hasChildren = this.nodes[nodeId].childrenCount > 0;
      if(hasChildren) {
         if(confirm('Do you want to select all elements in the branch?'/*'Selezionare tutti gli elementi del ramo?'*/)) {
            this.checkChildren(nodeId);
         }
      }
   }
   else if(this.hasCheckedChildren(nodeId)) {
      var hasChildren = this.nodes[nodeId].childrenCount > 0;
      if(hasChildren) {
         if(confirm('All elements in the branch will be unselected.\nContinue?'/*'Tutti gli elementi del ramo saranno deselezionati.\nContinuare?'*/)) {
            this.uncheckChildren(nodeId);
         }
         else {
            this.setCheck(nodeId, true);
            return;
         }
      }
   }

   this.nodes[nodeId].checked = ck.checked;

   if(this.onCheckChanged) {
      this.onCheckChanged();
   }
}

XTree.prototype.hasCheckedChildren = function (nodeId) {
   var node = this.nodes[nodeId];
   if(node.checked)
      return true;

   for(var n in node.children) {
      if(this.hasCheckedChildren(n))
         return true;
   }

   return false;
}

XTree.prototype.checkChildren = function (nodeId) {
   for(var c in this.nodes[nodeId].children) {
      var hasChildren = this.nodes[c].childrenCount > 0;
      if(hasChildren) {
         this.checkChildren(c);
      }
      this.setCheck(c, true);
   }
}

XTree.prototype.uncheckChildren = function (nodeId) {
   for(var c in this.nodes[nodeId].children) {
      var hasChildren = this.nodes[c].childrenCount > 0;
      if(hasChildren) {
         this.uncheckChildren(c);
      }
      this.setCheck(c, false);
   }
}

XTree.prototype.setCheck = function (nodeId, checkState) {
   this.nodes[nodeId].checked = checkState;
   var ck = document.getElementById('ck_' + nodeId);
   ck.checked = checkState;
}

XTree.prototype.getCheckStateChanges = function () {
   var checked = '';
   var unchecked = '';
   for(var n in this.nodes) {
      if(this.nodes[n].was_checked != this.nodes[n].checked) {
         if(this.nodes[n].checked) {
            if(checked.length > 0) { checked += ','; }
            checked += '["' + n + '","' + this.nodes[n].type + '"]';
         }
         else {
            if(unchecked.length > 0) { unchecked += ','; }
            unchecked += '["' + n + '","' + this.nodes[n].type + '"]';
         }
      }
   }
   return '{ "checked" : [' + checked + '], "unchecked" : [' + unchecked + '] }';
}

XTree.prototype.acceptCheckStateChanges = function () {
   for(var n in this.nodes) {
      this.nodes[n].was_checked = this.nodes[n].checked;
   }
   if(this.onAcceptCheckStateChanges) {
      this.onAcceptCheckStateChanges();
   }
}
