function Navigation (element) {
  if (!element) return;
	
	this.container = (element.nodeName.toLowerCase() == 'ul') ? element : element.getElementsByTagName('ul')[0];

  // Navigation Settings
  this.defaultHeight = this.container.parentNode.offsetHeight; // same as the containing div
  this.DELAY = 400;
  
  this.currentHeight = this.defaultHeight;
  
  var navigation = this;
  var openItem = null;
  
  this.state = this.CLOSED;
	
	this.createSlide();
  
  // Navigation behaviors
  var closeMenu = function (evt) {    
    navigation.handleEvent(evt, this);
    if (this.menuitem || !navigation.isChild(evt.relatedTarget)) $(navigation.openItem).removeClass('open');
  };
  var openMenu = function (evt) {
    if (navigation.openItem) $(navigation.openItem).removeClass('open');
    navigation.openItem = this;
    
    if (this.menuitem.submenu) $(this).addClass('open');
    navigation.handleEvent(evt, this);
  };
  var openSubMenu = function (evt) {
    $(this).addClass('open');
    //adjust manupanel
    navigation.handleEvent(evt, this);
  };
  var closeSubMenu = function (evt) {
    $(this).removeClass('open');
    // check 4 lost events
    if (!navigation.isChild(evt.relatedTarget)) navigation.handleEvent(evt, this);
  };
  
  // get nodes
  EventListener.addEvent(this.container, 'mouseout', closeMenu);
  var items = this.container.getElementsByTagName('li');
  
  // hook up behaviors
  for (var i = 0, j = items.length; i < j; i++) {
    $(items[i]).removeClass('sfHover');
    
    items[i].menuitem = {};
    if (this.hasSubmenu(items[i])) {
      items[i].menuitem.submenu = items[i].getElementsByTagName('ul')[0];
      items[i].menuitem.height =  this.calculateMenuHeight(items[i].menuitem.submenu);
    } else {
      items[i].menuitem.submenu = null;
      items[i].menuitem.height = this.defaultHeight;
    }
  
    if (items[i].parentNode == this.container) {
      // level 0 (top-level li's)
      EventListener.addEvent(items[i], 'mouseover', openMenu);
    } else if (items[i].parentNode.parentNode.parentNode == this.container) {
      // level 1
      EventListener.addEvent(items[i], 'mouseover', openSubMenu);
      EventListener.addEvent(items[i], 'mouseout', closeSubMenu);
    }
  }
  
  // recalculate the height
  for (var i = 0, j = items.length; i < j; i++) {
    if (items[i].parentNode == this.container && this.hasSubmenu(items[i])) {
      this.recalculateMenuHeight(items[i].menuitem);
    }
  }
}

  Navigation.prototype = {
    // States
    OPENED:    0,
    CLOSED:    1,
    PENDING:   2,
    ANIMATING: 3,

    handleEvent: function (evt, source) {
      evt.stopPropagation();
    
      var destination = evt.relatedTarget;
      if (!destination) return;
      
      var newItem = source.menuitem;
      if (this.isChild(destination) && !newItem) {
        return;
      }
      
      // get level
      var el = source;
      if (el && !el.level) el.level = this.getDepth(el);
      
      if (el.level > 1) {
        return;
      }
			
      switch (evt.type) {
        case 'mouseover':
          switch (this.state) {
            case this.PENDING:
              this.activeItem = newItem;
            break;
            case this.ANIMATING:
            case this.OPENED:
              if (newItem != this.activeItem) {
                this.activeItem = newItem;
                this.openMenu();
              }
            break;
            case this.CLOSED:
              this.pending = setTimeout(this.bind(this.openMenu), this.DELAY);
              this.activeItem = newItem;
              this.state = this.PENDING;
            break;
          }
        break;
        case 'mouseout':
          switch (this.state) {
            case this.PENDING:
              clearTimeout(this.pending);
              this.activeItem = null;
              this.state = this.CLOSED;
            break;
            case this.ANIMATING:
            case this.OPENED:
              this.closeMenu();
            break;
          }
        break;
      }
    },
		
		createSlide: function() {
      this.slide = this.container.parentNode.appendChild(document.createElement('div'));
      this.slide.className = 'slide';
			this.slide.style.top = this.defaultHeight + 'px';
      this.slide.appendChild(document.createElement('div'));
    },
		
		showSlide: function(display) {
      if (display) {
        this.slide.style.display = 'block';
      } else {
        this.slide.style.display = 'none';
      }
    },

    openMenu: function () {
      if (this.animator) {
        this.animator.stop();
      }
      
      var newHeight = this.activeItem.height;
			
			// anything to do?
			if (this.currentHeight == newHeight) {
				this.opened();
				return;
			}
    
      this.state = this.ANIMATING;
			this.showSlide(true);
      this.animator = new Animator(this.currentHeight, newHeight, this.bind(this.animate), this.bind(this.opened));
      this.animator.start();
    },

    opened: function () {
      this.state = this.OPENED;
      this.animator = null;
			
			if (this.currentHeight == this.defaultHeight) {
				$(this.activeItem).removeClass('open');
			  this.showSlide(false);
			}
    },

    closeMenu: function () {
      if (this.animator) {
        this.animator.stop();
      }
			
			// anything to do?
			if (this.currentHeight == this.defaultHeight) {
				this.closed();
				return;
			}
    
      this.state = this.ANIMATING;
			this.showSlide(true);
      this.animator = new Animator(this.currentHeight, this.defaultHeight, this.bind(this.animate), this.bind(this.closed));
      this.animator.start();
    },

    closed: function () {
			if (this.currentHeight == this.defaultHeight) {
			  this.showSlide(false);
			}
			
      this.state = this.CLOSED;
      this.activeItem = null;
      this.animator = null;
    },

    animate: function (x) {
      this.currentHeight = x;
      this.container.style.height = x + 'px';
			this.slide.style.height = (x - this.defaultHeight) + 'px';
    },

    calculateMenuHeight: function (list) {
      var total = 0;
      var items = list.getElementsByTagName('a');
      for (var i=0; i < items.length; i++) {
        if (items[i].parentNode.parentNode == list)
          total += 27;
      }
      total += 28; // Add extra padding
      return total+27;
    },

    recalculateMenuHeight: function (list) {
      var items = list.submenu.getElementsByTagName('li');
      var level1items = 0;
      for (var i=0; i < items.length; i++) {
        if (items[i].parentNode == list.submenu) {
          if ((level1items*27) + items[i].menuitem.height > list.height-27)
            items[i].menuitem.height = (level1items*27) + items[i].menuitem.height;
          else
            items[i].menuitem.height = list.height;
          level1items += 1;
        }
      }
    },
    
    hasSubmenu: function(element) {
      return ((typeof(element.hasSubMenu) === 'undefined') ? (element.hasSubMenu = (element.getElementsByTagName('li').length > 0)) : element.hasSubMenu);
    },

    isChild: function(node) {
      while (node && node != this.container.parentNode) {
        if (node == this.container) return true;
        try {
          node = node.parentNode;
        } catch (exc) { 
          return false;
        }
      }
      
      return false;
    },
    
    getParent: function(element, nodeName, level) {
      if (!element) return null;
    
      var parent = null;
      level = (!level || isNaN(level)) ? 99999 : level+1;
      nodeName = nodeName || element.nodeName;
      nodeName = nodeName.toLowerCase();
        
      try {
        var i = 0;
        var node = element.parentNode;
        
        while (node && node != this.container && i < level) {
          if (node.nodeName.toLowerCase() == nodeName) {
            parent = node;
          }
          
          ++i;
          node = node.parentNode;
        }
      } catch (exc) {
        return null;
      }
      
      return parent;
    },
    
    getDepth: function(element) {
      if (!element) return -1;

      var depth = 0;
      
      try {
        var node = element.parentNode;
        var nodeType = element.nodeName.toLowerCase();

        while (node && node != this.container) {
          if (node.nodeName.toLowerCase() == nodeType) {
            ++depth;
          }
          
          node = node.parentNode;
        }
      } catch (exc) {
        return -1;
      }
      
      return depth;
    },
    
    bind:function(obj) {
      var method = this;
      return function() { obj.apply(method, arguments); }
    }
  }
