/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX)
   Copyright (C) 2006 and later, Cockos, Inc.

    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.

    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software. If you use this software
       in a product, an acknowledgment in the product documentation would be
       appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
  

    This file provides basic windows menu API to interface an NSMenu

  */

#ifndef SWELL_PROVIDED_BY_APP

#import <Cocoa/Cocoa.h>

#include "swell.h"
#include "swell-menugen.h"

#include "swell-internal.h"


static void __filtnametobuf(char *out, const char *in, int outsz, NSString **eqout=NULL, unsigned int *eqmaskout=NULL);


bool SetMenuItemText(HMENU hMenu, int idx, int flag, const char *text)
{
  NSMenu *menu=(NSMenu *)hMenu;
  if (WDL_NOT_NORMALLY(!menu)) return false;
  
  NSMenuItem *item;
  if (flag & MF_BYPOSITION) item=[menu itemAtIndex:idx];
  else item =[menu itemWithTag:idx];
  if (!item) 
  {
    if (!(flag & MF_BYPOSITION))
    {
      const int n = (int) [menu numberOfItems];
      for (int x = 0; x < n; x ++)
      {
        item=[menu itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          NSMenu *m=[item submenu];
          if (m && SetMenuItemText((HMENU)m,idx,flag,text)) return true;
        }
      }
    }
    return false;
  }
  char buf[1024];
  NSString *eq = NULL;
  unsigned int eqmask = 0;
  __filtnametobuf(buf,text?text:"",sizeof(buf),
      (flag&MF_SWELL_DO_NOT_CALC_MODIFIERS) ? NULL : &eq,&eqmask);
  NSString *label=(NSString *)SWELL_CStringToCFString(buf);
  
  [item setTitle:label];
  if ([item hasSubmenu] && [item submenu]) [[item submenu] setTitle:label];
  if (eq)
  {
    [item setKeyEquivalentModifierMask:eqmask];
    [item setKeyEquivalent:eq];
  }

  [label release];
  return true;
}

bool EnableMenuItem(HMENU hMenu, int idx, int en)
{
  NSMenu *menu=(NSMenu *)hMenu;
  if (WDL_NOT_NORMALLY(!menu)) return false;
  
  NSMenuItem *item;
  if (en & MF_BYPOSITION) item=[menu itemAtIndex:idx];
  else item =[menu itemWithTag:idx];
  if (!item) 
  {
    if (!(en & MF_BYPOSITION))
    {
      const int n=(int)[menu numberOfItems];
      for (int x = 0; x < n; x ++)
      {
        item=[menu itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          NSMenu *m=[item submenu];
          if (m && EnableMenuItem((HMENU)m,idx,en)) return true;
        }
      }
    }
    return false;
  }
  [item setEnabled:((en&MF_GRAYED)?NO:YES)];
  return true;
}

bool CheckMenuItem(HMENU hMenu, int idx, int chk)
{
  NSMenu *menu=(NSMenu *)hMenu;
  if (WDL_NOT_NORMALLY(!menu)) return false;
  
  NSMenuItem *item;
  if (chk & MF_BYPOSITION) item=[menu itemAtIndex:idx];
  else item =[menu itemWithTag:idx];
  if (!item) 
  {
    if (!(chk & MF_BYPOSITION))
    {
      const int n=(int)[menu numberOfItems];
      for (int x = 0; x < n; x ++)
      {
        item=[menu itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          NSMenu *m=[item submenu];
          if (m && CheckMenuItem((HMENU)m,idx,chk)) return true;
        }
      }
    }
    return false;  
  }
  [item setState:((chk&MF_CHECKED)?NSOnState:NSOffState)];
  
  return true;
}
HMENU SWELL_GetCurrentMenu()
{
  return (HMENU)[NSApp mainMenu];
}

extern int g_swell_terminating;

void SWELL_SetCurrentMenu(HMENU hmenu)
{
  if (WDL_NORMALLY(hmenu && [(id)hmenu isKindOfClass:[NSMenu class]]))
  {
    if (!g_swell_terminating) [NSApp setMainMenu:(NSMenu *)hmenu];
  }
}

HMENU GetSubMenu(HMENU hMenu, int pos)
{
  NSMenu *menu=(NSMenu *)hMenu;
  WDL_ASSERT(menu != NULL);
  
  NSMenuItem *item=menu && pos >=0 && pos < [menu numberOfItems] ? [menu itemAtIndex:pos] : 0; 
  if (item && [item hasSubmenu]) return (HMENU)[item submenu];
  return 0;
}

