/*
 * Amiga GLUT graphics library toolkit
 * Version:  1.1
 * Copyright (C) 1998 Jarno van der Linden
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * glutMainLoop.c
 *
 * Version 1.0  27 Jun 1998
 * by Jarno van der Linden
 * jarno@kcbbs.gen.nz
 *
 */

extern struct ExecBase *SysBase;
#include <inline/intuition.h>
#include <inline/gadtools.h>
#include <inline/exec.h>
#include <inline/dos.h>
#include <inline/graphics.h>
#include <inline/layers.h>
#include <libraries/gadtools.h>

#include <stdlib.h>

#include "glutstuff.h"

extern void RedoMenu(int button, struct GlutMenu *glutmenu);

static int ConvRaw(UWORD code, UWORD qual)
{
  switch (code) {
    case CURSORLEFT:
      return GLUT_KEY_LEFT;
    case CURSORUP:
      return GLUT_KEY_UP;
    case CURSORRIGHT:
      return GLUT_KEY_RIGHT;
    case CURSORDOWN:
      return GLUT_KEY_DOWN;
    case 0x50:
      return GLUT_KEY_F1;
    case 0x51:
      return GLUT_KEY_F2;
    case 0x52:
      return GLUT_KEY_F3;
    case 0x53:
      return GLUT_KEY_F4;
    case 0x54:
      return GLUT_KEY_F5;
    case 0x55:
      return GLUT_KEY_F6;
    case 0x56:
      return GLUT_KEY_F7;
    case 0x57:
      return GLUT_KEY_F8;
    case 0x58:
      return GLUT_KEY_F9;
    case 0x59:
      return GLUT_KEY_F10;
  }

  return code;
}

static int ConvToButton(UWORD code, UWORD qual)
{
  switch (code) {
    case SELECTUP:
    case SELECTDOWN:
      if (qual & IEQUALIFIER_RCOMMAND)
	return GLUT_MIDDLE_BUTTON;
      if (qual & IEQUALIFIER_RALT)
	return GLUT_RIGHT_BUTTON;
      return GLUT_LEFT_BUTTON;
    case MIDDLEUP:
    case MIDDLEDOWN:
      return GLUT_MIDDLE_BUTTON;
    case MENUUP:
    case MENUDOWN:
      return GLUT_RIGHT_BUTTON;
  }

  return code;
}

static int ConvToButtonState(UWORD code)
{
  switch (code) {
    case SELECTUP:
    case MIDDLEUP:
    case MENUUP:
      return GLUT_UP;
    case SELECTDOWN:
    case MIDDLEDOWN:
    case MENUDOWN:
      return GLUT_DOWN;
  }

  return code;
}

