/*****************************************************************************************
* COPYRIGHT NOTICE                                                                       *
*                                                                                        *
* This code is (c) 2004 Dupoint AB. All rights reserved. Any unauthorized usage, copying *
* or reverse engineering is strictly prohibited.                                         *
*****************************************************************************************/

/**
* An arbitrary integer signifying 'very far away'.
*
* Also known as 'an item placed here will exist in the document tab order
* even if JavaScript is turned off, but will never be visible unless it is
* moved to a visible position'.
*
* See also the PHP class DynamicMenu, which has the same constant.
*/
var MENU_VERY_FAR_AWAY = -10000;



/**
* This is the internal JavaScript representation of a DynamicMenu.
*
* It will not do much without the DynamicMenuManager, and is mainly
* intended to keep a track of a menu's status.
*/

function DynamicMenu(menuId, settingsArray) {
  this.settings = settingsArray;
  this.menuObject = document.getElementById(menuId);
  this.cssId = menuId;
}

DynamicMenu.prototype = {
  isOpen : false, // True if the menu is open
  settings : null, // Object with all settings as properties
  triggeredByCommand : null, // Which command triggered this menu
  triggeredByEvent : null, // Which event triggered this menu
  mouseIsWithinMenu : false, // True if the mouse pointer is inside the menu
  cssId : null, // CSS id of the menu
  menuObject : null, // Object for the drop-down
  lastMouseDistanceFromSubmenu : null, // (integer) Mouse distance from the edge of sub menu
  submenuReferenceX : null, // (integer) Reference point for mouse distance from sub menu
  allowsMenuEvents : true, // Whether or not the menu accepts menu events
  focusCounter : 0, // Number of onFocus events the menu has received, compared to the number
                    // of onBlur
  focusCloseTimer : null, // Timer for closing the menu when an onBlur event was received.
  
  
  /**
  * Updates the menu's position prior to activating.
  */
  updatePosition : function (event, commandType) {
    var positionerObject, triggerObject, offsetLeft, offsetTop, heightCenter,
        windowRightEdge, windowBottomEdge;
    
    triggerObject = this.getObject('menuTrigger');
    positionerObject = this.getObject('menuPositioner');
    
    if (window.innerWidth != null) {
      // W3C standard
      windowRightEdge = window.innerWidth + window.scrollX;
      windowBottomEdge = window.innerHeight + window.scrollY;
    }
    else if (document.documentElement.clientWidth != null) {
      // IE non-standard
      windowRightEdge = document.documentElement.clientWidth + document.documentElement.scrollLeft;
      windowBottomEdge = document.documentElement.clientHeight + document.documentElement.scrollTop;
    }
    
    if (positionerObject != null) {
      // Use positioner position
      if (positionerObject.offsetLeft != null) {
        // W3C standard
        this.menuObject.style.left = positionerObject.offsetLeft + 'px';
        this.menuObject.style.top  = positionerObject.offsetTop + 'px';
      }
    }
    else if (triggerObject != null) {
      
      offsetLeft = this.getOffsetForObject(triggerObject, 'left');
      offsetTop = this.getOffsetForObject(triggerObject, 'top');
      
      rightEdge = offsetLeft + this.menuObject.offsetWidth;
      
      if (commandType == 'rootMenu') {
        if (this.settings.orientation == 'horizontal') {
          // If the menu would risk going too far out, align it to the trigger's
          // right edge instead of the left one.
          if (rightEdge > windowRightEdge && windowRightEdge != 0) {
            offsetLeft += triggerObject.offsetWidth - this.menuObject.offsetWidth;
          }
          this.menuObject.style.left = offsetLeft + 'px';
          this.menuObject.style.top = (offsetTop + triggerObject.offsetHeight) + 'px';
        }
        else {
          this.menuObject.style.left = (offsetLeft + triggerObject.offsetWidth) + 'px';
          this.menuObject.style.top = offsetTop + 'px';
        }
      }
      else if (commandType == 'submenu') {
        // With submenus, we can always count on that the parent object is absolutely positioned,
        // so all position arguments are based from there. Thus, we do not use the offsetLeft and
        // offsetRight variables.
        
        // The 'top' position is based on the assumption that the menu should be centered around
        // its parent item, so it is as easy as possible to hit.
        this.menuObject.style.left = triggerObject.offsetWidth + 'px';
        
        offsetTop = triggerObject.offsetTop + triggerObject.offsetHeight / 2 - this.menuObject.offsetHeight / 2;
        if (offsetTop < 0) { offsetTop = 0; }
        
        this.menuObject.style.top = offsetTop + 'px';
      }
      else {
        alert(commandType);
      }
    }
    else {
      // Neither positioner nor trigger - use mouse position
      // BUGBUG (jluin): Not implemented yet
      alert('Positioning could find neither positioner nor trigger!');
    }
  },
  
  /**
  * Activates the menu, first calling updatePosition().
  */
  activate : function (event, commandType) {
    var menuSelectionIndicator, childTags, i;
    
    this.updatePosition(event, commandType);
    this.placeIframeAtObject(this.menuObject);
    
    // Indicate that the mouse has entered this menu, so we can be accessible to keyboard navigation.
    dynamicMenuManager.mouseEnteredMenu(this.cssId, null);
    
    menuSelectionIndicator = this.getObject('menuSelectionIndicator');
    
    if (menuSelectionIndicator != null) {
      menuSelectionIndicator.className = 'activeMenu';
    }
    
    // Register focus listeners on all links in this menu, so we'll
    // know when the input caret has left our care.
    childTags = this.menuObject.getElementsByTagName("a");
    for (i = 0; i < childTags.length; i++) {
      // The onFocus/onBlur event handlers should always return false so the event does
      // not propagate to any parents.
      //alert(childTags[i].onfocus);
      childTags[i].onfocus = dynamicMenuManager.menuGotFocus;
      childTags[i].onblur  = dynamicMenuManager.menuLostFocus;
      //dynamicMenuManager.debugInfo(childTags[i]);
    }
  },
  
  inactivate : function () {
    this.removeIframeFromObject(this.menuObject);
    this.menuObject.style.left = MENU_VERY_FAR_AWAY+'px';
    this.menuObject.style.top = MENU_VERY_FAR_AWAY+'px';
    this.getObject('menuSelectionIndicator').className = '';
  },

  /**
  * Returns true if the mouse pointer is within seven pixels of the menu, menu title
  * or menu positioner.
  *
  * This is a pretty smart way to do things, as I see it, since we'll automatically
  * include the menu title in the comparisons instead of forcing each menu implementation
  * to tell us how high the menus are.
  */
  isMouseWithinActiveArea : function (x, y) {
    var menuPositioner, menuTrigger;
    
    menuPositioner = this.getObject('menuPositioner');
    menuTrigger = this.getObject('menuTrigger');
    
    if (this.isMouseWithinObjectLimits(this.menuObject, x, y)
     || this.isMouseWithinObjectLimits(menuTrigger, x, y)
     || this.isMouseWithinObjectLimits(menuPositioner, x, y))
    {
      return true;
    }
    else {
      //return false;
    }
  },
  
  /**
  * Returns true if the mouse pointer is within seven pixels of the specified object.
  *
  * This is a helper method to isMouseWithinActiveArea().
  */
  isMouseWithinObjectLimits : function (theObject, x, y) {
    var xMin, xMax, yMin, yMax, offsetLeft, offsetTop;
    var borderWidth = 7;
    
    if (theObject == null) { return false; }
    
    offsetLeft = this.getOffsetForObject(theObject, 'left');
    offsetTop = this.getOffsetForObject(theObject, 'top');
    
    xMin = offsetLeft - borderWidth;
    xMax = offsetLeft + theObject.offsetWidth + borderWidth;
    yMin = offsetTop - borderWidth;
    yMax = offsetTop + theObject.offsetHeight + borderWidth;
    
    return (x >= xMin && x <= xMax && y >= yMin && y <= yMax);
  },
  
  /**
  * Returns an object related to the menu.
  *
  * valid so far: menuTrigger, menuPositioner, menuSelectionIndicator
  */
  getObject : function (name) {
    return document.getElementById(name + '-' + this.cssId);
  },
  
  /**
  * Returns the full offset value for an object.
  *
  * According to W3C, offsetTop/offsetLeft should be counted as an offset from the
  * offsetParent, and at least IE/Windows behaves that way. This means that we have
  * to recurse through the offsetParent tree in order to find the full offset of an
  * item.
  *
  * @param Object theObject
  * @param string whichOffset
  *           Valid values: "left", "top"
  */
  getOffsetForObject : function (theObject, whichOffset) {
    var baseOffset;
    
    switch (whichOffset) {
      case 'left':
      baseOffset = theObject.offsetLeft;
      break;
      
      case 'top':
      baseOffset = theObject.offsetTop;
      break;
    }
    
    //alert(theObject.tagName+"."+theObject.className+"#"+theObject.id+" ("+whichOffset+") = "+baseOffset);
    
    if (theObject.offsetParent != null) {
      return baseOffset + this.getOffsetForObject(theObject.offsetParent, whichOffset);
    }
    else {
      //alert('(no further parents)');
      return baseOffset;
    }
  },
  
  /**
  * Enters a sub-menu; meaning, this menu will not accept hover events, and sub-menus
  * are treated with special care.
  */
  enterSubmenu : function (event, theMenu) {
    var menuLocker;
    
    menuLocker = this.getObject('menuLockIndicator');
    menuLocker.className = '';
    
    this.submenuReferenceX = this.getOffsetForObject(theMenu, 'left');
    //dynamicMenuManager.debugInfo('reference point: '+this.submenuReferenceX);
    
    this.allowsMenuEvents = false;
    
    dynamicMenuManager.addMouseListener(this);
    this.mouseMoved(event);
  },
  
  /**
  * Returns from a sub-menu; meaning, this menu will now accept hover events, and
  * we are free to open sub-menus at will.
  */
  returnFromSubmenu : function () {
    var menuLocker;
    
    this.allowsMenuEvents = true;
    
    menuLocker = this.getObject('menuLockIndicator');
    menuLocker.className = 'frontmostMenu';
    
    dynamicMenuManager.dropMouseListener(this);
    dynamicMenuManager.closeActiveMenu();
  },
  
  /**
  * Returns true if the menu object allows sub-menus to be opened.
  */
  canOpenSubmenus : function () {
    return this.allowsMenuEvents;
  },
  
  /**
  * Tracks the mouse. (Only used while the mouse is moving toward a sub menu)
  */
  mouseMoved : function (theEvent) {
    var pageX, submenuDistance;
    
    if (theEvent.pageX != null) {
      // W3C standard
      pageX = theEvent.pageX; pageY = theEvent.pageY;
    }
    else {
      // Windows IE non-standard
      pageX = theEvent.x + document.documentElement.scrollLeft;
      pageY = theEvent.y + document.documentElement.scrollTop;
    }
    
    submenuDistance = Math.abs(this.submenuReferenceX - pageX);
    
    if (this.lastMouseDistanceFromSubmenu == null) {
      //dynamicMenuManager.debugInfo('resetting mouse distance...');
      this.lastMouseDistanceFromSubmenu = submenuDistance;
    }
    
    //dynamicMenuManager.debugInfo('mouse diff is '+(this.lastMouseDistanceFromSubmenu - submenuDistance) + ' on '+pageX);
    
    if (this.lastMouseDistanceFromSubmenu - submenuDistance < 0) {
      // Mouse moved away from the submenu, so we'll activate events for this
      // menu again...
      //alert('mouse moved '+(this.lastMouseDistanceFromSubmenu - submenuDistance)+' so I will allow events again');
      this.returnFromSubmenu();
    }
    
    this.lastMouseDistanceFromSubmenu = submenuDistance;
  },
  
  /**
  * Called when a menu has gotten an onFocus event, either in the menu itself
  * or in a submenu.
  */
  gotFocus : function (event) {
    this.focusCounter++;
    if (this.focusTimer) {
      clearTimeout(this.focusTimer);
      focusTimer = null;
    }
  },
  
  /**
  * Called when a menu has gotten an onBlur event, either in the menu itself or in
  * a submenu.
  */
  lostFocus : function (event) {
    this.focusCounter--;
    //alert('lost focus, now at ' + this.focusCounter);
    //dynamicMenuManager.debugInfo('menu '+this.cssId+' lost focus and is now at ' + this.focusCounter);
    if (this.focusCounter == 0) {
      this.focusTimer = setTimeout('dynamicMenuManager.menuTimedOut("'+this.cssId+'");', 100);
    }
  },

  placeIframeAtObject : function (obj) {
    var theIframe, zIndex;

    // If there is already an iframe on the object, remove it and then create a new one.
    this.removeIframeFromObject(obj);

    if (navigator.appName != 'Microsoft Internet Explorer' || navigator.platform == 'MacPPC') {
      return;
    }
    
    var versionMatches = navigator.appVersion.match(/MSIE\s*([0-9]+)\.([0-9])/);
    if (versionMatches[1] <= 5 || (versionMatches[1] == 5 && versionMatches[2] == 5)) {
      // IE <= 5.0 does not handle iframe work-arounds correctly, skip it.
      return;
    }

    id = Math.random();

    document.body.insertAdjacentHTML('afterBegin', '<iframe style="display: none; left: 0px; position: absolute; top: 0px" src="javascript:false;" frameBorder="0" scrolling="no"></iframe>');
    //document.body.insertAdjacentHTML('beforeEnd', '<br />debug: '+obj);

    // The inserted iframe should now be the firstChild of the body object.
    theIframe = document.body.firstChild;
    //alert(obj.style);
    if (obj.style.zIndex == null || obj.style.zIndex == 0) {
      obj.style.zIndex = 99;
    }

    theIframe.style.zIndex = obj.style.zIndex - 1;
    theIframe.style.left   = obj.offsetLeft;
    theIframe.style.top    = obj.offsetTop;
    theIframe.style.width  = obj.offsetWidth;
    theIframe.style.height = obj.offsetHeight;
    theIframe.style.display = 'block';
    obj.dpIframe = theIframe;
    //alert(theIframe.style.zIndex);
  },



  removeIframeFromObject : function (obj) {
    if (navigator.appName != 'Microsoft Internet Explorer') {
      return;
    }

    if (obj.dpIframe != null) {
      document.body.removeChild(obj.dpIframe);
      obj.dpIframe = null;
    }
  }
}