int GetMenuItemCount(HMENU hMenu)
{
  NSMenu *menu=(NSMenu *)hMenu;
  WDL_ASSERT(menu != NULL);
  return (int)[menu numberOfItems];
}

int GetMenuItemID(HMENU hMenu, int pos)
{
  NSMenu *menu=(NSMenu *)hMenu;
  if (pos < 0 || pos >= (int)[menu numberOfItems])
  {
    WDL_ASSERT(pos==0); // don't assert if GetMenuItemID(0) is called on an empty menu
    return 0;
  }
  
  NSMenuItem *item=[menu itemAtIndex:pos]; 
  if (item) 
  {
    if ([item hasSubmenu]) return -1;
    return (int)[item tag];
  }
  return 0;
}

bool SetMenuItemModifier(HMENU hMenu, int idx, int flag, int code, unsigned int mask)
{
  if (WDL_NOT_NORMALLY(hMenu == NULL)) return false;
  
  NSMenu *menu=(NSMenu *)hMenu;
  
  NSMenuItem *item;
  if (flag & MF_BYPOSITION) item=[menu itemAtIndex:idx];
  else item =[menu itemWithTag:idx];
  if (!item) 
  {
    if (!(flag & MF_BYPOSITION))
    {
      const int n = (int)[menu numberOfItems];
      for (int x = 0; x < n; x ++)
      {
        item=[menu itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          NSMenu *m=[item submenu];
          if (m && SetMenuItemModifier((HMENU)m,idx,flag,code,mask)) return true;
        }
      }
    }
    return false;
  }

  bool suppressShift = false;
  unichar arrowKey = 0;

  if (code >= 'A' && code <= 'Z')
  {
    arrowKey = (mask & FSHIFT) ? code : (code + 'a' - 'A');
    suppressShift=true;
  }
  else if ((code>='0' && code <= '9') ||
           code== ' ' ||
           (!(mask&FVIRTKEY) && (code >= '!' && code <= '~')))
  {
    arrowKey=code;
  }
  else if (code >= VK_F1 && code <= VK_F24)
  {
    arrowKey = NSF1FunctionKey + code - VK_F1;
  }
  else switch (code&0xff)
  {
    #define DEFKP(wink,mack) case wink: arrowKey = mack; break;
    DEFKP(VK_UP,NSUpArrowFunctionKey)
    DEFKP(VK_DOWN,NSDownArrowFunctionKey)
    DEFKP(VK_LEFT,NSLeftArrowFunctionKey)
    DEFKP(VK_RIGHT,NSRightArrowFunctionKey)
    DEFKP(VK_INSERT,NSInsertFunctionKey)
    DEFKP(VK_DELETE,NSDeleteCharacter)
    DEFKP(VK_BACK,NSBackspaceCharacter)
    DEFKP(VK_HOME,NSHomeFunctionKey)
    DEFKP(VK_END,NSEndFunctionKey)
    DEFKP(VK_NEXT,NSPageDownFunctionKey)
    DEFKP(VK_PRIOR,NSPageUpFunctionKey)
    DEFKP(VK_SUBTRACT,'-')
    DEFKP(VK_RETURN,'\r')
    DEFKP(VK_TAB,'\t')
    DEFKP(VK_ESCAPE,27)
    // hmm numpad enter, what to do: DEFKP(VK_RETURN|32768, '\r')
  }

  unsigned int mask2=0;
  if (mask&FALT) mask2|=NSAlternateKeyMask;
  if (!suppressShift) if (mask&FSHIFT) mask2|=NSShiftKeyMask;
  if (mask&FCONTROL) mask2|=NSCommandKeyMask;
  if (mask&FLWIN) mask2|=NSControlKeyMask;

  [item setKeyEquivalentModifierMask:mask2];
  [item setKeyEquivalent:arrowKey?[NSString stringWithCharacters:&arrowKey length:1]:@""];
  return true;
}

