//
//  WBMenuManager.mm
//  MySQLWorkbench
//
//  Created by Alfredo Kojima on 3/Oct/08.
//  Copyright 2008 Sun Microsystems Inc. All rights reserved.
//

#import "WBMenuManager.h"
#import "MCPPUtilities.h"

#include "incremental_list_updater.h"


// identifiers from the App menu. These are set in the nib
#define APP_MENU_ABOUT 100
#define APP_MENU_PREFERENCES 101
#define APP_MENU_QUIT 102



// In MacOS X the following items are in the app menu so they are not managed by the menu manager:
// - About
// - Preferences...
// - Quit


@implementation WBMenuItem

- (id)initWithItem:(const bec::MenuItem&)item action:(SEL)action
{
  self= [super initWithTitle:[NSString stringWithUTF8String:item.caption.c_str()]
                      action:action
               keyEquivalent:@""];
  if (self != nil)
  {
    _command= [[NSString stringWithUTF8String:item.name.c_str()] retain];
    
    if (!item.shortcut.empty())
    {
      std::vector<std::string> parts(bec::split_string(item.shortcut, "+", 0));
      std::string key;
      NSUInteger mask= 0;
      
      if (parts.size() > 0)
      {
        key= parts.back();
        parts.pop_back();
        
        if (key.length() == 1)
        {
          key[0]= tolower(key[0]);
        }
        else
        {
          if (key == "Delete")
            key= NSDeleteCharacter;
          else if (key == "BackSpace" || key == "Backspace")
            key= NSBackspaceCharacter;
          else if (key == "F1")
            key= NSF1FunctionKey;
          else if (key == "F2")
            key= NSF2FunctionKey;
          else if (key == "F3")
            key= NSF3FunctionKey;
          else if (key == "F4")
            key= NSF4FunctionKey;
          else if (key == "F5")
            key= NSF5FunctionKey;
          else if (key == "F6")
            key= NSF6FunctionKey;
          else if (key == "F7")
            key= NSF7FunctionKey;
          else if (key == "F8")
            key= NSF8FunctionKey;
          else if (key == "F9")
            key= NSF9FunctionKey;
          else if (key == "F10")
            key= NSF10FunctionKey;
          else if (key == "F11")
            key= NSF11FunctionKey;
          else if (key == "F12")
            key= NSF12FunctionKey;
          else if (key == "Enter" || key == "Return")
            key= "\n";
          else
            NSLog(@"Unknown character '%s'", key.c_str());
        }
        
        [self setKeyEquivalent:[NSString stringWithUTF8String:key.c_str()]];
      }
  
      if (parts.size() > 0)
      {
        for (std::vector<std::string>::const_iterator iter= parts.begin();
             iter != parts.end(); ++iter)
        {
          if (*iter == "Command" || *iter == "Modifier")
            mask|= NSCommandKeyMask;
          else if (*iter == "Alt" || *iter == "Alternate" || *iter == "Option")
            mask|= NSAlternateKeyMask;
          else if (*iter == "Control")
            mask|= NSControlKeyMask;
          else if (*iter == "Shift")
            mask|= NSShiftKeyMask;
        }
        if (mask != 0)
          [self setKeyEquivalentModifierMask:mask];
      }
    }
    
    [self update:item];
  }
  return self;
}


- (void)update:(const bec::MenuItem&)item
{
  if (strcmp([[self title] UTF8String], item.caption.c_str())!=0)
    [self setTitle:[NSString stringWithUTF8String:item.caption.c_str()]];
  
  [self setEnabled:item.enabled];
  if (item.type == bec::MenuCheck || item.type == bec::MenuRadio)
    [self setState:item.checked ? NSOnState : NSOffState];  
}


- (void)setCommand:(NSString*)command
{
  [_command autorelease];
  _command= command;
}


- (NSString*)command
{
  return _command;
}

- (void)dealloc
{
  [_command release];
  [super dealloc];
}

@end



@implementation WBMenuManager


