/* opengl.c: OpenGL graphics driver. */

#include <stdio.h>

#include <Xm/Xm.h>
#include <Xm/DrawingA.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include <unistd.h>

#include "render.h"
#include "camera.h"
#include "error.h"
#include "pools.h"

#include <time.h>

static XVisualInfo *glx_visual;
static GLXContext glx_context;
static Display *glx_display;
static Window glx_window;

static int attribute_list_db[] = {GLX_RGBA, 
			       GLX_RED_SIZE, 2,
			       GLX_GREEN_SIZE, 2,
			       GLX_BLUE_SIZE, 2,
			       GLX_DEPTH_SIZE, 16,
			       GLX_DOUBLEBUFFER,
			       None};

static int attribute_list_nodb[] = {GLX_RGBA, 
			       GLX_RED_SIZE, 2,
			       GLX_GREEN_SIZE, 2,
			       GLX_BLUE_SIZE, 2,
			       GLX_DEPTH_SIZE, 16,
			       None};

static int ogl_inited = FALSE, doublebuf = FALSE;

static XVisualInfo *OpenGLGetVisual(Display *dpy)
{
  if (!glXQueryExtension(dpy, NULL, NULL)) 
    Fatal(1, NULL, "No GLX extension available on the display");

  /* get an appropriate visual: first look for a visual with double 
     buffering */
  glx_visual = glXChooseVisual(dpy, DefaultScreen(dpy), attribute_list_db);
  if (glx_visual) {
    doublebuf = TRUE;
    return glx_visual;
  } else {	/* look for a visual without double buffering */
    doublebuf = FALSE;
    return glx_visual = glXChooseVisual(dpy, DefaultScreen(dpy), attribute_list_nodb);
  }
}

/* finds the visual and colormap to be used for OpenGL rendering on the specified 
 * screen */
Boolean RenderGetVisualInfoAndColormap(Screen *screen,
				       XVisualInfo *visual_info,
				       Colormap *colormap)
{
  XVisualInfo *vi;
  Display *dpy = DisplayOfScreen(screen);

  vi = OpenGLGetVisual(dpy);
  if (!vi) 
    Fatal(1, NULL, "Failed to get a suitable X visual");
  *visual_info = *vi;
  
  *colormap = XCreateColormap(dpy, RootWindow(dpy, visual_info->screen),
			      visual_info->visual, AllocNone);	

  return TRUE;
}

void RenderClearWindow(void)
{
  glClearColor(Camera.background.r, Camera.background.g, Camera.background.b, 0.);
  glClearDepth(1.);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void RenderSetCamera(void)
{
  RenderClearWindow();

  /* use full viewport */
  glViewport(0, 0, Camera.hres, Camera.vres);

  /* determine distance to front- and backclipping plane */
  RenderGetNearFar(&Camera.near, &Camera.far);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(Camera.vfov*2., (float)Camera.hres/(float)Camera.vres,
		 Camera.near, Camera.far);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(Camera.eyep.x, Camera.eyep.y ,Camera.eyep.z,
	    Camera.lookp.x, Camera.lookp.y, Camera.lookp.z,
	    Camera.updir.x, Camera.updir.y, Camera.updir.z);
}

static void OpenGLInitCallback(Widget canvas, XtPointer client_data, XtPointer call_data)
{
  glx_display = XtDisplay(canvas);
  glx_window = XtWindow(canvas);

  /* create a GLX context */
  glx_context = glXCreateContext(glx_display, glx_visual, 0, GL_TRUE);
	
  /* connect the context to the window */
  glXMakeCurrent(glx_display, glx_window, glx_context);

  glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST); 

  glDrawBuffer(GL_FRONT);
  
  RenderClearWindow();
  glFinish();

  ogl_inited = TRUE;
  XtRemoveCallback(canvas, XmNexposeCallback, OpenGLInitCallback, (XtPointer)NULL);
}

/* open window for rendering */
Widget RenderCreateWindow(Widget parent)
{
  Widget canvas;
  /*  Dimension hres, vres; */

  canvas = XtVaCreateManagedWidget("canvas",
				   xmDrawingAreaWidgetClass,
				   parent, 
				   NULL);

  ogl_inited = FALSE;
  XtAddCallback(canvas, XmNexposeCallback, OpenGLInitCallback, (XtPointer)NULL);

  return canvas;
}

/* Returns 0 if not yet ready for rendering */
int RenderStartFrame(void)
{
  if (!ogl_inited)
    return 0;

  if (doublebuf)
    glDrawBuffer(GL_BACK);

  RenderSetCamera();

  switch (renderopts.backface_culling) {
  case BC_ON:
    glEnable(GL_CULL_FACE);
    break;
  case BC_OFF:
    glDisable(GL_CULL_FACE);
    break;
  default:
    break;
  }

  return 1;
}

/* Finishes rendering a frame */
void RenderEndFrame(void)
{
  glFinish();

  if (doublebuf)
    glXSwapBuffers(glx_display,glx_window);
  glDrawBuffer(GL_FRONT);
}

void SaveScreen(FILE *fp)
{
  long i, j, x=Camera.hres, y=Camera.vres;
  GLubyte *screen, *pixel;

  screen = (GLubyte *)Alloc(x * y * sizeof(GLubyte) * 4);

  glReadBuffer(GL_FRONT);
  glReadPixels(0,  0, x, y, GL_RGBA, GL_UNSIGNED_BYTE,  screen);
  
  fprintf(fp, "P6\n%ld %ld\n255\n", x, y);
  for (j=y-1; j>=0; j--)
    for (i=0, pixel=screen+4*(j*x); i<x; i++, pixel += 4) {
      putc(pixel[0], fp);
      putc(pixel[1], fp);
      putc(pixel[2], fp);
    }

  Free((char *)screen, x * y * 4 * sizeof(GLubyte));
}
