(function($){

  $.fn.contextMenu = function(menu, onShow) {
    var data = { menu: menu, onShow: onShow };

    this.bind("contextmenu", data, onContextMenu);
    this.bind("click", data, onClick);
  }

  // TODO: Replace with $.extend(true, ...) when deep copy
  // bug is fixed: http://dev.jquery.com/ticket/1562

  function merge(target) {
    if (!target) target = new Object();

    for(var j = 1; j < arguments.length; j++) {
      var source = arguments[j];

      for(var i in source) {
        if (source[i] == null) continue;
        switch(typeof source[i]) {
          case "string":
          case "number":
          case "boolean":
          case "function":
              target[i] = source[i];
              break;
          default:
              target[i] = merge(target[i], source[i]);
              break;
        }
      }
    }
    return target;
  }

  // Only one context menu should
  // be visible at a time
  var currentMenu;

  function onContextMenu(e) {
    closeCurrentMenu();

    if (e.data.onShow &&
        e.data.onShow.apply(this, [e]) == false) return;
                                  
    currentMenu = e.data.menu;
    $(document).bind("click", currentMenu, closeCurrentMenu);
    $(window).bind("blur", currentMenu, closeCurrentMenu);

    hideMenu(e.data.menu);
    showMenu(e.data.menu, e.pageX, e.pageY);
    return false;
  }

  function closeCurrentMenu() {
    if (!currentMenu) return;

    $(document).unbind("click", currentMenu, closeCurrentMenu);
    $(window).unbind("blur", currentMenu, closeCurrentMenu);
    hideMenu(currentMenu);
    currentMenu = null;
  }

  function onClick(e) {
    return onContextMenu(e);
  }

  function showMenu(menu, x, y) {
    menu = $(menu);
    if (menu.is(":visible")) return;

    menu.find("ul")
        .addClass('contextMenu')
        .prev("li")
        .addClass('containerItem')
        .end()
        .parent("li")
        .addClass('containerItem');

    menu.find("li")
        .addClass('menuItem')
        .removeClass('hoverItem')
        .bind("click", onItemClick)
        .bind("mouseenter", onItemEnter);

    menu.addClass('contextMenu')
        .css("left", x)
        .css("top", y)
        .show();
  }

  // Hide submenus in reverse order

  function hideMenu(menu) {
    var submenus = $(menu).find("ul")
                          .get()
                          .reverse();
    $(submenus).hide().removeClass('hoverItem');
    $(menu).hide().removeClass('hoverItem');
  }

  function getSubmenu(item) {
    item = $(item);
    var submenu = item.next();
    if (!submenu.is("ul")) {
      submenu = item.children();
    }
    if (submenu.is("ul")) {
      return submenu;
    }
  }

  // Return false for items that have submenus
  // So that clicking on such items doesn't close menu

  function onItemClick(e) {
    if (this == e.target) {
      return !getSubmenu(this);
    }
  }

  function onItemEnter(e) {
    var self = this;
    var menu = $(this).parent();

    menu.children("li").each(function() {
      var item = $(this);
      var submenu = getSubmenu(item);
      if (submenu) {
        var x = menu.width();
        var y = item.position().top - 1;
        self == this ? showMenu(submenu, x, y) : hideMenu(submenu);
      }
      if(self == this)
      	item.addClass('hoverItem');
      else
        item.removeClass('hoverItem');
    });
  }
})(jQuery);
