/*****************************************************************************************
* COPYRIGHT NOTICE                                                                       *
*                                                                                        *
* This code is (c) 2004 Dupoint AB. All rights reserved. Any unauthorized usage, copying *
* or reverse engineering is strictly prohibited.                                         *
*****************************************************************************************/

/**
* This is the JavaScript implementation of DynamicMenus.
*
* It is object-oriented JavaScript, which is kind of ugly but at least
* more coherent than having to use hordes of global variables.
*/
function DynamicMenuManager() {
  //this.init();
}

DynamicMenuManager.prototype = {
  
  activeMenu : null, // Object of the active menu, if any.
  parentMenus : new Array(), // All open parent menus, if any. Stack.
  menus : new Array(), // DynamicMenu objects for all menus, integer-indexed.
  menuCounter : 0, // Keeps count of how many menus have registered with the manager.
  mouseListeners : new Array(), // DynamicMenu objects that currently listen to mouse-move events.
  
  /**
  * Opens a root menu. This method is called from JavaScript trigger code.
  *
  * For opening a root menu, use the commandType "rootMenu"; for sub-menus,
  * use "subMenu".
  */
  openMenu : function (menuId, commandType, event) {
    var theMenu, activeMenuStatus, parentMenu;
    
    theMenu = document.getElementById(menuId);
    
    if (theMenu == null) { return; }
    if (theMenu.controlObject.isOpen) { return; }
    
    if (theMenu == null) {
      alert('No such menu!');
    }
    
    if (commandType == 'rootMenu') {
      this.closeAllMenus();
    }
    else if (commandType == 'submenu') {
      // Before we open a submenu, we should check that we are 'allowed' to open it.
      //this.debugInfo(theMenu.controlObject.settings.parentMenuId);
      parentMenu = document.getElementById(theMenu.controlObject.settings['parentMenuId']);
      
      if (!parentMenu.controlObject.canOpenSubmenus()) {
        //this.debugInfo(theMenu.id+' does not open submenus');
        return true; // Keep processing events
      }
      
      while (this.activeMenu != parentMenu) {
        this.closeActiveMenu();
      }
    }
    
    // Push away any currently active menu so we still keep track of it
    if (this.activeMenu && this.activeMenu != this.cssId) {
      // We would have used Array.push() here if only IE/Windows supported it :-(
      this.parentMenus[this.parentMenus.length] = this.activeMenu;
    }
    
    theMenu.controlObject.triggeredByCommand = commandType;
    theMenu.controlObject.triggeredByEvent = event.type;
    
    //this.debugInfo('Sending activate event '+commandType+' to the menu '+theMenu.id);
    theMenu.controlObject.activate(event, commandType);
    
    if (commandType == 'submenu') {
      this.activeMenu.controlObject.enterSubmenu(event, theMenu);
    }
    
    // Set this last so we can use it anywhere in this method.
    this.activeMenu = theMenu;
  },
  
  /**
  * Sets menu settings for the given menu. This method is called from 'outside',
  * i.e. PHP code.
  *
  * Also sets the internalId of each menu, so we can relate it to integer-based arrays,
  * and creates an entry in the menuStatus array for the menu.
  */
  setSettingsForMenu : function (menuId, settingsArray) {
    var menuObj = new DynamicMenu(menuId, settingsArray);
    
    this.menus[this.menuCounter] = menuObj;
    this.menuCounter++;
    
    document.getElementById(menuId).controlObject = menuObj;
  },
  
  /**
  * Closes all open menus.
  */
  closeAllMenus : function () {
    while (this.activeMenu != null) {
      this.closeActiveMenu();
    }
  },
  
  /**
  * Closes the active menu and makes its parent active.
  */
  closeActiveMenu : function () {
    this.closeMenuWithId(this.activeMenu.id);
    
    this.activeMenu = null;
    
    if (this.parentMenus.length > 0) {
      // We would have used Array.pop() here if only IE/Windows supported it... :-(
      this.activeMenu = this.parentMenus[this.parentMenus.length-1];
      this.parentMenus.length--;
      this.activeMenu.controlObject.returnFromSubmenu();
    }
  },
  
  /**
  * Closes the menu with the given id.
  */
  closeMenuWithId : function (theId) {
    var theMenu = document.getElementById(theId);
    theMenu.controlObject.inactivate();
  },
  
  
  /**
  * Returns true if the mouse is indicated as being inside any menu.
  */
  isMouseWithinMenu : function (theEvent) {
    var i;
    
    if (theEvent == null) {
      // This must be Internet Explorer, the browser that never passes an
      // event to a defined event handler...
      theEvent = event;
    }
    
    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;
    }
    
    for (i = 0; i < this.menus.length; i++) {
      if (this.menus[i].isMouseWithinActiveArea(pageX, pageY)) {
        return true;
      }
    }
    return false;
  },
  
  // MOUSING EVENTS -----------------------------------------------------------------------
  /**
  * Activated each time a mouse enters a menu, be it menu title or menu contents.
  */
  mouseEnteredMenu : function (menuId, event) {
    var theMenu = document.getElementById(menuId),
        controlObject;
    
    if (!theMenu) { return; }
    
    var controlObject = document.getElementById(menuId).controlObject;
    controlObject.mouseIsWithinMenu = true;
    
    if (this.closeMenusTimer != null) {
      clearTimeout(this.closeMenusTimer);
    }
  },
  
  /**
  * Activated each time the mouse pointer exits a menu, be it menu title or menu contents.
  *
  * This can be immediately followed or preceded by a mouseEnteredMenu event, which means that
  * we never close the menu just because the pointer isn't there.
  */
  mouseLeftMenu : function (menuId, event) {
    var theMenu = document.getElementById(menuId);

    if (!theMenu) { return; }

    theMenu.controlObject.mouseIsWithinMenu = false;
  },
  
  /**
  * Activated each time a mouse click occurs anywhere on the page.
  *
  * NOTE: This method is called statically and should not refer to the object 'this'.
  */
  mouseClickInBody : function (event) {
    // This method is always called statically, i.e. the 'this' object always points to
    // document.body. So we need to call it on our global instance of DynamicMenuManager.
    if (!dynamicMenuManager.isMouseWithinMenu(event)) {
      dynamicMenuManager.closeAllMenus();
    }
  },
  
  /**
  * Adds a mouse-move listener.
  */
  addMouseListener : function (newListener) {
    this.mouseListeners[this.mouseListeners.length] = newListener;
  },
  
  /**
  * Removes a mouse-move listener.
  */
  dropMouseListener : function (theListener) {
    var i;
    
    for (i = 0; i < this.mouseListeners.length; i++) {
      if (this.mouseListeners[i] == theListener) {
        this.mouseListeners[i] = null;
      }
    }
  },
  
  /**
  * Activated each time the mouse moves.
  *
  * NOTE: This method is called statically and should not refer to the object 'this'.
  */
  mouseMoved : function (theEvent) {
    var pageX, pageY, i;
    
    if (dynamicMenuManager.activeMenu == null)
    {
      //dynamicMenuManager.debugInfo('ignoring event on ' + theEvent.x);
      return true;
    }
    
    if (dynamicMenuManager.activeMenu.controlObject.triggeredByEvent != "mouseover") {
      return true;
    }
    
    if (theEvent == null && event != null) {
      // Windows IE does not pass the event to its event-triggered functions,
      // instead we have to retrieve the event ourselves from a global object. Boo!
      theEvent = event;
    }
    
    if (theEvent.pageX != null) {
      // W3C standard
      pageX = theEvent.pageX; pageY = theEvent.pageY;
    }
    else if (document.documentElement.scrollTop != null) {
      // Windows IE non-standard
      // IMPORTANT! (jluin) I just spent well over an hour banging my head against this...
      // Apparently, declaring a document as XHTML deprecates document.body with no indication
      // that one should use document.documentElement instead. So PLEASE MAKE SURE that all
      // pages that use DynamicMenus are also declared as XHTML!
      pageX = theEvent.clientX + document.documentElement.scrollLeft;
      pageY = theEvent.clientY + document.documentElement.scrollTop;
    }
    else if (document.body.scrollTop != null) {
      // Possibly a fallback? Not tested, will hopefully not be reached! -- jluin 2004-11-12
      pageX = theEvent.clientX + document.body.scrollLeft;
      pageY = theEvent.clientY + document.body.scrollTop;
    }
    
    // FIXME (jluin): This piece of code assumes that there is only one menu, please
    // correct this when implementing support for sub-menus
    if (dynamicMenuManager.activeMenu.controlObject.triggeredByCommand == 'rootMenu') {
      if (!dynamicMenuManager.activeMenu.controlObject.isMouseWithinActiveArea(pageX, pageY)) {
        //dynamicMenuManager.debugInfo('mouse is not within active area; closing.');
        dynamicMenuManager.closeAllMenus();
      }
    }
    
    // Fire mouse-move events to all listeners
    if (dynamicMenuManager.mouseListeners != null) {
      for (i = 0; i < dynamicMenuManager.mouseListeners.length; i++) {
        if (dynamicMenuManager.mouseListeners[i] != null) {
          dynamicMenuManager.mouseListeners[i].mouseMoved(theEvent);
        }
      }
    }
  },
  
  /**
  * Activated when the window is resized.
  */
  windowResized : function (event) {
    // Fire positioning events to all menus
    var menuToReposition;
    
    // We should only need to reposition the active root menu.
    if (dynamicMenuManager.parentMenus.length > 0) {
      menuToReposition = dynamicMenuManager.parentMenus[0];
    }
    else {
      menuToReposition = dynamicMenuManager.activeMenu;
    }
    
    if (menuToReposition && menuToReposition.controlObject) {
      //dynamicMenuManager.debugInfo('repositioning: ' + menuToReposition);
      menuToReposition.controlObject.updatePosition(event, 'rootMenu');
    }
    
  },
  
  debugInfo : function (theText) {
    var debugObj = document.getElementById('debugInfo');
    if (!debugObj) { return; }
    
    debugObj.innerHTML = theText + '<br />';
  },
  
  /**
  * Triggered when a menu gets an onFocus event.
  */
  menuGotFocus : function (theEvent, menuId) {
    var theMenu;
    
    if (!theEvent) {
      // IE/Win will for some reason not pass us the event, we have to
      // retrieve it as a global object.
      theEvent = event;
    }
    //alert(theEvent.srcElement);
    if (menuId == null) {
      theMenu = dynamicMenuManager.getMenuFromEvent(theEvent);
    }
    else {
      // menuId != null, so we'll always have a menu object.
      theMenu = document.getElementById(menuId);
    }
    
    // Not all menus necessarily exist (empty ones never draw the tags),
    // so if it doesn't exist it is not necessarily an error.
    if (theMenu != null) {
      theMenu.controlObject.gotFocus(theEvent);
    }
  },
  
  /**
  * Triggered when a menu gets an onBlur event.
  */
  menuLostFocus : function (theEvent, menuId) {
    var theMenu;
    
    if (!theEvent) {
      // IE/Win will for some reason not pass us the event, we have to
      // retrieve it as a global object.
      theEvent = event;
    }
    //alert(theEvent.srcElement);
    if (menuId == null) {
      theMenu = dynamicMenuManager.getMenuFromEvent(theEvent);
    }
    else {
      // menuId != null, so we'll always have a menu object.
      theMenu = document.getElementById(menuId);
    }
    
    // Not all menus necessarily exist (empty ones never draw the tags),
    // so if it doesn't exist it is not necessarily an error.
    if (theMenu != null) {
      theMenu.controlObject.lostFocus(theEvent);
    }
  },
  
  /**
  * Returns the menu object closest to the given event.
  */
  getMenuFromEvent : function (theEvent) {
    // We need to find the element closest to the triggering element that has
    // a control object, and then we'll assume that object is the menu object.
    //alert(theEvent);
    if (theEvent.target != null) {
      // W3C object model - standard
      theMenu = theEvent.target.parentNode;
    }
    else {
      // IE/Win object model - special-case
      theMenu = theEvent.srcElement.parentElement;
      //alert(theMenu);
    }

    while (theMenu != null && theMenu.controlObject == null) {
      //alert('regression to ' + theMenu.parentNode);
      theMenu = theMenu.parentNode;
    }
    
    return theMenu;
  },
  
  /**
  * Called from a timeout when a menu has lost focus and focus didn't return within
  * the defined time.
  */
  menuTimedOut : function (menuId) {
    this.closeMenuWithId(menuId);
  },
  
  // Bogus last entry so I don't have to remember that the last method definition
  // must not end in a comma.
  bogusLastEntry : null
};

// JavaScript OOP only works when instantiated, so we create a single global instance of our class.
var dynamicMenuManager = new DynamicMenuManager();

// Register all mouse clicks and mouse movements so we can track them
document.body.onclick = dynamicMenuManager.mouseClickInBody;
document.body.onmousemove = dynamicMenuManager.mouseMoved;
window.onresize = dynamicMenuManager.windowResized;