void glutMainLoop(void)
{
  BOOL wanttoquit = FALSE;

  while (1) {
    struct GlutWindow *actWindow;
    struct GlutMenu *actMenu;
    struct IntuiMessage *msg;
    struct Window *win;
    BOOL idleing = TRUE;

    if(!glutstuff.Windows.nodes) {
      DEBUGOUT(1, "no windows open to process\n");
      return;
    }

    /* Apply changes
     */
    actWindow = (struct GlutWindow *)&glutstuff.Windows;
    while ((actWindow = (struct GlutWindow *)nGetNext(&actWindow->WindowNode))) {
      win = actWindow->window;
      stuffMakeCurrent(actWindow);

      if (actWindow->WindowTimers.nodes) {
        struct GlutTimer *gt = (struct GlutTimer *)nGetHead(&actWindow->WindowTimers);

	while(gt) {
	  struct GlutTimer *new = (struct GlutTimer *)nGetNext(&gt->TimerNode);
	  ULONG secs, micros;

	  CurrentTime(&secs, &micros);						/* better for accuracity */
	  if ((secs > gt->secs) || ((secs == gt->secs) && (micros > gt->micros))) {
	    DEBUGOUT(2, "do timer (%ld:%ld), set for (%ld:%ld)\n", secs, micros, gt->secs, gt->micros);

	    idleing = FALSE;
	    if (gt->timerfunc)							/* be paranoid */
	      (*gt->timerfunc) (gt->TimerValue);

	    dRemove(&actWindow->WindowTimers, &gt->TimerNode);
	    FreeVecPooled(glutPool, (ULONG *)gt);
	  }
	  else
	    DEBUGOUT(2, "skip timer (%ld:%ld), wait for (%ld:%ld)\n", secs, micros, gt->secs, gt->micros);

	  gt = new;
	}
      }

      /* Hide/Show/Iconify */							/* TODO: workbench.library icons */
      /*  then break if hide or iconify */
      /*  continue if show */
      if (actWindow->pop) {
        WindowToFront(win);
        actWindow->pop = FALSE;
        actWindow->push = FALSE;
      }
      else if (actWindow->push) {
        WindowToBack(win);
        actWindow->push = FALSE;
        actWindow->pop = FALSE;
      }

      if (actWindow->needposition) {
	actWindow->needposition = FALSE;
	actWindow->needpositiongui = FALSE;
	/* Let the change be made through the gui */
	ChangeWindowBox(win, actWindow->winx, actWindow->winy, actWindow->winwidth, actWindow->winheight);
	actWindow->fullscreen = FALSE;		/* BUG?: could prevent fullscreen-mode */
      }

      if (actWindow->needpositiongui) {
	actWindow->needpositiongui = FALSE;
	actWindow->fullscreen = FALSE;		/* BUG?: could prevent fullscreen-mode */
      }

      if (actWindow->needreshape) {
	actWindow->needreshape = FALSE;
	actWindow->needreshapegui = FALSE;				/* Reshaping and redisplaying is useless */
	actWindow->needredisplay = FALSE;				/* this round. Change is inevitable */
	/* Let the change be made through the gui */
	ChangeWindowBox(win, actWindow->winx, actWindow->winy, actWindow->winwidth, actWindow->winheight);
	actWindow->fullscreen = FALSE;		/* BUG?: could prevent fullscreen-mode */
      }

      if (actWindow->needreshapegui) {
	actWindow->needreshapegui = FALSE;
	if (actWindow->reshapefunc)
	  (*actWindow->reshapefunc) (InnerWidth(win), InnerHeight(win));
	actWindow->fullscreen = FALSE;		/* BUG?: could prevent fullscreen-mode */
      }

      if (actWindow->fullscreen) {
       /*
        * TODO: make real fullscreen
        *  close window, open borderless window on old screen, rearrange contexts
        *  pop the window to front if something overlays it
        * ALTERNATIVE:
        *  try to make window borderless on the run
        */
	ChangeWindowBox(win, actWindow->winx = 0,
			     actWindow->winy = 0,
			     actWindow->winwidth = win->WScreen->Width,
			     actWindow->winheight = win->WScreen->Height);
	WindowToFront(win);
      }

      if (actWindow->needredisplay) {
	actWindow->needredisplay = FALSE;
	if (actWindow->displayfunc)
	  (*actWindow->displayfunc) ();
      }

      if (actWindow->needvisibility) {
	actWindow->needvisibility = FALSE;
	if (actWindow->visibilityfunc)
	  (*actWindow->visibilityfunc) (actWindow->visible ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
      }

      if ((actWindow->leftmenu && actWindow->leftmenu->needupdate) || actWindow->needleftmenu) {
	actWindow->needleftmenu = FALSE;
	RedoMenu(GLUT_LEFT_BUTTON, actWindow->leftmenu);
      }

      if ((actWindow->middlemenu && actWindow->middlemenu->needupdate) || actWindow->needmiddlemenu) {
	actWindow->needmiddlemenu = FALSE;
	RedoMenu(GLUT_MIDDLE_BUTTON, actWindow->middlemenu);
      }

      if ((actWindow->rightmenu && actWindow->rightmenu->needupdate) || actWindow->needrightmenu) {
	actWindow->needrightmenu = FALSE;
	RedoMenu(GLUT_RIGHT_BUTTON, actWindow->rightmenu);
      }
    }

    if (glutstuff.Menues.nodes) {
      actMenu = (struct GlutMenu *)&glutstuff.Menues;
      while ((actMenu = (struct GlutMenu *)nGetNext(&actMenu->MenuNode))) {
	stuffMakeCurrentMenu(actMenu);
	actMenu->needupdate = FALSE;
      }
    }

    /* Wait for something to happen
     */
    if (glutstuff.idlefunc == NULL)
      Wait(1L << glutstuff.msgport->mp_SigBit);

    /* Handle all messages (if any)
     */
    while ((msg = (struct IntuiMessage *)GetMsg(glutstuff.msgport))) {
      struct GlutMenuEntry *actEntry;
      struct MenuItem *item;
      UWORD menuNumber;
      struct IntuiMessage cmsg = *msg;

      ReplyMsg(&msg->ExecMessage);

      stuffMakeCurrent((struct GlutWindow *)cmsg.IDCMPWindow->UserData);
      if (glutstuff.curwin != (struct GlutWindow *)cmsg.IDCMPWindow->UserData) {
        Printf("serious error found in stuffMakeCurrent(), quitting ...\n");
        continue;
      }
      actWindow = glutstuff.curwin;

      if (actWindow->window != cmsg.IDCMPWindow) {
        Printf("serious error found in stuffMakeCurrent(), quitting ...\n");
        continue;
      }
      win = actWindow->window;

      actWindow->qualifiers = cmsg.Qualifier;
      switch (cmsg.Class) {
	case IDCMP_REFRESHWINDOW:							/* received only in DB-mode, non-DB-modes are NoCareRefresh */
	  BeginRefresh(win);
	  amigaMesaSwapBuffers(actWindow->context);					/* put current contents into damaged region */
	  EndRefresh(win, TRUE);
	  break;
	case IDCMP_CHANGEWINDOW:
	  idleing = FALSE;
	  actWindow->winx = win->LeftEdge;
	  actWindow->winy = win->TopEdge;
	  actWindow->winwidth = InnerWidth(win);
	  actWindow->winheight = InnerHeight(win);
	  if ((InnerWidth(win) != actWindow->wincurwidth) ||
	      (InnerHeight(win) != actWindow->wincurheight)) {
#ifdef USE_CLIP_LAYER_
	    if (actWindow->clipreg)							/* Get rid of last clipping region */
	      DisposeRegion(InstallClipRegion(win->WLayer, NULL));
	    clipWindowToBorders(win);
#endif
	    actWindow->wincurwidth = InnerWidth(win);
	    actWindow->wincurheight = InnerHeight(win);
	    actWindow->needreshapegui = TRUE;
	    actWindow->needredisplay = TRUE;
	  }
	  if ((win->LeftEdge != actWindow->wincurx) ||
	      (win->TopEdge != actWindow->wincury)) {
	    actWindow->wincurx = win->LeftEdge;
	    actWindow->wincury = win->TopEdge;
	    actWindow->needpositiongui = TRUE;
	  }
	  break;
	case IDCMP_VANILLAKEY:
	  if (cmsg.Code & IECODE_UP_PREFIX) {
	    if (actWindow->keyboardupfunc) {
	      if (!((cmsg.Qualifier & IEQUALIFIER_REPEAT) &&
		    (actWindow->ignorekeyrepeat ||
		     (actWindow->repeatmode == GLUT_KEY_REPEAT_OFF)))) {
	        idleing = FALSE;
	        (*actWindow->keyboardupfunc) (ConvRaw(cmsg.Code, cmsg.Qualifier), cmsg.MouseX, cmsg.MouseY);
	      }
	    }
	  }
	  else if (actWindow->keyboardfunc) {
	    if (!((cmsg.Qualifier & IEQUALIFIER_REPEAT) &&
		  (actWindow->ignorekeyrepeat ||
		   (actWindow->repeatmode == GLUT_KEY_REPEAT_OFF)))) {
	      idleing = FALSE;
	      (*actWindow->keyboardfunc) (ConvRaw(cmsg.Code, cmsg.Qualifier), cmsg.MouseX, cmsg.MouseY);
	    }
	  }
	  break;
	case IDCMP_RAWKEY:
	  if (cmsg.Code & IECODE_UP_PREFIX) {
	    if (actWindow->specialupfunc) {
	      if (!((cmsg.Qualifier & IEQUALIFIER_REPEAT) &&
		    (actWindow->ignorekeyrepeat ||
		     (actWindow->repeatmode == GLUT_KEY_REPEAT_OFF)))) {
	        idleing = FALSE;
	        (*actWindow->specialupfunc) (ConvRaw(cmsg.Code, cmsg.Qualifier), cmsg.MouseX, cmsg.MouseY);
	      }
	    }
	  }
	  else if (actWindow->specialfunc) {
	    if (!((cmsg.Qualifier & IEQUALIFIER_REPEAT) &&
		  (actWindow->ignorekeyrepeat ||
		   (actWindow->repeatmode == GLUT_KEY_REPEAT_OFF)))) {
	      idleing = FALSE;
	      (*actWindow->specialfunc) (ConvRaw(cmsg.Code, cmsg.Qualifier), cmsg.MouseX, cmsg.MouseY);
	    }
	  }
	  break;
	case IDCMP_MENUPICK:
	  menuNumber = cmsg.Code;
	  while (menuNumber != MENUNULL) {
	    item = ItemAddress(actWindow->menu, menuNumber);
	    actEntry = (struct GlutMenuEntry *)GTMENUITEM_USERDATA(item);
	    stuffMakeCurrentMenu(actEntry->EntryMenu);
	    if (glutstuff.curmenu->menufunc) {
	      idleing = FALSE;
	      (*glutstuff.curmenu->menufunc) (actEntry->EntryValue);
	    }
	    menuNumber = item->NextSelect;
	  }
	  break;
	case IDCMP_CLOSEWINDOW:
	  if (wanttoquit) {
	    /* User wants to quit, but ESC doesn't do anything
	     * Panic, and return in the hope that it will drop
	     * through to an end-of-program call.
	     * (Note that we don't use exit(), as we may want
	     * to put all this in a run-time library).
	     */
	    return;
	  }
	  if (actWindow->keyboardfunc) {
	    idleing = FALSE;
	    (*actWindow->keyboardfunc) (27, 0, 0);
	  }
	  wanttoquit = TRUE;
	  break;
	case IDCMP_MOUSEBUTTONS:
	  if (actWindow->mousefunc) {
	    idleing = FALSE;
	    (*actWindow->mousefunc) (ConvToButton(cmsg.Code, cmsg.Qualifier), ConvToButtonState(cmsg.Code), cmsg.MouseX, cmsg.MouseY);
	  }
	  break;
	case IDCMP_INTUITICKS:
	  if ((actWindow->mousex != cmsg.MouseX) ||
	      (actWindow->mousey != cmsg.MouseY)) {
	    actWindow->mousex = cmsg.MouseX;
	    actWindow->mousey = cmsg.MouseY;

	    if ((cmsg.Qualifier & IEQUALIFIER_LEFTBUTTON) &&
		(actWindow->motionfunc)) {
	      idleing = FALSE;
	      (*actWindow->motionfunc) (cmsg.MouseX, cmsg.MouseY);
	    }
	    else if (actWindow->passivemotionfunc) {
	      idleing = FALSE;
	      (*actWindow->passivemotionfunc) (cmsg.MouseX, cmsg.MouseY);
	    }

	    if (actWindow->entryfunc) {
	      if ((actWindow->mousex < actWindow->winx) ||
		  (actWindow->mousey < actWindow->winy) ||
		  (actWindow->mousex > (actWindow->winx + win->Width)) ||
		  (actWindow->mousey > (actWindow->winy + win->Height))) {
	        /* out of window region */
	        if (!actWindow->lastoutwindow) {
		  idleing = FALSE;
	          (*actWindow->entryfunc) (GLUT_LEFT);
	          actWindow->lastoutwindow = TRUE;
	        }
	      }
	      else {
	        /* in to window region */
	        if (actWindow->lastoutwindow) {
		  idleing = FALSE;
	          (*actWindow->entryfunc) (GLUT_ENTERED);
	          actWindow->lastoutwindow = FALSE;
	        }
	      }
	    }
	  }
	  break;
      }
    }

#if 0    
    if (actWindow->windowstatusfunc) {
      struct GlutWindow *gwo;
      
      for (gwo = (struct GlutWindow *)nGetHead(&glutstuff.Windows); gwo; gwo = (struct GlutWindow *)nGetNext(&gwo->WindowNode)) {
        /* skip us, compare only others */
        if(gwo != actWindow) {
          /* ... dynamic rectangle list */
        }
      }
    }
#endif

    /* If nothing is happening, call the idle function
     */
    if (idleing && glutstuff.idlefunc)
      (*glutstuff.idlefunc) ();
  }
}
