/* Copyright (c) Mark J. Kilgard, 1994, 1997, 1998. */
/* Copyright (c) Nate Robins, 1997. */

/* This program is freely distributable without licensing fees
   and is provided without guarantee or warrantee expressed or
   implied. This program is -not- in the public domain. */

/* This file completely re-implements glut_menu.c and glut_menu2.c
   for Win32.  Note that neither glut_menu.c nor glut_menu2.c are
   compiled into Win32 GLUT. */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>

#include "glutint.h"

void (GLUTCALLBACK *__glutMenuStatusFunc) (int, int, int);
GLUTmenuItem *__glutItemSelected;
unsigned __glutMenuButton;

static GLUTmenu **menuList = NULL;
static int menuListSize = 0;
static UINT uniqueMenuHandler = 1;

/* DEPRICATED, use glutMenuStatusFunc instead. */
void GLUTAPIENTRY
glutMenuStateFunc(GLUTmenuStateCB menuStateFunc)
{
  __glutMenuStatusFunc = (GLUTmenuStatusCB) menuStateFunc;
}

void GLUTAPIENTRY
glutMenuStatusFunc(GLUTmenuStatusCB menuStatusFunc)
{
  __glutMenuStatusFunc = menuStatusFunc;
}

void
__glutSetMenu(GLUTmenu * menu)
{
  __glutCurrentMenu = menu;
}

static void
unmapMenu(GLUTmenu * menu)
{
  if (menu->cascade) {
    unmapMenu(menu->cascade);
    menu->cascade = NULL;
  }
  menu->anchor = NULL;
  menu->highlighted = NULL;
}

void
__glutFinishMenu(Window win, int x, int y)
{

  unmapMenu(__glutMappedMenu);

  /* XXX Put in a GdiFlush just in case.  Probably unnecessary. -mjk  */
  GdiFlush();

  if (__glutMenuStatusFunc) {
    __glutSetWindow(__glutMenuWindow);
    __glutSetMenu(__glutMappedMenu);

    /* Setting __glutMappedMenu to NULL permits operations that
       change menus or destroy the menu window again. */
    __glutMappedMenu = NULL;

    __glutMenuStatusFunc(GLUT_MENU_NOT_IN_USE, x, y);
  }
  /* Setting __glutMappedMenu to NULL permits operations that
     change menus or destroy the menu window again. */
  __glutMappedMenu = NULL;

  /* If an item is selected and it is not a submenu trigger,
     generate menu callback. */
  if (__glutItemSelected && !__glutItemSelected->isTrigger) {
    __glutSetWindow(__glutMenuWindow);
    /* When menu callback is triggered, current menu should be
       set to the callback menu. */
    __glutSetMenu(__glutItemSelected->menu);
    __glutItemSelected->menu->select(__glutItemSelected->value);
  }
  __glutMenuWindow = NULL;
}

static void
mapMenu(GLUTmenu * menu, int x, int y)
{
  TrackPopupMenu((HMENU) menu->win, TPM_LEFTALIGN |
    ((__glutMenuButton == TPM_RIGHTBUTTON) ? TPM_RIGHTBUTTON : TPM_LEFTBUTTON),
    x, y, 0, __glutCurrentWindow->win, NULL);
}

void
__glutStartMenu(GLUTmenu * menu, GLUTwindow * window,
		int x, int y, int x_win, int y_win)
{
  assert(__glutMappedMenu == NULL);
  __glutMappedMenu = menu;
  __glutMenuWindow = window;
  __glutItemSelected = NULL;
  if (__glutMenuStatusFunc) {
    __glutSetMenu(menu);
    __glutSetWindow(window);
    __glutMenuStatusFunc(GLUT_MENU_IN_USE, x_win, y_win);
  }
  mapMenu(menu, x, y);
}

GLUTmenuItem *
__glutGetUniqueMenuItem(GLUTmenu * menu, UINT unique)
{
  GLUTmenuItem *item;
  int i;

  i = menu->num;
  item = menu->list;
  while (item) {
    if (item->unique == unique) {
      return item;
    }
    if (item->isTrigger) {
      GLUTmenuItem *subitem;
      subitem = __glutGetUniqueMenuItem(menuList[item->value], unique);
      if (subitem) {
        return subitem;
      }
    }
    i--;
    item = item->next;
  }
  return NULL;
}

GLUTmenuItem *
__glutGetMenuItem(GLUTmenu * menu, Window win, int *which)
{
  GLUTmenuItem *item;
  int i;

  i = menu->num;
  item = menu->list;
  while (item) {
    if (item->win == win) {
      *which = i;
      return item;
    }
    if (item->isTrigger) {
      GLUTmenuItem *subitem;

      subitem = __glutGetMenuItem(menuList[item->value],
        win, which);
      if (subitem) {
        return subitem;
      }
    }
    i--;
    item = item->next;
  }
  return NULL;
}