static void __filtnametobuf(char *out, const char *input, int outsz, NSString **eqout, unsigned int *eqmaskout)
{
  const char *inp = input;
  while (*inp && *inp != '\t' && outsz>1)
  {
    if (*inp == '&') inp++;
    *out++=*inp++;
    outsz--;
  }
  *out=0;
  if (eqout && eqmaskout && *inp)
  {
    while (*inp && *inp != '\t') inp++;
    if (*inp++ == '\t')
    {
again:
      while (*inp)
      {
        if (!strnicmp(inp,"Ctrl+",5)) { inp += 5; *eqmaskout |= NSCommandKeyMask; }
        else if (!strnicmp(inp,"Alt+",4)) { inp += 4; *eqmaskout |= NSAlternateKeyMask; }
        else if (!strnicmp(inp,"Shift+",6)) { inp += 6; *eqmaskout |= NSShiftKeyMask; }
        else if (!strnicmp(inp,"Win+",4)) { inp += 4; *eqmaskout |= NSControlKeyMask; }
        else break;
      }
      if (WDL_NOT_NORMALLY(!*inp)) return;

      char tmp[128];
      lstrcpyn(tmp,inp,sizeof(tmp));
      char *p = tmp;
      while (*p && *p != ' ' && *p != ',') p++;
      bool was_comma = *p == ',';
      if (was_comma && p == tmp) p++;
      *p=0;
      if (WDL_NOT_NORMALLY(!tmp[0])) return;

      unichar arrowKey = 0;
      int idx;
      if (tmp[0] == 'F' && (idx=atoi(tmp+1))> 0 && idx <= 24)
        arrowKey = NSF1FunctionKey + idx - 1;
      else if (tmp[0] >= '0' && tmp[0] <= '9')
        arrowKey = tmp[0];
      else
      {
        if (!stricmp(tmp,"Up")) arrowKey = NSUpArrowFunctionKey;
        else if (!stricmp(tmp,"Down")) arrowKey = NSDownArrowFunctionKey;
        else if (!stricmp(tmp,"Left")) arrowKey = NSLeftArrowFunctionKey;
        else if (!stricmp(tmp,"Right")) arrowKey = NSRightArrowFunctionKey;
        else if (!strnicmp(tmp,"Ins",3))
        {
          if (was_comma)
          {
            while (*inp && *inp != ',') inp++;
            if (*inp)
            {
              inp++;
              while (*inp == ' ') inp++;
              if (*inp)
              {
                *eqmaskout = 0;
                goto again;
              }
            }
          }

          arrowKey = NSInsertFunctionKey;
        }
        else if (!strnicmp(tmp,"Del",3)) arrowKey = NSDeleteFunctionKey;
        else if (!strnicmp(tmp,"Back",4)) arrowKey = NSBackspaceCharacter;
        else if (!stricmp(tmp,"Home")) arrowKey = NSHomeFunctionKey;
        else if (!stricmp(tmp,"End")) arrowKey = NSEndFunctionKey;
        else if (!stricmp(tmp,"PageDown") || !stricmp(tmp,"PgDn")) arrowKey = NSPageDownFunctionKey;
        else if (!stricmp(tmp,"PageUp") || !stricmp(tmp,"PgUp")) arrowKey = NSPageUpFunctionKey;
        else if (!stricmp(tmp,"PageUp") || !stricmp(tmp,"PgUp")) arrowKey = NSPageUpFunctionKey;
        else if (!stricmp(tmp,"-")) arrowKey = '-';
        else if (!stricmp(tmp,"+")) arrowKey = '+';
        else if (!stricmp(tmp,"Enter")||!stricmp(tmp,"Return")) arrowKey = '\r';
        else if (!stricmp(tmp,"Tab")) arrowKey = '\t';
        else if (!strnicmp(tmp,"Esc",3)) arrowKey = 27;
        else if (!strcmp(tmp,",")) arrowKey = tmp[0];
        else if (!stricmp(tmp,"Click")||!stricmp(tmp,"(Click)")) arrowKey = NSUpArrowFunctionKey;
        else if (!stricmp(tmp,"Doubleclick")||!stricmp(tmp,"(Doubleclick)")) arrowKey = NSHomeFunctionKey;
        else if (tmp[0] >= 'A' && tmp[0] <= 'Z' && !tmp[1])
        {
          arrowKey = tmp[0];
          if (*eqmaskout & NSShiftKeyMask)
            *eqmaskout &= ~NSShiftKeyMask;
          else
            arrowKey += 'a' - 'A';
        }
        else if (!strcmp(tmp,"Ctrl")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSCommandKeyMask; }
        else if (!strcmp(tmp,"Alt")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSAlternateKeyMask; }
        else if (!strcmp(tmp,"Shift")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSShiftKeyMask; }
        else if (!strcmp(tmp,"Win")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSControlKeyMask; }
        else
        {
          wdl_log("swell-cocoa: unknown key in menu text: '%s' orig '%s'\n",tmp,input);
        }
      }
      if (arrowKey) *eqout = [NSString stringWithCharacters:&arrowKey length:1];
    }
  }
}

// #define SWELL_MENU_ACCOUNTING

#ifdef SWELL_MENU_ACCOUNTING
struct menuTmp
{
  NSMenu *menu;
  NSString *lbl;
};

