#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)visgl.c	5.01 01/02/19 xlockmore";

#endif

/*-
 * visgl.c - separated out of vis.c for lots of good reasons !
 *         - parts grabbed from xscreensaver
 *
 * Copyright (c) 2001 by Eric Lassauge
 *
 * Revision History:
 *
 * E.Lassauge - 09-Mar-2001:
 *	- Use showfps vs erroneous fpsTop
 *      - use MI_DELAY instead of MI_PAUSE for accumulating FPS value
 */


#include "xlock.h"
#include "visgl.h"

#ifdef USE_GL
#include <GL/glu.h>

static GLXContext *glXContext = (GLXContext *) NULL;
Bool *glOK = (Bool *) NULL;

/* fps stuff */
static XFontStruct *font_struct = (XFontStruct *) NULL;
static GLuint   font_dlist;
extern Bool     fpsTop;
extern char 	*fpsfontname;

/*-
 * NOTE WELL:  We _MUST_ destroy the glXContext between each mode
 * in random mode, otherwise OpenGL settings and paramaters from one
 * mode will affect the default initial state for the next mode.
 * BUT, we are going to keep the visual returned by glXChooseVisual,
 * because it will still be good (and because Mesa must keep track
 * of each one, even after XFree(), causing a small memory leak).
 */

XVisualInfo *
getGLVisual(Display * display, int screen, XVisualInfo * wantVis, int monochrome)
{
	if (wantVis) {
		/* Use glXGetConfig() to see if wantVis has what we need already. */
		int         depthBits, doubleBuffer;

		/* glXGetConfig(display, wantVis, GLX_RGBA, &rgbaMode); */
		glXGetConfig(display, wantVis, GLX_DEPTH_SIZE, &depthBits);
		glXGetConfig(display, wantVis, GLX_DOUBLEBUFFER, &doubleBuffer);

		if ((depthBits > 0) && doubleBuffer) {
			return wantVis;
		} else {
			XFree((char *) wantVis);	/* Free it up since its useless now. */
			wantVis = (XVisualInfo *) NULL;
		}
	}
	/* If wantVis is useless, try glXChooseVisual() */
	if (monochrome) {
		/* Monochrome display - use color index mode */
		int         attribList[] =
		{GLX_DOUBLEBUFFER, None};

		return glXChooseVisual(display, screen, attribList);
	} else {
		int         attribList[] =
#if 1
		{GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1,
		 GLX_DOUBLEBUFFER, GLX_DEPTH_SIZE, 1, None};

#else
		{GLX_RGBA, GLX_DOUBLEBUFFER, GLX_DEPTH_SIZE, 1, None};

#endif
		return glXChooseVisual(display, screen, attribList);
	}
}

/*-
 * The following function should be called on startup of any GL mode.
 * It returns a GLXContext for the calling mode to use with
 * glXMakeCurrent().
 */
GLXContext *
init_GL(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	int         screen = MI_SCREEN(mi);
	XVisualInfo xvi_in, *xvi_out;
	int         num_vis;
	GLboolean   rgbaMode;

#ifdef FX
	extern void resetSize(ModeInfo * mi, Bool setGL);

#endif

	if (!glXContext) {
		glXContext = (GLXContext *) calloc(MI_NUM_SCREENS(mi),
						   sizeof (GLXContext));
		if (!glXContext)
			return (GLXContext *) NULL;
	}
	if (glXContext[screen]) {
		glXDestroyContext(display, glXContext[screen]);
		glXContext[screen] = (GLXContext) NULL;
	}
	xvi_in.screen = screen;
	xvi_in.visualid = XVisualIDFromVisual(MI_VISUAL(mi));
	xvi_out = XGetVisualInfo(display, VisualScreenMask | VisualIDMask,
				 &xvi_in, &num_vis);
	if (!xvi_out) {
		(void) fprintf(stderr, "Could not get XVisualInfo\n");
		return (GLXContext *) NULL;
	}
/*-
 * PURIFY 4.0.1 on SunOS4 and on Solaris 2 reports a 104 byte memory leak on
 * the next line each time that a GL mode is run in random mode when using
 * MesaGL 2.2.  This cumulative leak can cause xlock to eventually crash if
 * available memory is depleted.  This bug is fixed in MesaGL 2.3. */
	if (glOK && glOK[screen])
		glXContext[screen] = glXCreateContext(display, xvi_out, 0, GL_TRUE);

	XFree((char *) xvi_out);

	if (!glXContext[screen]) {
		if (MI_IS_VERBOSE(mi))
			(void) fprintf(stderr,
				       "GL could not create rendering context on screen %d.\n", screen);
		return (GLXContext *) NULL;
	}
/*-
 * PURIFY 4.0.1 on Solaris2 reports an uninitialized memory read on the next
 * line. PURIFY 4.0.1 on SunOS4 does not report this error. */
	if (!glXMakeCurrent(display, window, glXContext[screen])) {
		if (MI_IS_DEBUG(mi)) {
			(void) fprintf(stderr, "GLX error\n");
			(void) fprintf(stderr, "If using MesaGL, XGetWindowAttributes is\n");
			(void) fprintf(stderr, "probably returning a null colormap in\n");
			(void) fprintf(stderr, "XMesaCreateWindowBuffer in xmesa1.c .\n");
		}
		return (GLXContext *) NULL;
	}
	/* True Color junk */
	glGetBooleanv(GL_RGBA_MODE, &rgbaMode);
	if (!rgbaMode) {
		glIndexi(MI_WHITE_PIXEL(mi));
		glClearIndex((float) MI_BLACK_PIXEL(mi));
	}
#ifdef FX
	resetSize(mi, True);
#endif
	return (&(glXContext[screen]));
}