GLUTmenu *
__glutGetMenu(Window win)
{
  GLUTmenu *menu;

  menu = __glutMappedMenu;
  while (menu) {
    if (win == menu->win) {
      return menu;
    }
    menu = menu->cascade;
  }
  return NULL;
}

GLUTmenu *
__glutGetMenuByNum(int menunum)
{
  if (menunum < 1 || menunum > menuListSize) {
    return NULL;
  }
  return menuList[menunum - 1];
}

static int
getUnusedMenuSlot(void)
{
  int i;

  /* Look for allocated, unused slot. */
  for (i = 0; i < menuListSize; i++) {
    if (!menuList[i]) {
      return i;
    }
  }
  /* Allocate a new slot. */
  menuListSize++;
  if (menuList) {
    menuList = (GLUTmenu **)
      realloc(menuList, menuListSize * sizeof(GLUTmenu *));
  } else {
    /* XXX Some realloc's do not correctly perform a malloc
       when asked to perform a realloc on a NULL pointer,
       though the ANSI C library spec requires this. */
    menuList = (GLUTmenu **) malloc(sizeof(GLUTmenu *));
  }
  if (!menuList) {
    __glutFatalError("out of memory.");
  }
  menuList[menuListSize - 1] = NULL;
  return menuListSize - 1;
}

static void
menuModificationError(void)
{
  /* XXX Remove the warning after GLUT 3.0. */
  __glutWarning("The following is a new check for GLUT 3.0; update your code.");
  __glutFatalError("menu manipulation not allowed while menus in use.");
}

int GLUTAPIENTRY
glutCreateMenu(GLUTselectCB selectFunc)
{
  GLUTmenu *menu;
  int menuid;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  menuid = getUnusedMenuSlot();
  menu = (GLUTmenu *) malloc(sizeof(GLUTmenu));
  if (!menu) {
    __glutFatalError("out of memory.");
  }
  menu->id = menuid;
  menu->num = 0;
  menu->submenus = 0;
  menu->select = selectFunc;
  menu->list = NULL;
  menu->cascade = NULL;
  menu->highlighted = NULL;
  menu->anchor = NULL;
  menu->win = (HWND) CreatePopupMenu();
  menuList[menuid] = menu;
  __glutSetMenu(menu);
  return menuid + 1;
}

int GLUTAPIENTRY
__glutCreateMenuWithExit(GLUTselectCB selectFunc, void (__cdecl *exitfunc)(int))
{
  __glutExitFunc = exitfunc;
  return glutCreateMenu(selectFunc);
}

void GLUTAPIENTRY
glutDestroyMenu(int menunum)
{
  GLUTmenu *menu = __glutGetMenuByNum(menunum);
  GLUTmenuItem *item, *next;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  assert(menu->id == menunum - 1);
  DestroyMenu( (HMENU) menu->win);
  menuList[menunum - 1] = NULL;
  /* free all menu entries */
  item = menu->list;
  while (item) {
    assert(item->menu == menu);
    next = item->next;
    free(item->label);
    free(item);
    item = next;
  }
  if (__glutCurrentMenu == menu) {
    __glutCurrentMenu = NULL;
  }
  free(menu);
}

int GLUTAPIENTRY
glutGetMenu(void)
{
  if (__glutCurrentMenu) {
    return __glutCurrentMenu->id + 1;
  } else {
    return 0;
  }
}

void GLUTAPIENTRY
glutSetMenu(int menuid)
{
  GLUTmenu *menu;

  if (menuid < 1 || menuid > menuListSize) {
    __glutWarning("glutSetMenu attempted on bogus menu.");
    return;
  }
  menu = menuList[menuid - 1];
  if (!menu) {
    __glutWarning("glutSetMenu attempted on bogus menu.");
    return;
  }
  __glutSetMenu(menu);
}

static void
setMenuItem(GLUTmenuItem * item, const char *label,
	    int value, Bool isTrigger)
{
  GLUTmenu *menu;

  menu = item->menu;
  item->label = __glutStrdup(label);
  if (!item->label) {
    __glutFatalError("out of memory.");
  }
  item->isTrigger = isTrigger;
  item->len = (int) strlen(label);
  item->value = value;
  item->unique = uniqueMenuHandler++;
  if (isTrigger) {
    AppendMenu((HMENU) menu->win, MF_POPUP, (UINT)item->win, label);
  } else {
    AppendMenu((HMENU) menu->win, MF_STRING, item->unique, label);
  }
}