WDL_PtrList<menuTmp> allMenus;
#endif

@implementation SWELL_Menu
- (id)copyWithZone:(NSZone *)zone
{
  id rv = [super copyWithZone:zone];
#ifdef SWELL_MENU_ACCOUNTING
  if (rv)
  {
    menuTmp *mt = new menuTmp;
    mt->menu=(NSMenu *)rv;
    NSString *lbl = [(SWELL_Menu *)rv title];
    mt->lbl = lbl;
    [lbl retain];
    allMenus.Add(mt);
    NSLog(@"copy menu, new count=%d lbl=%@\n",allMenus.GetSize(),lbl);
  }
#endif
  return rv;
}
-(void)dealloc
{
#ifdef SWELL_MENU_ACCOUNTING
  int x;
  bool f=false;
  for(x=0;x<allMenus.GetSize();x++)
  {
    if (allMenus.Get(x)->menu == self)
    {
      NSLog(@"dealloc menu, found self %@\n",allMenus.Get(x)->lbl);
      allMenus.Delete(x);
      f=true;
      break;
    }
  }

  NSLog(@"dealloc menu, new count=%d %@\n",allMenus.GetSize(), [self title]);
  if (!f) 
  {
    NSLog(@"deleting unfound menu!!\n");
  }
#endif
  [super dealloc];
}
@end

HMENU CreatePopupMenu()
{
  return CreatePopupMenuEx(NULL);
}
HMENU CreatePopupMenuEx(const char *title)
{
  SWELL_Menu *m;
  if (title)
  {
    char buf[1024];
    __filtnametobuf(buf,title,sizeof(buf));
    NSString *lbl=(NSString *)SWELL_CStringToCFString(buf);
    m=[[SWELL_Menu alloc] initWithTitle:lbl];
#ifdef SWELL_MENU_ACCOUNTING
    menuTmp *mt = new menuTmp;
    mt->menu=m;
    mt->lbl = lbl;
    [lbl retain];
    allMenus.Add(mt);
    NSLog(@"alloc menu, new count=%d lbl=%@\n",allMenus.GetSize(),lbl);
#endif
    [lbl release];
  }
  else
  {
    m=[[SWELL_Menu alloc] init];
#ifdef SWELL_MENU_ACCOUNTING
    menuTmp *mt = new menuTmp;
    mt->menu=m;
    mt->lbl = @"<none>";
    allMenus.Add(mt);
    NSLog(@"alloc menu, new count=%d lbl=%@\n",allMenus.GetSize(),@"<none>");
#endif
  }
  [m setAutoenablesItems:NO];

  return (HMENU)m;
}

void DestroyMenu(HMENU hMenu)
{
  if (WDL_NORMALLY(hMenu))
  {
    SWELL_SetMenuDestination(hMenu,NULL);
    NSMenu *m=(NSMenu *)hMenu;
    [m release];
  }
}



int AddMenuItem(HMENU hMenu, int pos, const char *name, int tagid)
{
  if (WDL_NOT_NORMALLY(!hMenu)) return -1;
  NSMenu *m=(NSMenu *)hMenu;
  NSString *label=(NSString *)SWELL_CStringToCFString(name); 
  NSMenuItem *item=[m insertItemWithTitle:label action:NULL keyEquivalent:@"" atIndex:pos];
  [label release];
  [item setTag:tagid];
  [item setEnabled:YES];
  return 0;
}

bool DeleteMenu(HMENU hMenu, int idx, int flag)
{
  if (WDL_NOT_NORMALLY(!hMenu)) return false;
  NSMenu *m=(NSMenu *)hMenu;
  NSMenuItem *item=NULL;
  
  if (flag&MF_BYPOSITION)
  {
    if (idx >=0 && idx < [m numberOfItems])
      item=[m itemAtIndex:idx];
    if (!item) return false;
  }
  else
  {
    item=[m itemWithTag:idx];
    if (!item) 
    {
      const int n = (int) [m numberOfItems];
      for (int x=0;x<n;x++)
      {
        item=[m itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          if (DeleteMenu((HMENU)[item submenu],idx,flag)) return true;
        }
      }
      return false;
    }
  }
  
  if ([item hasSubmenu])
  {
    HMENU sm = (HMENU)[item submenu];
    if (sm) SWELL_SetMenuDestination(sm,NULL);
    [m setSubmenu:nil forItem:item];
  }
  [m removeItem:item];
  return true;
}