void
FreeAllGL(ModeInfo * mi)
{
	int         scr;

#ifdef FX
	extern void resetSize(ModeInfo * mi, Bool setGL);

#endif

	if (glXContext) {
		/* To release the current context */
		glXMakeCurrent(MI_DISPLAY(mi), None, (GLXContext) NULL);
		/* then destroy all contexts */
		for (scr = 0; scr < MI_NUM_SCREENS(mi); scr++) {
			if (glXContext[scr]) {
				glXDestroyContext(MI_DISPLAY(mi), glXContext[scr]);
				glXContext[scr] = (GLXContext) NULL;
			}
		}
		free(glXContext);
		glXContext = (GLXContext *) NULL;
	}
#ifdef FX
	resetSize(mi, False);
#endif

#if 0
	if (glOK) {
		free(glOK);
		glOK = NULL;
	}
#endif
	if (MI_IS_FPS(mi))
	{
		/* Free font stuff */
		if (font_struct)
		{
  			int last;

  			last = font_struct->max_char_or_byte2;
  			clear_gl_error ();
			if (glIsList(font_dlist)) {
	  			glDeleteLists (font_dlist,(GLuint) last+1);
  				(void) check_gl_error ("glDeleteLists");
				font_dlist = 0;
			}
			XFreeFont(MI_DISPLAY(mi),font_struct);
			/* font_struct should be freed now */
			font_struct = (XFontStruct *) NULL;
		}
		/* else oops ? */
	}
}

/* clear away any lingering error codes */
void
clear_gl_error (void)
{
  while (glGetError() != GL_NO_ERROR);
}

/* report a GL error. */
Bool
check_gl_error (const char *type)
{
  char buf[100];
  GLenum i;
  const char *e;
  switch ((i = glGetError())) {
  case GL_NO_ERROR: return False;
  case GL_INVALID_ENUM:          e = "invalid enum";      break;
  case GL_INVALID_VALUE:         e = "invalid value";     break;
  case GL_INVALID_OPERATION:     e = "invalid operation"; break;
  case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
  case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
  case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
#ifdef GL_TABLE_TOO_LARGE_EXT
  case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
#endif
#ifdef GL_TEXTURE_TOO_LARGE_EXT
  case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
#endif
  default:
    e = buf; (void) sprintf (buf, "unknown error %d", (int) i); break;
  }
  (void) fprintf (stderr, "XLock: %s error: %s\n", type, e);
  return True;
}


/* Frames-per-second statistics */

static int fps_text_x = 10;
static int fps_text_y = 10;
static int fps_sample_frames = 10;

static Bool
fps_init (ModeInfo *mi)
{
  const char *font = fpsfontname;
  XFontStruct *f;
  Font id;
  int first, last;

  if (!font) font = "-*-courier-bold-r-normal-*-180-*";
  f = XLoadQueryFont(MI_DISPLAY(mi), font);
  if (!f) f = XLoadQueryFont(MI_DISPLAY(mi), "fixed");

  font_struct = f; /* save font struct info */
  id = f->fid;
  first = f->min_char_or_byte2;
  last = f->max_char_or_byte2;

  clear_gl_error ();
  font_dlist = glGenLists ((GLuint) last+1);
  if (check_gl_error ("glGenLists"))
 	return False;

  if (fpsTop) /* Draw string on top of screen */
    fps_text_y = - (f->ascent + 10);

  glXUseXFont(id, first, last-first+1, font_dlist + first);
  return (!check_gl_error ("glXUseXFont"));
}