class IncrementalMenuRebuilder 
: public bec::IncrementalListUpdater<NSUInteger,NSMenuItem*,wb::MenuItemList::const_iterator>
{
  WBMenuManager *_manager;
  NSMenu *_menu;
  NSArray *_menuItems;
  const wb::MenuItemList &_items;
  
public:
  IncrementalMenuRebuilder(WBMenuManager *manager, NSMenu *menu, const wb::MenuItemList &items)
  : _manager(manager), _menu(menu), _items(items)
  {
    _menuItems= [[_menu itemArray] retain];
  }
  
  virtual ~IncrementalMenuRebuilder()
  {
    [_menuItems release];
  }
  
  
  virtual dest_iterator get_dest_iterator()
  {
    return 0;
  }
  
  virtual source_iterator get_source_iterator()
  {
    return _items.begin();
  }
  
  virtual dest_iterator increment_dest(dest_iterator &iter)
  {
    return ++iter;
  }
  
  virtual source_iterator increment_source(source_iterator &iter)
  {
    return ++iter;
  }
  
  virtual bool has_more_dest(dest_iterator iter)
  {
    return iter < [_menuItems count];
  }
  
  virtual bool has_more_source(source_iterator iter)
  {
    return iter != _items.end();
  }
  
  virtual bool items_match(dest_iterator diter, source_iterator siter)
  {
    NSMenuItem *item= [_menuItems objectAtIndex:diter];
    NSString *oid= [item representedObject];
    return oid && (strcmp(siter->oid.c_str(), [oid UTF8String])==0);
  }
  
  virtual dest_ref get_dest(dest_iterator iter)
  {
    return [_menuItems objectAtIndex:iter];
  }

  
  virtual dest_iterator begin_adding()
  {
    return 0;
  }
  
  
  virtual dest_iterator add(dest_iterator &iter, source_iterator source_item)
  {    
    NSMenuItem *newItem;
    
    if (source_item->type == bec::MenuSeparator)
    {
      newItem= [NSMenuItem separatorItem];
    }
    else
    {
      newItem= [[[WBMenuItem alloc] initWithItem:*source_item action:@selector(activateItem:)] autorelease];
      [newItem setTarget:_manager];
    }
    [newItem setRepresentedObject:[NSString stringWithUTF8String:source_item->oid.c_str()]];
    
    if (source_item->type == bec::MenuCascade)
    {
      NSMenu *submenu= [[[NSMenu alloc] initWithTitle:[newItem title]] autorelease];
      wb::MenuItemList subItems= _manager->_wbui->get_command_ui()->get_menu_items(source_item->name);
      [newItem setSubmenu:submenu];
      
      [submenu setAutoenablesItems:NO];
      
      if (!subItems.empty())
      {
        IncrementalMenuRebuilder rebuilder(_manager, submenu, subItems);
        
        rebuilder.execute();
      }
    }
    
    [_menu insertItem:newItem atIndex:iter++];
    
    return iter;
  }
  
  virtual dest_iterator add(dest_iterator &iter, dest_ref item)
  {
    [item retain];
    [_menu removeItem:item];
    [_menu insertItem:item atIndex:iter++];
    [item release];
    return iter;
  }
  
  // end adding items to the dest item, stuff after the last item added must be removed
  virtual void end_adding(dest_iterator iter)
  {
    NSArray *items= [_menu itemArray];
    NSInteger i= [items count]-1;
    
    while (i >= (NSInteger)iter)
    {
      NSMenuItem *item= [items objectAtIndex: i--];

      if ([item isKindOfClass:[WBMenuItem class]])
        [_menu removeItem:item];
    }
  }
 
  virtual void update(dest_ref item, source_iterator source_item)
  {    
    if ([item isKindOfClass:[WBMenuItem class]])
      [(WBMenuItem*)item update:*source_item];
    
    if ([item submenu])
    {
      wb::MenuItemList subItems= _manager->_wbui->get_command_ui()->get_menu_items(source_item->name);
      
      IncrementalMenuRebuilder rebuilder(_manager, [item submenu], subItems);
      
      rebuilder.execute();
    }
  }
};