BOOL SetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi)
{
  if (WDL_NOT_NORMALLY(!hMenu)) return 0;
  NSMenu *m=(NSMenu *)hMenu;
  NSMenuItem *item;
  if (byPos) item=[m itemAtIndex:pos];
  else item=[m itemWithTag:pos];

  if (!item) 
  {
    if (!byPos)
    {
      const int n = (int)[m numberOfItems];
      for (int x = 0; x < n; x ++)
      {
        item=[m itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          NSMenu *m1=[item submenu];
          if (m1 && SetMenuItemInfo((HMENU)m1,pos,byPos,mi)) return true;
        }
      }      
    }
    return 0;
  }
  
  if (mi->fMask & MIIM_TYPE)
  {
    if ((mi->fType &~ MFT_RADIOCHECK) == MFT_STRING && mi->dwTypeData)
    {
      char buf[1024];
      NSString *eq=NULL;
      unsigned int eqmask = 0;
      __filtnametobuf(buf,mi->dwTypeData?mi->dwTypeData:"(null)",sizeof(buf),
          (mi->fMask & MIIM_SWELL_DO_NOT_CALC_MODIFIERS) ? NULL : &eq,&eqmask);
      NSString *label=(NSString *)SWELL_CStringToCFString(buf); 
      
      [item setTitle:label];

      if ([item hasSubmenu])
      {
        NSMenu *subm=[item submenu];
        if (subm) [subm setTitle:label];
      }
      if (eq)
      {
        [item setKeyEquivalentModifierMask:eqmask];
        [item setKeyEquivalent:eq];
      }
      
      [label release];      
    }
  }
  if (mi->fMask & MIIM_SUBMENU) 
  {
    NSMenu *oldMenu = [item hasSubmenu] ? [item submenu] : NULL;
    NSMenu *newMenu = (NSMenu*)mi->hSubMenu;
    if (oldMenu != newMenu)
    {
      if (oldMenu) [oldMenu retain]; // we do not destroy the old menu, caller responsibility

      if (newMenu) [newMenu setTitle:[item title]];
      [m setSubmenu:newMenu forItem:item];
      if (newMenu) [newMenu release]; // let the parent menu free it
    }
  }

  if (mi->fMask & MIIM_STATE)
  {
    [item setState:((mi->fState&MFS_CHECKED)?NSOnState:NSOffState)];
    [item setEnabled:((mi->fState&MFS_GRAYED)?NO:YES)];
  }
  if (mi->fMask & MIIM_ID)
  {
    [item setTag:mi->wID];
  }
  if (mi->fMask & MIIM_DATA)
  {
    SWELL_DataHold* newh = [[SWELL_DataHold alloc] initWithVal:(void*)mi->dwItemData];
    [item setRepresentedObject:newh]; 
    [newh release];    
  }
  
  return true;
}

BOOL GetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi)
{
  if (WDL_NOT_NORMALLY(!hMenu)) return 0;
  NSMenu *m=(NSMenu *)hMenu;
  NSMenuItem *item;
  if (byPos)
  {
    item=[m itemAtIndex:pos];
  }
  else item=[m itemWithTag:pos];
  
  if (!item) 
  {
    if (!byPos)
    {
      const int n = (int)[m numberOfItems];
      for (int x = 0; x < n; x ++)
      {
        item=[m itemAtIndex:x];
        if (item && [item hasSubmenu])
        {
          NSMenu *m1=[item submenu];
          if (m1 && GetMenuItemInfo((HMENU)m1,pos,byPos,mi)) return true;
        }
      }      
    }
    return 0;
  }
  
  if (mi->fMask & MIIM_TYPE)
  {
    if ([item isSeparatorItem]) mi->fType = MFT_SEPARATOR;
    else
    {
      mi->fType = MFT_STRING;
      if (mi->dwTypeData && mi->cch)
      {
        mi->dwTypeData[0]=0;
        SWELL_CFStringToCString([item title], (char *)mi->dwTypeData, mi->cch);
      }
    }
  }
  
  if (mi->fMask & MIIM_DATA)
  {
    SWELL_DataHold *h=[item representedObject];
    mi->dwItemData =  (INT_PTR)(h && [h isKindOfClass:[SWELL_DataHold class]]? [h getValue] : 0);
  }
  
  if (mi->fMask & MIIM_STATE)
  {
    mi->fState=0;
    if ([item state]) mi->fState|=MFS_CHECKED;
    if (![item isEnabled]) mi->fState|=MFS_GRAYED;
  }
  
  if (mi->fMask & MIIM_ID)
  {
    mi->wID = (unsigned int)[item tag];
  }
  
  if(mi->fMask & MIIM_SUBMENU)
  {
    mi->hSubMenu = (HMENU) ([item hasSubmenu] ? [item submenu] : NULL);
  }
  
  return 1;
  
}