static Bool
fps_print_string (ModeInfo *mi, GLfloat x, GLfloat y, const char *string)
{
  static GLfloat red = 0.0, green = 0.0, blue = 0.0;
  int i;
  /* save the current state */
  /* note: could be expensive! */

  while (red == 0.0 && green == 0.0 && blue == 0.0) {
     red = NRAND(2) ? 1.0 : 0.0;
     green = NRAND(2) ? 1.0 : 0.0;
     blue = NRAND(2) ? 1.0 : 0.0;
  }
  if (y < 0)
    y = MI_HEIGHT(mi) + y;

  clear_gl_error ();
  glPushAttrib(GL_ALL_ATTRIB_BITS);
  {
    if (check_gl_error ("glPushAttrib"))
	return False;
    /* disable lighting and texturing when drawing bitmaps!
       (glPopAttrib() restores these, I believe.)
     */
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
    glDisable(GL_BLEND);

    /* glPopAttrib() does not restore matrix changes, so we must
       push/pop the matrix stacks to be non-intrusive there.
     */
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    {
      if (check_gl_error ("glPushMatrix"))
	return False;
      glLoadIdentity();

      /* Each matrix mode has its own stack, so we need to push/pop
         them separately. */
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      {
        if (check_gl_error ("glPushMatrix"))
	  return False;
        glLoadIdentity();

        gluOrtho2D(0.0, (GLfloat) MI_WIDTH(mi), 0.0, (GLfloat) MI_HEIGHT(mi));
        if (check_gl_error ("gluOrtho2D"))
	  return False;
        /* draw the text */
        glColor3f (red, green, blue);
        glRasterPos2f (x, y);
        for (i = 0; i < (int) strlen(string); i++)
          glCallList (font_dlist + (int)string[i]);

        if (check_gl_error ("fps_print_string"))
	  return False;
      }
      glPopMatrix();
    }
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

  }
  /* clean up after our state changes */
  glPopAttrib();
  return (!check_gl_error ("glPopAttrib"));
}


void
do_fps (ModeInfo *mi)
{
  /* every N frames, get the time and use it to get the frames per second */
  static int frame_counter = -1;
  static double oldtime = 0; /* time in usecs, as a double */
  static double newtime = 0;

  static char msg [1024] = { 0, };

  if (MI_IS_ICONIC(mi))
  {
	/* don't do it in iconic state */
	return;
  }
  if (frame_counter == -1)
    {
      if (!fps_init (mi))
	return;
      frame_counter = fps_sample_frames;
    }

  if (frame_counter++ == fps_sample_frames)
    {
      double fps;
      struct timeval now;
# ifdef GETTIMEOFDAY_TWO_ARGS
#ifdef VMS
	(void) gettimeofday(&now, NULL);
#else
	struct timezone tzp;
	(void) gettimeofday(&now, &tzp);
#endif
# else
      (void) gettimeofday(&now);
# endif

      oldtime = newtime;
      newtime = now.tv_sec + ((double) now.tv_usec * 0.000001);

      fps = fps_sample_frames / (newtime - oldtime);

      if (fps < 0.0001)
        {
          (void) strcpy(msg, "FPS: (accumulating...)");
        }
      else
        {
          (void) sprintf(msg, "FPS: %.02f", fps);

          if (MI_DELAY(mi) != 0 && !fpsTop)
            { /* Include delay information in FPS display only if not on top */
              char buf[40];
              (void) sprintf(buf, "%f", MI_DELAY(mi) / 1000000.0); /* FTSO C */
              while(*buf && buf[strlen(buf)-1] == '0')
                buf[strlen(buf)-1] = 0;
              if (buf[strlen(buf)-1] == '.')
                buf[strlen(buf)-1] = 0;
              (void) sprintf(msg + strlen(msg), " (including %s sec/frame delay)",
                      buf);
            }
        }

      frame_counter = 0;
    }
    (void) fps_print_string (mi, (float) fps_text_x, (float) -fps_text_y, msg);
}
#endif