void GLUTAPIENTRY
glutAddMenuEntry(const char *label, int value)
{
  GLUTmenuItem *entry;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  entry = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
  if (!entry) {
    __glutFatalError("out of memory.");
  }
  entry->menu = __glutCurrentMenu;
  setMenuItem(entry, label, value, FALSE);
  __glutCurrentMenu->num++;
  entry->next = __glutCurrentMenu->list;
  __glutCurrentMenu->list = entry;
}

void GLUTAPIENTRY
glutAddSubMenu(const char *label, int menu)
{
  GLUTmenuItem *submenu;
  GLUTmenu     *popupmenu;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  submenu = (GLUTmenuItem *) malloc(sizeof(GLUTmenuItem));
  if (!submenu) {
    __glutFatalError("out of memory.");
  }
  __glutCurrentMenu->submenus++;
  submenu->menu = __glutCurrentMenu;
  popupmenu = __glutGetMenuByNum(menu);
  if (popupmenu) {
    submenu->win = popupmenu->win;
  }
  setMenuItem(submenu, label, /* base 0 */ menu - 1, TRUE);
  __glutCurrentMenu->num++;
  submenu->next = __glutCurrentMenu->list;
  __glutCurrentMenu->list = submenu;
}

void GLUTAPIENTRY
glutChangeToMenuEntry(int num, const char *label, int value)
{
  GLUTmenuItem *item;
  int i;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  i = __glutCurrentMenu->num;
  item = __glutCurrentMenu->list;
  while (item) {
    if (i == num) {
      if (item->isTrigger) {
        /* If changing a submenu trigger to a menu entry, we
           need to account for submenus.  */
        item->menu->submenus--;
	/* Nuke the Win32 menu. */
	DestroyMenu((HMENU) item->win);
      }
      free(item->label);

      item->label = strdup(label);
      if (!item->label)
	__glutFatalError("out of memory");
      item->isTrigger = FALSE;
      item->len = (int) strlen(label);
      item->value = value;
      item->unique = uniqueMenuHandler++;
      ModifyMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1,
        MF_BYPOSITION | MFT_STRING, item->unique, label);

      return;
    }
    i--;
    item = item->next;
  }
  __glutWarning("Current menu has no %d item.", num);
}

void GLUTAPIENTRY
glutChangeToSubMenu(int num, const char *label, int menu)
{
  GLUTmenu *popupmenu;
  GLUTmenuItem *item;
  int i;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  i = __glutCurrentMenu->num;
  item = __glutCurrentMenu->list;
  while (item) {
    if (i == num) {
      if (!item->isTrigger) {
        /* If changing a menu entry to as submenu trigger, we
           need to account for submenus.  */
        item->menu->submenus++;
	item->win = (HWND) CreatePopupMenu();
      }
      free(item->label);
      
      item->label = strdup(label);
      if (!item->label)
	__glutFatalError("out of memory");
      item->isTrigger = TRUE;
      item->len = (int) strlen(label);
      item->value = menu - 1;
      item->unique = uniqueMenuHandler++;
      popupmenu = __glutGetMenuByNum(menu);
      if (popupmenu)
	item->win = popupmenu->win;
      ModifyMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1,
        MF_BYPOSITION | MF_POPUP, (UINT) item->win, label);
      return;
    }
    i--;
    item = item->next;
  }
  __glutWarning("Current menu has no %d item.", num);
}

void GLUTAPIENTRY
glutRemoveMenuItem(int num)
{
  GLUTmenuItem *item, **prev;
  int i;

  if (__glutMappedMenu) {
    menuModificationError();
  }
  i = __glutCurrentMenu->num;
  prev = &__glutCurrentMenu->list;
  item = __glutCurrentMenu->list;
  while (item) {
    if (i == num) {
      /* Found the menu item in list to remove. */
      __glutCurrentMenu->num--;

      /* Patch up menu's item list. */
      *prev = item->next;

      RemoveMenu((HMENU) __glutCurrentMenu->win, (UINT) i - 1, MF_BYPOSITION);

      free(item->label);
      free(item);
      return;
    }
    i--;
    prev = &item->next;
    item = item->next;
  }
  __glutWarning("Current menu has no %d item.", num);
}

void GLUTAPIENTRY
glutAttachMenu(int button)
{
  if (__glutCurrentWindow == __glutGameModeWindow) {
    __glutWarning("cannot attach menus in game mode.");
    return;
  }
  if (__glutMappedMenu) {
    menuModificationError();
  }
  if (__glutCurrentWindow->menu[button] < 1) {
    __glutCurrentWindow->buttonUses++;
  }
  __glutCurrentWindow->menu[button] = __glutCurrentMenu->id + 1;
}

void GLUTAPIENTRY
glutDetachMenu(int button)
{
  if (__glutMappedMenu) {
    menuModificationError();
  }
  if (__glutCurrentWindow->menu[button] > 0) {
    __glutCurrentWindow->buttonUses--;
    __glutCurrentWindow->menu[button] = 0;
  }
}