void SWELL_InsertMenu(HMENU menu, int pos, unsigned int flag, UINT_PTR idx, const char *str)
{
  MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING,
    (flag & ~MF_BYPOSITION),(flag&MF_POPUP) ? 0 : (unsigned int)idx,NULL,NULL,NULL,0,(char *)str};
  
  if (flag&MF_POPUP) 
  {
    mi.hSubMenu = (HMENU)idx;
    mi.fMask |= MIIM_SUBMENU;
    mi.fState &= ~MF_POPUP;
  }
  
  if (flag&MF_SEPARATOR)
  {
    mi.fMask=MIIM_TYPE;
    mi.fType=MFT_SEPARATOR;
    mi.fState &= ~MF_SEPARATOR;
  }
  
  if (flag&MF_BITMAP)
  {
    mi.fType=MFT_BITMAP;
    mi.fState &= ~MF_BITMAP;
  }
    
  InsertMenuItem(menu,pos,(flag&MF_BYPOSITION) ?  TRUE : FALSE, &mi);
}


void InsertMenuItem(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi)
{
  if (WDL_NOT_NORMALLY(!hMenu)) return;
  NSMenu *m=(NSMenu *)hMenu;
  NSMenuItem *item;
  int ni = (int)[m numberOfItems];
  
  if (!byPos) 
  {
    pos = (int)[m indexOfItemWithTag:pos];
  }
  if (pos < 0 || pos > ni) pos=ni; 
  
  NSString *label=0;
  if ((mi->fType & ~MFT_RADIOCHECK) == MFT_STRING)
  {
    char buf[1024];
    NSString *eq=NULL;
    unsigned int eqmask = 0;
    __filtnametobuf(buf,mi->dwTypeData?mi->dwTypeData:"(null)",sizeof(buf),
        (mi->fMask & MIIM_SWELL_DO_NOT_CALC_MODIFIERS) ? NULL : &eq,&eqmask);
    label=(NSString *)SWELL_CStringToCFString(buf); 
    item=[m insertItemWithTitle:label action:NULL keyEquivalent:@"" atIndex:pos];
    if (eq)
    {
      [item setKeyEquivalentModifierMask:eqmask];
      [item setKeyEquivalent:eq];
    }
  }
  else if (mi->fType == MFT_BITMAP)
  {
    item=[m insertItemWithTitle:@"(no image)" action:NULL keyEquivalent:@"" atIndex:pos];
    if (mi->dwTypeData)
    {
      NSImage *i=(NSImage *)GetNSImageFromHICON((HICON)mi->dwTypeData);
      if (i)
      {
        [item setImage:i];
        [item setTitle:@""];
      }
    }
  }
  else
  {
    item = [NSMenuItem separatorItem];
    [m insertItem:item atIndex:pos];
  }
  
  if ((mi->fMask & MIIM_SUBMENU) && mi->hSubMenu)
  {
    if (label) [(NSMenu *)mi->hSubMenu setTitle:label];
    [m setSubmenu:(NSMenu *)mi->hSubMenu forItem:item];
    [((NSMenu *)mi->hSubMenu) release]; // let the parent menu free it
  }
  if (label) [label release];
  
  if (!ni) [m setAutoenablesItems:NO];
  [item setEnabled:YES];
  
  if (mi->fMask & MIIM_STATE)
  {
    if (mi->fState&MFS_GRAYED)
    {
      [item setEnabled:NO];
    }
    if (mi->fState&MFS_CHECKED)
    {
      [item setState:NSOnState];
    }
  }
 
   if (mi->fMask & MIIM_DATA)
  {
    SWELL_DataHold *h=[[SWELL_DataHold alloc] initWithVal:(void*)mi->dwItemData];
    [item setRepresentedObject:h];
    [h release];
  }
  else
  {
    [item setRepresentedObject:nil];
  }
 
  if (mi->fMask & MIIM_ID)
  {
    [item setTag:mi->wID];
  }
  
  int i;
  ni = (int)[m numberOfItems];
  // try to find a valid action/target
  for (i = 0; i < ni; i ++)
  {
    NSMenuItem *fi=[m itemAtIndex:i];
    if (fi && fi != item)
    {
      SEL act = [fi action];
      id tgt = [fi target];
      if (act || tgt)
      {
        if (act) [item setAction:act];
        if (tgt) [item setTarget:tgt];
        break;
      }
    }
    if (i == 5 && ni > 14) i = ni-6; // only look at first and last 6 items or so
  }
}



