/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: mode.c,v 1.11 2000/03/12 12:58:54 rich Exp $
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include <GL/glut.h>

#include "xracer.h"
#include "xracer-mode.h"
#include "xracer-log.h"
#include "xracer-text.h"
#include "xracer-ws.h"
#include "xracer-screenshot.h"
#include "xracer-arch.h"

static const struct xrMode *current_mode = 0;
static int frame = 0;		/* Frame counter. */

static void *fps_font = 0;	/* Frames per second counter font. */
static int fps_enabled = 1;	/* Frames per second enabled? */

/* The frame per second counter is averaged over the last N frames. */
#define FPS_FRAMES 8		/* Must be a power of 2!! */
static double fps_time[FPS_FRAMES];
static int fps_index = 0;

static int take_screenshot = 0;	/* Take a screenshot. */

/* GLUT callback functions. For these functions, we want to be
 * able to do something before and/or after each callback, so we
 * unfortunately suffer an extra function call overhead.
 */
static void
display ()
{
  GLenum error;

  /* Recalculate current time, once per frame only. */
  xrArchRecalculateCurrentTime ();

  /* Record the current time so we can display the frame per second counter
   * a number of frames later.
   */
  fps_time[fps_index] = xrCurrentTime;
  fps_index = (fps_index+1) & (FPS_FRAMES-1);

  if (current_mode->display) current_mode->display ();

  /* Any errors? */
  error = glGetError ();
  if (error != GL_NO_ERROR)
    xrLog (LOG_ERROR, "GL error: %s", gluErrorString (error));

  /*xrLog (LOG_DEBUG, "finished frame %d", frame);*/

  /* Switch to orthographic projection for final overlay messages. */
  glMatrixMode (GL_PROJECTION);
  glPushMatrix ();
  glLoadIdentity ();
  glOrtho (0, (GLdouble) xrWidth, 0, (GLdouble) xrHeight, 0, 1000);
  glMatrixMode (GL_MODELVIEW);
  glPushMatrix ();
  glLoadIdentity ();

  /* Enable modes for text. */
  glEnable (GL_BLEND);
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  /* Log messages. */
  xrLogDisplay ();

  /* Frame per second counter. */
  if (fps_enabled && frame > FPS_FRAMES)
    {
      double frame_time = (xrCurrentTime - fps_time[fps_index]) / FPS_FRAMES;
      double fps = 1 / frame_time;
      static char buffer[32];
      int width;

      sprintf (buffer, "%3.1f fps", fps);
      width = xrTextGetWidth (fps_font, buffer);
      xrTextPuts (fps_font, buffer, xrWidth - width, 16);
    }

  glDisable (GL_BLEND);

  /* Restore matrices. */
  glMatrixMode (GL_MODELVIEW);
  glPopMatrix ();
  glMatrixMode (GL_PROJECTION);
  glPopMatrix ();

  /* Flush commands out and tell GLUT to swap the double buffers. */
  glFlush ();
  glutSwapBuffers ();
  frame ++;

  /* We can't warp and grab the pointer until the window is mapped. Wait
   * a few frames before doing this.
   */
  if (frame == 4)
    {
      xrWsWarpPointer ();
      xrWsGrabPointer ();
    }

  /* Take a screenshot, if requested. */
  if (take_screenshot)
    {
      xrScreenshot ();
      take_screenshot = 0;
    }
}

static void
reshape (int w, int h)
{
  xrLog (LOG_DEBUG, "reshape (%d, %d)", w, h);

  xrWidth = w;
  xrHeight = h;

  glViewport (0, 0, (GLsizei) w, (GLsizei) h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (50, (GLfloat) w / (GLfloat) h, 0.04, 40);

  if (current_mode->reshape) current_mode->reshape (w, h);
}

static void
keyboard (unsigned char key, int x, int y)
{
  if (current_mode->keyboard && current_mode->keyboard (key, x, y))
    return;

  /* There are some default keys. Modes may override these keys
   * simply by capturing the keyboard event and returning 1.
   */
  switch (key)
    {
    case 'q':			/* Exit program abruptly. */
      exit (0);
    case 'f':			/* Toggle frames per second display. */
      fps_enabled ^= 1;
      return;
    case 's':			/* Take a screenshot. */
      take_screenshot = 1;
      return;
    }

#if 1
  xrLog (LOG_DEBUG, "unknown keyboard event: key = %u 0x%02x (%c)",
	 (unsigned) key, (unsigned) key, isprint (key) ? key : '?');
#endif
}

/* Can't call this idle because it clashes with a symbol in
 * libc5. Thanks to "Nicolas S." <bobakitoo@yahoo.com>
 */
static void
gidle ()
{
  if (current_mode->idle) current_mode->idle ();

  glutPostRedisplay ();
}

/* Program-level initializations. */
void
xrModeInit ()
{
  glutDisplayFunc (display);
  glutReshapeFunc (reshape);
  glutKeyboardFunc (keyboard);
  glutIdleFunc (gidle);

  fps_font = xrTextFindFont ("crillee", 14);
  xrLogAssert (fps_font != 0);
}

void
xrEnterMode (const struct xrMode *mode, const void *args)
{
  /* Call the previous end_mode function, if there is one. */
  if (current_mode && current_mode->end_mode) current_mode->end_mode ();

  /* Set up the new mode and initialize the callbacks. */
  current_mode = mode;
  glutMouseFunc (mode->mouse);
  glutMotionFunc (mode->motion);
  glutPassiveMotionFunc (mode->passive_motion);
  glutSpecialFunc ((void (*)(int, int, int)) mode->special);
  glutKeyboardUpFunc ((void (*)(unsigned char, int, int)) mode->keyboard_up);
  glutSpecialUpFunc ((void (*)(int, int, int)) mode->special_up);

  if (mode->name) xrLog (LOG_DEBUG, "enter mode: %s", mode->name);

  if (mode->start_mode) mode->start_mode (args);
}