- (id)initWithMenuBar:(NSMenu*)menu
            WBContext:(wb::WBContextUI*)wbui
{
  if ((self= [super init]))
  {
    _menuBar= menu;
    _wbui= wbui;
    
    // hack
    // the way to create a Edit menu that works just like the one you get in IB is a mistery
    // so we create it in IB, keep a reference to it and only display it when we need,
    // which is in modal windows. Once the modal window is gone, the normal WB 
    // created menu can be used
    _defaultEditMenu= [[_menuBar itemAtIndex: 1] retain];
    [_defaultEditMenu setMenu: nil];
    [_menuBar removeItemAtIndex: 1];
  }
  return self;
}


- (void)activateItem:(WBMenuItem*)item
{
  _wbui->get_command_ui()->activate_command([[item command] UTF8String]);
}



- (void)differentialMenuRebuild:(NSMenu*)menu
                          items:(const wb::MenuItemList&)items
{
  IncrementalMenuRebuilder rebuilder(self, menu, items);
  
  rebuilder.execute();
}


- (void)rebuildMainMenu
{
  // temporarily remove 1st item from menubar
  NSMenuItem *firstItem= [[_menuBar itemAtIndex:0] retain];
  [_menuBar removeItemAtIndex:0];
  [self differentialMenuRebuild:_menuBar items:_wbui->get_command_ui()->get_main_menus()];
  
  [_menuBar insertItem:firstItem atIndex:0];
  [firstItem release];

  {
    NSMenu *appMenu= [[_menuBar itemAtIndex: 0] submenu];

    // setup actions for the Application menu entries
    id item= [appMenu itemWithTag:APP_MENU_ABOUT];
    [item setAction:@selector(activateItem:)];
    [item setTarget:self];
    [item setCommand: @"builtin:show_about"]; 
  
    item= [appMenu itemWithTag:APP_MENU_PREFERENCES];
    [item setAction:@selector(activateItem:)];
    [item setTarget:self];
    [item setCommand: @"plugin:wb.form.showOptions"]; 
    
    item= [appMenu itemWithTag:APP_MENU_QUIT];
    [item setAction:@selector(activateItem:)];
    [item setTarget:self];
    [item setCommand: @"plugin:wb.file.exit"];
  }
  
  if (![[NSApp keyWindow] isMainWindow])
    [self rebuildMainMenuForAuxiliaryWindow];
}


- (void)rebuildMainMenuForAuxiliaryWindow
{
  // because because modal loops disable menus that dont have the modal window as the target
  // we need this hack to make the Edit menu point directly to the modal window

  if ([_menuBar numberOfItems] > 2)
  {
    NSMenuItem *editMenu= [_menuBar itemAtIndex: 2];
    if (editMenu != _defaultEditMenu)
    {
      [_menuBar removeItemAtIndex: 2];
      [_menuBar insertItem: _defaultEditMenu atIndex: 2];
    }
  }
}


- (void)refreshMenu:(NSMenu*)menu withItems:(const wb::MenuItemList&)items;
{
  // diff menu reubilder is kind of broken, so we just reset the whole thing
  while ([menu numberOfItems] > 0) [menu removeItemAtIndex:0];
  [self differentialMenuRebuild:menu items:items];
}


+ (void)fillMenu:(NSMenu*)menu withItems:(const wb::MenuItemList&)items 
        selector:(SEL)selector target:(id)target
{
  int oldCount= [menu numberOfItems];
  
  for (bec::MenuItemList::const_iterator iter= items.begin();
       iter != items.end(); ++iter)
  {
    if (iter->type == bec::MenuAction)
    {
      NSMenuItem *item= [[NSMenuItem alloc] initWithTitle:[NSString stringWithCPPString: iter->caption]
                                                   action:selector
                                            keyEquivalent:@""];
      [item setTarget: target];
      [menu addItem: item];
      [item setRepresentedObject: [NSString stringWithCPPString: iter->name]];
      [item release];
    }
    else if (iter->type == bec::MenuSeparator)
      [menu addItem: [NSMenuItem separatorItem]];
    else
      NSLog(@"unknown context menu item type in %s", iter->name.c_str());
  }
  
  for (int i= oldCount-1; i >= 0; i--)
    [menu removeItemAtIndex:0];
}
@end
