/*
 * RSTree
 *
 * -> tree constructor:
 *    - icons open/closed
 *    - padding-left
 * -> RSTree.addNode( node, parent )
 *
 * -> node constructor:
 *    - id
 *    - text
 *    - link (href)
 *    - title
 *
 *
 */

function RSTreeException( message, code ) {
  this.name = "RSTreeException";
  this.message = message;
  this.code = code;
  this.toString = function () { return this.code + ": " + this.message };
}

function RSTreeNode( theTree, id, parentId, text, href, title, html ) {
  // inherent params
  this.level      = null; // undefined
  this.isOpen     = 0;    // 0 = closed (collapsed), 1 = open (expanded)
  this.childIds   = new Array();
  this.icon       = null;
  this.iconOpen   = null;
  this.iconClosed = null;
  this.iconLeaf   = null;
  // copied params
  this.theTree    = theTree;
  this.id         = id;
  this.href       = href;
  this.title      = title;
  this.text       = text;
  this.parentId   = parentId;
  this.html       = html;

  var self = this;
  // methods
  this.expand = function(evt) {
    if( 0 == self.childIds.length ) {
      throw new RSTreeException( self.id, "E_NODE_NO_CHILDREN" );
    }
    if( self.isOpen ) {
      self.domContainer.style.display = "none";
      if( self.icon ) {
        self.icon.src = self.iconClosed || self.theTree.iconClosed;
      }
      self.isOpen = 0;
      return false;
    }
    self.domContainer.style.display = "block";
    if( self.icon ) {
      self.icon.src = self.iconOpen || self.theTree.iconOpen;
    }
    self.isOpen = 1;
    evt = evt || window.event;
    evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;
    return false;
  };

  this.getIcon = function( hasChildNodes, isExpanded ) {
    var hIcon;
    /*
    var imgs = this.domNode.getElementsByTagName("IMG");
    if ( imgs.length > 0 ) {
      hIcon = imgs[0];
    }
    */
    var src;
    if( ! hasChildNodes ) {
      this.domNode.className = "rs-tree-leaf";
      src = this.iconLeaf || this.theTree.iconLeaf;
    } else {
      this.domNode.className = "rs-tree-node";
      if( isExpanded ) {
        this.isOpen = 1;
        src = this.iconOpen || this.theTree.iconOpen;
      } else {
        this.isOpen = 0;
        src = this.iconClosed || this.theTree.iconClosed;
      }
    }

    if( src ) {
      this.icon = hIcon || new Image();
      this.icon.onclick = this.expand;
      this.icon.src = src;
      this.domNode.appendChild( this.icon );
    }
    return this.icon;
  } // getIcon()

  this.getTitleNode = function( hasChildNodes ) {
    var hTitle;
    /*
    var titles = this.domNode.getElementsByTagName("A");
    if ( titles.length > 0 ) {
      hTitle = titles[0];
    }
    */
    if( this.href ) {
      if ( ! hTitle ) {
        hTitle = document.createElement( "A" );
        this.domNode.appendChild( hTitle );
        hTitle.appendChild( document.createTextNode( this.text ) );
      }
      hTitle.href = this.href;
      this.title && hTitle.setAttribute( "title", this.title );
      if( "#" == this.href && hasChildNodes ) {
        hTitle.onclick = this.expand;
      }
      this.titleNode = hTitle;
    } else {
      this.titleNode = document.createElement( "SPAN" );
      this.titleNode.appendChild( document.createTextNode( this.text ) );
      if ( ! hTitle ) {
        this.domNode.appendChild( this.titleNode );
      } else {
        this.domNode.replaceChild( this.titleNode, hTitle );
      }
    }
    if( this.onmouseover ) {
      this.titleNode.onmouseover = this.onmouseover;
    }
  }

  this.draw = function( parentContainer, isExpanded ) {
    this.domNode = document.createElement( "DIV" );
    var hIcon, hTitle;
    if( this.html ) {
      this.domNode.innerHTML = this.html;
    }

    var hasChildNodes = 0 < this.childIds.length;

    this.getIcon( hasChildNodes, isExpanded );

    this.getTitleNode( hasChildNodes, isExpanded );
   
    if( hasChildNodes ) {
      this.domContainer = document.createElement( "DIV" );
      this.domContainer.className = "rs-tree-container";
      if( ! isExpanded ) {
        this.domContainer.style.display = "none";
      }
      this.domNode.appendChild( this.domContainer );
      isExpanded = this.theTree.initialExpandLevel > this.level;
      for( var i in this.childIds ) {
        this.theTree.getNode( this.childIds[i] ).draw( this.domContainer, isExpanded );
      }
    }
    parentContainer.appendChild( this.domNode );
  }; // draw()

  this.setCurrent = function() {
    var pn = this;
    this.domNode.className += " rs-tree-current";
    this.titleNode.className = "current";
    this.childIds.length > 0 && this.expand({});
    while( pn = this.theTree.getNode( pn.parentId ) ) {
      pn.expand({});
    }
    return this;
  } // setCurrent()
} // RSTreeNode()