@implementation SWELL_PopupMenuRecv
-(id) initWithWnd:(HWND)wnd
{
  if ((self = [super init]))
  {
    cbwnd=wnd;
    m_act=0;
  }
  return self;
}

-(void) onSwellCommand:(id)sender
{
  int tag=(int) [sender tag];
  if (tag)
    m_act=tag;
}

-(int) isCommand
{
  return m_act;
}

- (void)menuNeedsUpdate:(NSMenu *)menu
{
  if (cbwnd)
  {
    SendMessage(cbwnd,WM_INITMENUPOPUP,(WPARAM)menu,0);
    SWELL_SetMenuDestination((HMENU)menu,(HWND)self);
  }
}

@end

static void SWELL_SetMenuDestinationInt(NSMenu *m, HWND hwnd, bool is_top_level, bool do_skip_sub)
{
  [m setDelegate:(id)hwnd];
  const int n = (int)[m numberOfItems];
  for (int x = 0; x < n; x++)
  {
    NSMenuItem *item=[m itemAtIndex:x];
    if (item)
    {
      if ([item hasSubmenu])
      {
        NSMenu *mm=[item submenu];
        if (mm)
        {
          if (do_skip_sub) 
          {
            id del = [mm delegate];
            NSString *cn = del ? [del className] : NULL;
            if (cn)
            {
              char buf[1024];
              SWELL_CFStringToCString(cn, buf, sizeof(buf));
              if (strstr(buf,"NSServices")) continue;
            }
          }
          SWELL_SetMenuDestinationInt(mm,hwnd,false, is_top_level && !x);
        }
      }
      else
      {
        if ([item tag])
        {
          [item setTarget:(id)hwnd];
          if (hwnd) [item setAction:@selector(onSwellCommand:)];
        }
      }
    }
  }
}

void SWELL_SetMenuDestination(HMENU menu, HWND hwnd)
{
  if (WDL_NOT_NORMALLY(!menu || (hwnd && ![(id)hwnd respondsToSelector:@selector(onSwellCommand:)]))) return;

  NSMenu *m = (NSMenu *)menu, *par = [m supermenu];

  bool do_skip_sub = false;
  if (par && ![par supermenu] && [par numberOfItems]>0)
  {
    NSMenuItem *item = [par itemAtIndex:0];
    do_skip_sub = item && [item hasSubmenu] && [item submenu] == m;
  }
  SWELL_SetMenuDestinationInt(m,hwnd,!par,do_skip_sub);
}

int TrackPopupMenu(HMENU hMenu, int flags, int xpos, int ypos, int resvd, HWND hwnd, const RECT *r)
{
  ReleaseCapture(); // match win32 -- TrackPopupMenu() ends any captures
  if (WDL_NORMALLY(hMenu))
  {
    NSMenu *m=(NSMenu *)hMenu;
    NSView *v=(NSView *)hwnd;
    if (v && [v isKindOfClass:[NSWindow class]]) v=[(NSWindow *)v contentView];
    if (!v) v=[[NSApp mainWindow] contentView];
    if (!v) return 0;
    
    NSEvent *event = [NSApp currentEvent];
       
    {      
      //create a new event at these coordinates, faking it
      NSWindow *w = [v window];
      NSPoint pt = NSMakePoint(xpos, ypos);
      pt=[w convertScreenToBase:pt];
      pt.y -= 4;
      NSInteger wn = [w windowNumber]; // event ? [event windowNumber] : [w windowNumber];
      NSTimeInterval ts = event ? [event timestamp] : 0;
      NSGraphicsContext *gctx = event ? [event context] : 0;
      event = [NSEvent otherEventWithType:NSApplicationDefined location:pt modifierFlags:0 timestamp:ts windowNumber:wn context:gctx subtype:0 data1:0 data2:0];
    }
    
    SWELL_PopupMenuRecv *recv = [[SWELL_PopupMenuRecv alloc] initWithWnd:((flags & TPM_NONOTIFY) ? 0 : hwnd)];
    
    SWELL_SetMenuDestination((HMENU)m,(HWND)recv);
    
    if (!(flags&TPM_NONOTIFY)&&hwnd)
    {
      SendMessage(hwnd,WM_INITMENUPOPUP,(WPARAM)m,0);
      SWELL_SetMenuDestination((HMENU)m,(HWND)recv);
    }
    
    [NSMenu popUpContextMenu:m withEvent:event forView:v];

    int ret=[recv isCommand];    
    SWELL_SetMenuDestination((HMENU)m,(HWND)NULL);
    [recv release];
    
    if (ret<=0) return 0;
    
    if (flags & TPM_RETURNCMD) return ret;
    
    if (hwnd) SendMessage(hwnd,WM_COMMAND,ret,0);
    
    return 1;
  }
  return 0;
}




