package gwtquery.jsplugins.menu.client;

/**
 *
 * This class wraps the jquery menu plugin from:
 *
 * http://p.sohei.org/jquery-plugins/menu/
 *
 */
public abstract class JsMenu {

  public static native void loadPlugin() /*-{
  var l = @com.google.gwt.query.jsquery.client.JsQueryUtils::log(Ljava/lang/Object;);
  var window = $wnd;
  var document = $doc;
  var jQuery = $wnd.$;

(function($)
{
  var menus = [], //list of all menus
    visibleMenus = [], //list of all visible menus
    activeMenu = activeItem = null,
    menuDIVElement = $('<div class="menu-div outerbox" style="position:absolute;top:0;left:0;display:none;"><div class="shadowbox1"></div><div class="shadowbox2"></div><div class="shadowbox3"></div></div>')[0],
    menuULElement = $('<ul class="menu-ul innerbox"></ul>')[0],
    menuItemElement = $('<li style="position:relative;"><div class="menu-item"></div></li>')[0],
    arrowElement = $('<img class="menu-item-arrow" />')[0],
    $rootDiv = $('<div id="root-menu-div" style="position:absolute;top:0;left:0;"></div>'), //create main menu div
    defaults = {
      // $.Menu options
      showDelay : 200,
      hideDelay : 200,
      hoverOpenDelay: 0,
      offsetTop : 0,
      offsetLeft : 0,
      minWidth: 0,
      onOpen: null,
      onClose: null,

      // $.MenuItem options
      onClick: null,
      arrowSrc: null,
      addExpando: false,

      // $.fn.menuFromElement options
      copyClassAttr: false
    };

  $(function(){
    $rootDiv.appendTo('body');
  });

  $.extend({
    MenuCollection : function(items) {

      this.menus = [];

      this.init(items);
    }
  });
  $.extend($.MenuCollection, {
    prototype : {
      init : function(items)
      {
        if ( items && items.length )
        {
          for ( var i = 0; i < items.length; i++ )
          {
            this.addMenu(items[i]);
            items[i].menuCollection = this;
          }
        }
      },
      addMenu : function(menu)
      {
        if ( menu instanceof $.Menu )
          this.menus.push(menu);

        menu.menuCollection = this;

        var self = this;
        $(menu.target).hover(function(){
          if ( menu.visible )
            return;

          //when there is an open menu in this collection, hide it and show the new one
          for ( var i = 0; i < self.menus.length; i++ )
          {
            if ( self.menus[i].visible )
            {
              self.menus[i].hide();
              menu.show();
              return;
            }
          }
        }, function(){});
      }
    }
  });

  $.extend({
    Menu : function(target, items, options) {
      this.menuItems = []; //all direct child $.MenuItem objects
      this.subMenus = []; //all subMenus from this.menuItems
      this.visible = false;
      this.active = false; //this menu has hover or one of its submenus is open
      this.parentMenuItem = null;
      this.settings = $.extend({}, defaults, options);
      this.target = target;
      this.$eDIV = null;
      this.$eUL = null;
      this.timer = null;
      this.menuCollection = null;
      this.openTimer = null;

      this.init();
      if ( items && $.isArray(items) )
        this.addItems(items);
    }
  });
  $.extend($.Menu, {
    checkMouse : function(e)
    {
      var t = e.target;
      //the user clicked on the target of the currenty open menu
      if ( visibleMenus.length && t == visibleMenus[0].target )
        return;

      //get the last node before the #root-menu-div
      while ( t.parentNode && t.parentNode != $rootDiv[0] )
        t = t.parentNode;

      // FIXME: why do we need a timeout
      setTimeout($.Menu.closeAll, 100);

      // FIXME: JsQuery each doesn't work with arrays
      // if ( !$(visibleMenus).filter(function(){ return this.$eDIV[0] == t }).length )
      // {
      //  $.Menu.closeAll();
      // }
      return true;
    },
    checkKey : function(e)
    {
      switch ( e.keyCode )
      {
        case 13: //return
          if ( activeItem )
            activeItem.click(e, activeItem.$eLI[0]);
          break;
        case 27: //ESC
          $.Menu.closeAll();
          break;
        case 37: //left
          if ( !activeMenu )
            activeMenu = visibleMenus[0];
          var a = activeMenu;
          if ( a && a.parentMenuItem ) //select the parent menu and close the submenu
          {
            //unbind the events temporary, as we dont want the hoverout event to fire
            var pmi = a.parentMenuItem;
            pmi.$eLI.unbind('mouseout').unbind('mouseover');
            a.hide();
            pmi.hoverIn(true);
            setTimeout(function(){ //bind again..but delay it
              pmi.bindHover();
            });
          }
          else if ( a && a.menuCollection ) //select the previous menu in the collection
          {
            var pos,
              mcm = a.menuCollection.menus;
            if ( (pos = $.inArray(a, mcm)) > -1 )
            {
              if ( --pos < 0 )
                pos = mcm.length - 1;
              $.Menu.closeAll();
              mcm[pos].show();
              mcm[pos].setActive();
              if ( mcm[pos].menuItems.length ) //select the first item
                mcm[pos].menuItems[0].hoverIn(true);
            }
          }
          break;
        case 38: //up
          if ( activeMenu )
            activeMenu.selectNextItem(-1);
          break;
        case 39: //right
          if ( !activeMenu )
            activeMenu = visibleMenus[0];
          var m,
            a = activeMenu,
            asm = activeItem ? activeItem.subMenu : null;
          if ( a )
          {
            if ( asm && asm.menuItems.length ) //select the submenu
            {
              asm.show();
              asm.menuItems[0].hoverIn();
            }
            else if ( (a = a.inMenuCollection()) ) //select the next menu in the collection
            {
              var pos,
                mcm = a.menuCollection.menus;
              if ( (pos = $.inArray(a, mcm)) > -1 )
              {
                if ( ++pos >= mcm.length )
                  pos = 0;
                $.Menu.closeAll();
                mcm[pos].show();
                mcm[pos].setActive();
                if ( mcm[pos].menuItems.length ) //select the first item
                  mcm[pos].menuItems[0].hoverIn(true);
              }
            }
          }
          break;
        case 40: //down
          if ( !activeMenu )
          {
            if ( visibleMenus.length && visibleMenus[0].menuItems.length )
              visibleMenus[0].menuItems[0].hoverIn();
          }
          else
            activeMenu.selectNextItem();
          break;
      }
      if ( e.keyCode > 36 && e.keyCode < 41 )
        return false; //this will prevent scrolling
    },
    closeAll : function()
    {
      while ( visibleMenus.length )
        visibleMenus[0].hide();
    },
    setDefaults : function(d)
    {
      $.extend(defaults, d);
    },
    prototype : {

      init : function()
      {
        var self = this;
        if ( !this.target )
          return;
        else if ( this.target instanceof $.MenuItem )
        {
          this.parentMenuItem = this.target;
          this.target.addSubMenu(this);
          this.target = this.target.$eLI;
        }

        menus.push(this);

        //use the dom methods instead the ones from jquery (faster)
        this.$eDIV = $(menuDIVElement.cloneNode(1));
        this.$eUL = $(menuULElement.cloneNode(1));
        this.$eDIV[0].appendChild(this.$eUL[0]);
        $rootDiv[0].appendChild(this.$eDIV[0]);

        //bind events
        if ( !this.parentMenuItem )
        {
          $(this.target).click(function(e){
            self.onClick(e);
          }).hover(function(e){
            self.setActive();

            if ( self.settings.hoverOpenDelay )
            {
              self.openTimer = setTimeout(function(){
                if ( !self.visible )
                  self.onClick(e);
              }, self.settings.hoverOpenDelay);
            }
          }, function(){
            if ( !self.visible )
              $(this).removeClass('activetarget');

            if ( self.openTimer )
              clearTimeout(self.openTimer);
          });
        }
        else
        {
          this.$eDIV.hover(function(){
            self.setActive();
          }, function(){});
        }
      },
      setActive : function()
      {
        if ( !this.parentMenuItem )
          $(this.target).addClass('activetarget');
        else
          this.active = true;
      },
      addItem : function(item)
      {
        if ( item instanceof $.MenuItem )
        {
          if ( $.inArray(item, this.menuItems) == -1 )
          {
            this.$eUL.append(item.$eLI);
            this.menuItems.push(item);
            item.parentMenu = this;
            if ( item.subMenu )
              this.subMenus.push(item.subMenu);
          }
        }
        else
        {
          this.addItem(new $.MenuItem(item, this.settings));
        }
      },
      addItems : function(items)
      {
        for ( var i = 0; i < items.length; i++ )
        {
          this.addItem(items[i]);
        }
      },
      removeItem : function(item)
      {
        var pos = $.inArray(item, this.menuItems);
        if ( pos > -1 )
          this.menuItems.splice(pos, 1);
        item.parentMenu = null;
      },
      hide : function()
      {
        if ( !this.visible )
          return;

        var i,
          pos = $.inArray(this, visibleMenus);

        this.$eDIV.hide();

        if ( pos >= 0 )
          visibleMenus.splice(pos, 1);
        this.visible = this.active = false;

        $(this.target).removeClass('activetarget');

        //hide all submenus
        for ( i = 0; i < this.subMenus.length; i++ )
        {
          this.subMenus[i].hide();
        }

        //set all items inactive (e.g. remove hover class..)
        for ( i = 0; i < this.menuItems.length; i++ )
        {
          if ( this.menuItems[i].active )
            this.menuItems[i].setInactive();
        }

        if ( !visibleMenus.length ) //unbind events when the last menu was closed
          $(document).unbind('mousedown', $.Menu.checkMouse).unbind('keydown', $.Menu.checkKey);

        if ( activeMenu == this )
          activeMenu = null;

        if ( this.settings.onClose )
          this.settings.onClose.call(this);
      },
      show : function(e)
      {
        if ( this.visible )
          return;

        var zi,
          pmi = this.parentMenuItem;

        if ( this.menuItems.length ) //show only when it has items
        {
          if ( pmi ) //set z-index
          {
            zi = parseInt(pmi.parentMenu.$eDIV.css('z-index'));
            this.$eDIV.css('z-index', (isNaN(zi) ? 1 : zi + 1));
          }
          this.$eDIV.css({visibility: 'hidden', display:'block'});

          //set min-width
          if ( this.settings.minWidth )
          {
            if ( this.$eDIV.width() < this.settings.minWidth )
              this.$eDIV.css('width', this.settings.minWidth);
          }

          this.setPosition();
          this.$eDIV.css({display:'none', visibility: ''}).show();

          //IEs default width: auto is bad! ie6 and ie7 have are producing different errors.. (7 = 5px shadowbox + 2px border)
          if ( 0) //$.browser.msie )
            this.$eUL.css('width', parseInt($.browser.version) == 6 ? this.$eDIV.width() - 7 : this.$eUL.width());

          if ( this.settings.onOpen )
            this.settings.onOpen.call(this);
        }

        if ( visibleMenus.length == 0 )
          $(document).bind('mousedown', $.Menu.checkMouse).bind('keydown', $.Menu.checkKey);

        this.visible = true;
        visibleMenus.push(this);
      },
      setPosition : function()
      {
        var $t, o, posX, posY,
          pmo, //parent menu offset
          wst, //window scroll top
          wsl, //window scroll left
          ww = $(window).width(),
          wh = $(window).height(),
          pmi = this.parentMenuItem,
          height = this.$eDIV[0].clientHeight,
          width = this.$eDIV[0].clientWidth,
          pheight; //parent height

        if ( pmi )
        {
          //position on the right side of the parent menu item
          o = pmi.$eLI.offset();
          posX = o.left + pmi.$eLI.width();
          posY = o.top;
        }
        else
        {
          //position right below the target
          $t = $(this.target);
          o = $t.offset();

          posX = o.left + this.settings.offsetLeft;
          posY = o.top + $t.height() + this.settings.offsetTop;
        }

        //y-pos
        if ($().scrollTop )
        {
          wst = $(window).scrollTop();
          if ( wh < height ) //menu is bigger than the window
          {
            //position the menu at the top of the visible area
            posY = wst;
          }
          else if ( wh + wst < posY + height ) //outside on the bottom?
          {
            if ( pmi )
            {
              pmo = pmi.parentMenu.$eDIV.offset();
              pheight = pmi.parentMenu.$eDIV[0].clientHeight;
              if ( height <= pheight )
              {
                //bottom position = parentmenu-bottom position
                posY = pmo.top + pheight - height;
              }
              else
              {
                //top position = parentmenu-top position
                posY = pmo.top;
              }
              //still outside on the bottom?
              if ( wh + wst < posY + height )
              {
                //shift the menu upwards till the bottom is visible
                posY -= posY + height - (wh + wst);
              }
            }
            else
            {
              //shift the menu upwards till the bottom is visible
              posY -= posY + height - (wh + wst);
            }
          }
        }
        //x-pos
        if ($().scrollLeft )
        {
          wsl = $(window).scrollLeft();
          if ( ww + wsl < posX + width )
          {
            if ( pmi )
            {
              //display the menu not on the right side but on the left side
              posX -= pmi.$eLI.width() + width;
              //outside on the left now?
              if ( posX < wsl )
                posX = wsl;
            }
            else
            {
              //shift the menu to the left until it fits
              posX -= posX + width - (ww + wsl);
            }
          }
        }

        //set position
        this.$eDIV.css({left: posX, top: posY});
      },
      onClick : function(e)
      {
        if ( this.visible )
        {
          this.hide();
          this.setActive(); //the class is removed in the hide() method..add it again
        }
        else
        {
          //close all open menus
          $.Menu.closeAll();
          this.show(e);
        }
      },
      addTimer : function(callback, delay)
      {
        var self = this;
        this.timer = setTimeout(function(){
          callback.call(self);
          self.timer = null;
        }, delay);
      },
      removeTimer : function()
      {
        if ( this.timer )
        {
          clearTimeout(this.timer);
          this.timer = null;
        }
      },
      selectNextItem : function(offset)
      {
        var i, pos = 0,
          mil = this.menuItems.length,
          o = offset || 1;

        //get current pos
        for ( i = 0; i < mil; i++ )
        {
          if ( this.menuItems[i].active )
          {
            pos = i;
            break;
          }
        }
        this.menuItems[pos].hoverOut();

        do //jump over the separators
        {
          pos += o;
          if ( pos >= mil )
            pos = 0;
          else if ( pos < 0 )
            pos = mil - 1;
        } while ( this.menuItems[pos].separator );
        this.menuItems[pos].hoverIn(true);
      },
      inMenuCollection : function()
      {
        var m = this;
        while ( m.parentMenuItem )
          m = m.parentMenuItem.parentMenu;
        return m.menuCollection ? m : null;
      },
      destroy : function() //delete menu
      {
        var pos, item;

        this.hide();

        //unbind events
        if ( !this.parentMenuItem )
          $(this.target).unbind('click').unbind('mouseover').unbind('mouseout');
        else
          this.$eDIV.unbind('mouseover').unbind('mouseout');

        //destroy all items
        while ( this.menuItems.length )
        {
          item = this.menuItems[0];
          item.destroy();
          delete item;
        }

        if ( (pos = $.inArray(this, menus)) > -1 )
          menus.splice(pos, 1);

        if ( this.menuCollection )
        {
          if ( (pos = $.inArray(this, this.menuCollection.menus)) > -1 )
            this.menuCollection.menus.splice(pos, 1);
        }

        this.$eDIV.remove();
      }
    }
  });

  $.extend({
    MenuItem : function(obj, options)
    {
      if ( typeof obj == 'string' )
        obj = {src: obj};

      this.src = obj.src || '';
      this.url = obj.url || null;
      this.urlTarget = obj.target || null;
      this.addClass = obj.addClass || null;
      this.data = obj.data || null;

      this.$eLI = null;
      this.parentMenu = null;
      this.subMenu = null;
      this.settings = $.extend({}, defaults, options);
      this.active = false;
      this.enabled = true;
      this.separator = false;

      this.init();

      if ( obj.subMenu )
        new $.Menu(this, obj.subMenu, options);
    }
  });

  $.extend($.MenuItem, {
    prototype : {
      init : function()
      {
        var i, isStr,
          src = this.src,
          self = this;

        this.$eLI = $(menuItemElement.cloneNode(1));
        if ( this.addClass )
          this.$eLI[0].setAttribute('class', this.addClass);

        if ( this.settings.addExpando && this.data )
          this.$eLI[0].menuData = this.data;

        if ( src == '' )
        {
          this.$eLI.addClass('menu-separator');
          this.separator = true;
        }
        else
        {
          isStr = typeof src == 'string';
          if ( isStr && this.url ) //create a link node, when we have an url
            src = $('<a href="' + this.url + '"' + (this.urlTarget ? 'target="' + this.urlTarget + '"' : '') + '>' + src + '</a>');
          else if ( isStr || !src.length )
            src = [src];
          //go through the passed DOM-Elements (or jquery objects or text nodes.) and append them to the menus list item
          //this.$eLI.append(this.src) is really slow when having a lot(!!!) of items
          for ( i = 0; i < src.length; i++ )
          {
            if ( typeof src[i] == 'string' )
            {
              //we cant use createTextNode, as html entities won't be displayed correctly (eg. &copy;)
              elem = document.createElement('span');
              elem.innerHTML = src[i];
              this.$eLI[0].firstChild.appendChild(elem);
            }
            else
              this.$eLI[0].firstChild.appendChild(src[i].cloneNode(1));
          }
        }

        this.$eLI.click(function(e){
          self.click(e, this);
        });
        this.bindHover();
      },
      click : function(e, scope)
      {
        if ( this.enabled && this.settings.onClick )
          this.settings.onClick.call(scope, e, this);
      },
      bindHover : function()
      {
        var self = this;
        this.$eLI.hover(function(){
            self.hoverIn();
          }, function(){
            self.hoverOut();
        });
      },
      hoverIn : function(noSubMenu)
      {
        this.removeTimer();

        var i,
          pms = this.parentMenu.subMenus,
          pmi = this.parentMenu.menuItems,
          self = this;

        //remove the timer from the parent item, when there is one (e.g. to close the menu)
        if ( this.parentMenu.timer )
          this.parentMenu.removeTimer();

        if ( !this.enabled )
          return;

        //deactivate all menuItems on the same level
        for ( i = 0; i < pmi.length; i++ )
        {
          if ( pmi[i].active )
            pmi[i].setInactive();
        }

        this.setActive();
        activeMenu = this.parentMenu;

        //are there open submenus on the same level? close them!
        for ( i = 0; i < pms.length; i++ )
        {
          if ( pms[i].visible && pms[i] != this.subMenu && !pms[i].timer ) //close if there is no closetimer running already
            pms[i].addTimer(function(){
              this.hide();
            }, pms[i].settings.hideDelay);
        }

        if ( this.subMenu && !noSubMenu )
        {
          //set timeout to show menu
          this.subMenu.addTimer(function(){
            this.show();
          }, this.subMenu.settings.showDelay);
        }
      },
      hoverOut : function()
      {
        this.removeTimer();

        if ( !this.enabled )
          return;

        if ( !this.subMenu || !this.subMenu.visible )
          this.setInactive();
      },
      removeTimer : function()
      {
        if ( this.subMenu )
        {
          this.subMenu.removeTimer();
        }
      },
      setActive : function()
      {
        this.active = true;
        this.$eLI.addClass('active');

        //set the parent menu item active too if necessary
        var pmi = this.parentMenu.parentMenuItem;
        if ( pmi && !pmi.active )
          pmi.setActive();

        activeItem = this;
      },
      setInactive : function()
      {
        this.active = false;
        this.$eLI.removeClass('active');
        if ( this == activeItem )
          activeItem = null;
      },
      enable : function()
      {
        this.$eLI.removeClass('disabled');
        this.enabled = true;
      },
      disable : function()
      {
        this.$eLI.addClass('disabled');
        this.enabled = false;
      },
      destroy : function()
      {
        this.removeTimer();

        this.$eLI.remove();

        //unbind events
        this.$eLI.unbind('mouseover').unbind('mouseout').unbind('click');
        //delete submenu
        if ( this.subMenu )
        {
          this.subMenu.destroy();
          delete this.subMenu;
        }
        this.parentMenu.removeItem(this);
      },
      addSubMenu : function(menu)
      {
        if ( this.subMenu )
          return;
        this.subMenu = menu;
        if ( this.parentMenu && $.inArray(menu, this.parentMenu.subMenus) == -1 )
          this.parentMenu.subMenus.push(menu);
        if ( this.settings.arrowSrc )
        {
          var a = arrowElement.cloneNode(0);
          a.setAttribute('src', this.settings.arrowSrc);
          this.$eLI[0].firstChild.appendChild(a);
        }
      }
    }
  });


  $.extend($.fn, {
    menuFromElement : function(options, list, bar)
    {
      var createItems = function(ul)
      {
        var menuItems = [],
          subItems,
          menuItem,
          lis, $li, i, subUL, submenu, target,
          classNames = null;

        lis = getAllChilds(ul, 'LI');
        for ( i = 0; i < lis.length; i++ )
        {
          subItems = [];

          if ( !lis[i].childNodes.length ) //empty item? add separator
          {
            menuItems.push(new $.MenuItem('', options));
            continue;
          }

          if ( (subUL = getOneChild(lis[i], 'UL')) )
          {
            subItems = createItems(subUL);
            //remove subUL from DOM
            $(subUL).remove();
          }

          //select the target...get the elements inside the li
          $li = $(lis[i]);
          if ( $li[0].childNodes.length == 1 && $li[0].childNodes[0].nodeType == 3 )
            target = $li[0].childNodes[0].nodeValue;
          else
            target = $li[0].childNodes;

          if ( options && options.copyClassAttr )
            classNames = $li.attr('class');

          //create item
          menuItem = new $.MenuItem({src: target, addClass: classNames}, options);
          menuItems.push(menuItem);
          //add submenu
          if ( subItems.length )
            new $.Menu(menuItem, subItems, options);

        }
        return menuItems;
      };
      return this.each(function()
      {
        var ul, m;
        //get the list element
        if ( list || (ul = getOneChild(this, 'UL')) )
        {
          //if a specific list element is used, clone it, as we probably need it more than once
          ul = list ? $(list).clone(true)[0] : ul;
          menuItems = createItems(ul);
          if ( menuItems.length )
          {
            m = new $.Menu(this, menuItems, options);
            if ( bar )
              bar.addMenu(m);
          }
          $(ul).hide();
        }
      });
    },
    menuBarFromUL : function(options)
    {
      return this.each(function()
      {
        var i,
          lis = getAllChilds(this, 'LI');
        if ( lis.length )
        {
          bar = new $.MenuCollection();
          for ( i = 0; i < lis.length; i++ )
            $(lis[i]).menuFromElement(options, null, bar);
        }
      });
    },
    menu : function(options, items)
    {
      return this.each(function()
      {
        if ( items && $.isArray(items) )
          new $.Menu(this, items, options);
        else
        {
          if ( this.nodeName.toUpperCase() == 'UL' )
            $(this).menuBarFromUL(options);
          else
            $(this).menuFromElement(options, items);
        }
      });
    }
  });

  //faster than using jquery
  var getOneChild = function(elem, name)
  {
    if ( !elem )
      return null;

    var n = elem.firstChild;
    for ( ; n; n = n.nextSibling )
    {
      if ( n.nodeType == 1 && n.nodeName.toUpperCase() == name )
        return n;
    }
    return null;
  };
  //faster than using jquery
  var getAllChilds = function(elem, name)
  {
    if ( !elem )
      return [];

    var r = [],
      n = elem.firstChild;
    for ( ; n; n = n.nextSibling )
    {
      if ( n.nodeType == 1 && n.nodeName.toUpperCase() == name )
        r[r.length] = n;
    }
    return r;
  };

})(jQuery);

  }-*/;
}