function RSTree( treeId, iconOpen, iconClosed, iconLeaf, initialExpandLevel ) {
  this.treeElement = null;
  // preload images
  this.iconOpen   = iconOpen;
  this.iconClosed = iconClosed;
  this.iconLeaf   = iconLeaf;

  (new Image()).src = iconOpen;
  (new Image()).src = iconClosed;
  if( iconLeaf ) {
    (new Image()).src = iconLeaf;
  }
  
  this.nodes = {};
  this.topLevelNodes = new Array();

  this.initialExpandLevel = 0;

  /** Add a Node to the tree
  *
  * @param nodeId int
  * @param parentId int
  * @param text string
  * @param href string
  * @param title string
  * @returns RSTreeNode | null
  * @throws E_NODE_EXISTS, E_NODE_NO_PARENT
  */
  this.addNode = function( nodeId, parentId, text, href, title, html ) {
    if( ( "node"+nodeId ) in this.nodes ) {
      throw new RSTreeException( "node '"+nodeId+"'", "E_NODE_EXISTS" );
    }
    var node = new RSTreeNode( this, nodeId, parentId, text, href, title, html );
    this.nodes[ "node"+nodeId ] = node;
    return node;
  }

  /** Get a Node by id
  *
  * @param id int
  * @returns RSTreeNode | null
  */
  this.getNode = function( id ) {
    if( ("node"+id) in this.nodes ) {
      return this.nodes["node"+id];
    }
    return null;
  }

  /** Draw the tree after adding all nodes or modifying;
  * may also expand the current branch marked by currentId
  *
  * @param currentId int - the branch to expand
  */
  this.draw = function( currentId ) {
    // bind nodes
    for( var i in this.nodes ) {
      var node = this.nodes[i];
      var level;
      if( 0 == node.parentId ) {
        this.topLevelNodes.push( node.id );
        level = 0;
      } else if( ("node" + node.parentId) in this.nodes ) {
        var pn = this.nodes[ "node" + node.parentId ];
        pn.childIds.push( node.id );
        level = pn.level + 1;
      } else {
        this.topLevelNodes.push( node.id );
        level = 0;
//        throw "E_NODE_NO_PARENT";
      }
      node.level = level;
    }

    this.treeElement = document.getElementById( treeId );
    if( ! this.treeElement ) {
      throw "E_NO_TREE_ELEMENT";
    }    
    for( var i in this.topLevelNodes ) {
      this.nodes[ "node"+this.topLevelNodes[i] ].draw( this.treeElement );
    }
    if( currentId && ("node"+currentId) in this.nodes ) {
      this.nodes[ "node"+currentId ].setCurrent();
    }
  } // draw()

  /** Expand the nodes which are nested at level equal to or less than maxLevel, or all if maxLevel == -1
  *
  * @param maxLevel int
  */
  this.expand = function( maxLevel ) {
    if( -1 == maxLevel ) {
      maxLevel = Number.MAX_VALUE;
    }
    for( var i in this.nodes ) {
      var node = this.nodes[ i ];
      if( node.level+1 > maxLevel ) { // +1: allow level 0 nodes to collapse too
        // collapse
        node.isOpen = 1;
      } else {
        // expand
        node.isOpen = 0;
      }
      if( node.childIds.length > 0 ) {
        node.expand({});
      }
    }
  } // expand()

}