void SWELL_Menu_AddMenuItem(HMENU hMenu, const char *name, int idx, unsigned int flags)
{
  MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING,
    (unsigned int) ((flags)?MFS_GRAYED:0),(unsigned int)idx,NULL,NULL,NULL,0,(char *)name};
  if (!name)
  {
    mi.fType = MFT_SEPARATOR;
    mi.fMask&=~(MIIM_STATE|MIIM_ID);
  }
  InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi);
}

int SWELL_GenerateMenuFromList(HMENU hMenu, const void *_list, int listsz)
{
  SWELL_MenuGen_Entry *list = (SWELL_MenuGen_Entry *)_list;
  const int l1=strlen(SWELL_MENUGEN_POPUP_PREFIX);
  while (listsz>0)
  {
    int cnt=1;
    if (!list->name) SWELL_Menu_AddMenuItem(hMenu,NULL,-1,0);
    else if (!strcmp(list->name,SWELL_MENUGEN_ENDPOPUP)) break;
    else if (!strncmp(list->name,SWELL_MENUGEN_POPUP_PREFIX,l1)) 
    { 
      MENUITEMINFO mi={sizeof(mi),MIIM_SUBMENU|MIIM_STATE|MIIM_TYPE,MFT_STRING,0,0,CreatePopupMenuEx(list->name+l1),NULL,NULL,0,(char *)list->name+l1};
      cnt += SWELL_GenerateMenuFromList(mi.hSubMenu,list+1,listsz-1);
      InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi);
    }
    else SWELL_Menu_AddMenuItem(hMenu,list->name,list->idx,list->flags);

    list+=cnt;
    listsz -= cnt;
  }
  return (int) (list + 1 - (SWELL_MenuGen_Entry *)_list);
}


SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; // todo: move to per-module thingy

static SWELL_MenuResourceIndex *resById(SWELL_MenuResourceIndex *head, const char *resid)
{
  SWELL_MenuResourceIndex *p=head;
  while (p)
  {
    if (p->resid == resid) return p;
    p=p->_next;
  }
  return 0;
}

HMENU SWELL_LoadMenu(SWELL_MenuResourceIndex *head, const char *resid)
{
  SWELL_MenuResourceIndex *p;
  
  if (!(p=resById(head,resid))) return 0;
  HMENU hMenu=CreatePopupMenu();
  if (hMenu) p->createFunc(hMenu);
  return hMenu;
}

HMENU SWELL_DuplicateMenu(HMENU menu)
{
  if (WDL_NOT_NORMALLY(!menu)) return 0;
  NSMenu *ret = (NSMenu *)[(NSMenu *)menu copy];
  return (HMENU)ret;
}

BOOL  SetMenu(HWND hwnd, HMENU menu)
{
  if (WDL_NOT_NORMALLY(!hwnd||![(id)hwnd respondsToSelector:@selector(swellSetMenu:)])) return FALSE;
  if (g_swell_terminating)  return FALSE;

  [(id)hwnd swellSetMenu:(HMENU)menu];
  NSWindow *nswnd = (NSWindow *)hwnd;
  if ([nswnd isKindOfClass:[NSWindow class]] || 
     ([nswnd isKindOfClass:[NSView class]] && (nswnd=[(NSView *)nswnd window]) && hwnd == (HWND)[nswnd contentView]))
  {
    if ([NSApp keyWindow]==nswnd &&
        [NSApp mainMenu] != (NSMenu *)menu)
    {
      [NSApp setMainMenu:(NSMenu *)menu];
      if (menu) SendMessage(hwnd,WM_INITMENUPOPUP,(WPARAM)menu,0); // find a better place for this! TODO !!!
    }
  }
  
  return TRUE;
}

HMENU GetMenu(HWND hwnd)
{
  if (WDL_NOT_NORMALLY(!hwnd)) return NULL;
  if ([(id)hwnd isKindOfClass:[NSWindow class]]) hwnd = (HWND)[(NSWindow *)hwnd contentView];
  if ([(id)hwnd respondsToSelector:@selector(swellGetMenu)]) return (HMENU) [(id)hwnd swellGetMenu];
  return NULL;
}

void DrawMenuBar(HWND hwnd)
{
}


#endif
