/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     graphXO.c                                                      */
/*                                                                          */
/*                                                                          */
/* description:  simple graphical routines                                  */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Universitaet Bremen                                          */
/*             Bibliothekstr. 2                                             */
/*             D-28359 Bremen, Germany                                      */
/*                                                                          */
/*             Kunibert G. Siebert                                          */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "alberta.h"

#include <GL/glx.h>
#include <GL/gl.h>
#include <unistd.h>

/*---8<---------------------------------------------------------------------*/
/*---  standard colors                                                   ---*/
/*--------------------------------------------------------------------->8---*/

const GRAPH_RGBCOLOR rgb_black   = {0.0,0.0,0.0};
const GRAPH_RGBCOLOR rgb_white   = {1.0,1.0,1.0};
const GRAPH_RGBCOLOR rgb_red     = {1.0,0.0,0.0};
const GRAPH_RGBCOLOR rgb_green   = {0.0,1.0,0.0};
const GRAPH_RGBCOLOR rgb_blue    = {0.0,0.0,1.0};
const GRAPH_RGBCOLOR rgb_yellow  = {1.0,1.0,0.0};
const GRAPH_RGBCOLOR rgb_magenta = {1.0,0.0,1.0};
const GRAPH_RGBCOLOR rgb_cyan    = {0.0,1.0,1.0};
const GRAPH_RGBCOLOR rgb_grey50  = {0.5,0.5,0.5};

const GRAPH_RGBCOLOR rgb_albert  = {0.433,0.781,0.969};
const GRAPH_RGBCOLOR rgb_alberta = {0.000,0.391,0.605};


struct ogl_window
{
  Display      *dpy;
  Window       win;
  GLXContext   context;
  unsigned int width, height;
  float        xmin[MAX(2,DIM_MAX)];
  float        xmax[MAX(2,DIM_MAX)];
  float        diam[MAX(2,DIM_MAX)];
  Bool         doubleBuffered;
};
typedef struct ogl_window OGL_WINDOW;

#if DIM_OF_WORLD < 3
static Bool opengl_available = True;
#endif

/****************************************************************************/

#if DIM_OF_WORLD < 3
static Bool WaitForNotify(Display *d, XEvent *e, char *arg)
{
  return (e->type == MapNotify) && (e->xmap.window == (Window)arg);
}
#endif

/****************************************************************************/

#if DIM_OF_WORLD < 3
static Bool OGL_set_std_window(OGL_WINDOW *ogl_win)
{
  Bool res = false;

  if (ogl_win) {

    res = glXMakeCurrent(ogl_win->dpy, ogl_win->win, ogl_win->context);

    glViewport(0, 0, ogl_win->width, ogl_win->height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glOrtho(ogl_win->xmin[0], ogl_win->xmax[0],
	    ogl_win->xmin[1], ogl_win->xmax[1],
	    -1.0, 1.0);
  }
  return res;
}

#define OGL_FLUSH(ogl)\
if ((ogl)->doubleBuffered)glXSwapBuffers((ogl)->dpy, ogl->win);else glFlush()

static int open_info = 0;

static XVisualInfo *findVisual(Display *dpy, int screen, Bool *doubleBuffered)
{
	/*
	 * Find a GLX Visual to use.
	 *   Order of preference
	 *       24 bit mono double buffered
	 *       24 bit single buffered
	 *       any depth double buffered
	 *       any depth single buffered
	 */
  int dblBuf24[] = { GLX_RGBA, GLX_DOUBLEBUFFER,
		     GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, None };
  int snglBuf24[] = { GLX_RGBA, 
		      GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, None };
  int dblBuf[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
  int snglBuf[] = { GLX_RGBA, None };
  
  XVisualInfo *vi = NULL;

  if ((vi = glXChooseVisual(dpy, screen, dblBuf24)))
  {
    INFO(open_info,2,"Using dblBuf24\n");
    *doubleBuffered = True;
    return(vi);
  } 

  if ((vi = glXChooseVisual(dpy, screen, snglBuf24)))
  {
    *doubleBuffered = False;
    INFO(open_info,2,"Using snglBuf24\n");
    return(vi);
  }

  if ((vi = glXChooseVisual(dpy, screen, dblBuf)))
  {
    *doubleBuffered = True;
    INFO(open_info,2,"Using dblBuf\n");
    return(vi);
  }

  if ((vi = glXChooseVisual(dpy, screen, snglBuf)))
  {
    INFO(open_info,2,"Using snglBuf\n");
    *doubleBuffered = False;
    return(vi);
  }

  WARNING("can't find visual\n");
  return(NULL);
}

static Window createWindow(Display *dpy, int screen, XVisualInfo *vi,
			   const char *name, const char *geom)
{
  XSetWindowAttributes attributes;
  unsigned long valuemask = 0;
  Window        root = RootWindow(dpy, screen);
  Atom	        delete_window_atom = XInternAtom(dpy, "WM_DELETE_WINDOW", 0);
  Window        window;
  XSizeHints    size_hints;
  XEvent        event;
  int           status;
  const char    *default_geom = "300x300+0+0";

  attributes.colormap = XCreateColormap(dpy, root, vi->visual, AllocNone);
  if (!attributes.colormap)
  {
    WARNING("no color map\n");
    return(0);
  }
  valuemask = CWColormap;
  valuemask = CWColormap|CWBorderPixel|CWBackPixel;

  size_hints.flags = PResizeInc | PMinSize ;
  size_hints.width_inc  =  1;
  size_hints.height_inc =  1;
  size_hints.min_width  = 10;
  size_hints.min_height = 10;

  if (!geom) geom = default_geom;

  status = XGeometry(dpy, vi->screen, (char *) geom, (char *) default_geom, 
		     2, 1, 1, 0, 0, &(size_hints.x), &(size_hints.y),
		     &(size_hints.width), &(size_hints.height));
  if (status & (XValue|YValue)) 
  {
    size_hints.flags |= USPosition;
    size_hints.flags &= ~PPosition;
  }
  if (status & (WidthValue|HeightValue)) 
  {
    size_hints.flags |= USSize;
    size_hints.flags &= ~PSize;
  }

  if (size_hints.flags & USSize) 
  {
    size_hints.flags |= PAspect;
    size_hints.min_aspect.x = size_hints.width;
    size_hints.min_aspect.y = size_hints.height;
    size_hints.max_aspect.x = size_hints.width;
    size_hints.max_aspect.y = size_hints.height;
  }

  attributes.border_pixel = 0;
  attributes.background_pixel = 0;
  attributes.event_mask = StructureNotifyMask;
  window = XCreateWindow(dpy, root, size_hints.x, size_hints.y,
			 size_hints.width, size_hints.height, 0, vi->depth,
			 InputOutput, vi->visual, valuemask, &attributes);

  if (!window) 
  {
    WARNING("couldn't create a window\n");
    return(0);
  }

  XSetStandardProperties(dpy, window, name, name, None, NULL, 0, &size_hints);
  XSelectInput(dpy, window, ExposureMask|StructureNotifyMask);

  XMapWindow(dpy, window);
  
  XSetWMProtocols(dpy, window, &delete_window_atom, 1);
  XIfEvent(dpy, &event, WaitForNotify, (char*) window);
  XStoreName(dpy, window, name);

  return(window);
}

static OGL_WINDOW *OGL_create_window(const char *name, const char *geometry)
{
  FUNCNAME("OGL_create_window");
  Display      *dpy;
  int          screen;
  Window       window, root;
  int          dummy;
  Bool	       doubleBuffered;
  XVisualInfo  *vi;
  GLXContext   context;
  unsigned int bwidth, depth;
  int          x, y;
  OGL_WINDOW * ogl_win;
  
  if (!(dpy = XOpenDisplay(NULL))) 
  {
    WARNING("can't open X display\n");
    return (NULL);
  }
  screen = DefaultScreen(dpy);

  if (!(opengl_available = glXQueryExtension(dpy, &dummy, &dummy)))
  {
    WARNING("server doesn't support GLX Extension\n");
    return (NULL);
  }

  if (!(vi = findVisual(dpy, screen, &doubleBuffered)))
    return(NULL);
	
  if (!(window = createWindow(dpy, screen, vi, name, geometry)))
    return(NULL);

  context = glXCreateContext(dpy, vi, NULL, GL_TRUE);

  XFree(vi);

  if (context == NULL) 
  {
    WARNING("can't create context\n");
    return(NULL);
  }
  
  if (!glXMakeCurrent(dpy, window, context)) 
  {
    WARNING("glXMakeCurrent failed\n");
    return (NULL);
  }

  ogl_win = MEM_ALLOC(1, OGL_WINDOW);
  ogl_win->dpy = dpy;
  ogl_win->win = window;
  ogl_win->context  = context;
  ogl_win->doubleBuffered = doubleBuffered;
  
  XGetGeometry(dpy, window, &root, &x, &y,
	       &ogl_win->width, &ogl_win->height,
	       &bwidth, &depth);
  
  return(ogl_win);
}

/****************************************************************************/

static void OGL_destroy_window(OGL_WINDOW *ogl_win)
{
  /* FUNCNAME("OGL_destroy_window"); re-enable when MSG() is used here ... */ 

  if (!ogl_win)  return;

  glXDestroyContext(ogl_win->dpy, ogl_win->context);

  XDestroyWindow(ogl_win->dpy, ogl_win->win);
  XFlush(ogl_win->dpy);

  MEM_FREE(ogl_win, 1, OGL_WINDOW);
}

/****************************************************************************/

static void OGL_clear_window(OGL_WINDOW *ogl_win, const GRAPH_RGBCOLOR c)
{
  FUNCNAME("OGL_clear_window");

  Window       root;
  unsigned int w, h, depth, bwidth;
  int          x, y;

  if (!ogl_win) {
    MSG("no OGL_WINDOW\n");
    return;
  }

  OGL_set_std_window(ogl_win);

  /* Has the window size changed? */
  XGetGeometry(ogl_win->dpy, ogl_win->win,
	       &root, &x, &y, &w, &h, &bwidth, &depth);
 
  if (w != ogl_win->width || h != ogl_win->height){
    ogl_win->width  = w;
    ogl_win->height = h;
    XResizeWindow(ogl_win->dpy, ogl_win->win, w, h);
  }

  glXMakeCurrent(ogl_win->dpy, ogl_win->win, ogl_win->context);

  glViewport(0, 0, w, h);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glOrtho(ogl_win->xmin[0], ogl_win->xmax[0],
	  ogl_win->xmin[1], ogl_win->xmax[1],
	  -1.0, 1.0);

  if (c)
    glClearColor(c[0], c[1], c[2], 1.0);
  else
    glClearColor(1.0, 1.0, 1.0, 1.0);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  OGL_FLUSH(ogl_win);
}
#endif

/****************************************************************************/

/****************************************************************************/
/* Common graphic routines available to the user                            */
/****************************************************************************/

#if DIM_OF_WORLD == 1
#include "graphXO_1d.c"
#endif
#if DIM_OF_WORLD == 2
#include "graphXO_2d.c"
#endif

GRAPH_WINDOW graph_open_window(const char *title, const char *geometry,
			       REAL *world, MESH *mesh)
{
  FUNCNAME("graph_open_window");

  switch(mesh->dim) {
#if DIM_OF_WORLD == 1
      case 1:
	return graph_open_window_1d(title, geometry, world, mesh);
#endif
#if DIM_OF_WORLD == 2
      case 2:
	return graph_open_window_2d(title, geometry, world, mesh);
#endif
      case 3:
	ERROR("Not implemented for dim == 3!\n");
	return 0;
      default:
	ERROR_EXIT("Illegal mesh->dim: must equal DIM_OF_WORLD\n");
  }

  return 0;
}


void graph_close_window(GRAPH_WINDOW win)
{
#if DIM_OF_WORLD==1
  graph_close_window_1d(win);
#elif DIM_OF_WORLD==2
  graph_close_window_2d(win);
#else
  ERROR("Not implemented for dim == 3!\n");
#endif
  return;
}


void graph_clear_window(GRAPH_WINDOW win, const GRAPH_RGBCOLOR c)
{
#if DIM_OF_WORLD==1
  graph_clear_window_1d(win, c);
#elif DIM_OF_WORLD==2
  graph_clear_window_2d(win, c);
#else
  ERROR("Not implemented for dim == 3!\n");
#endif
  return;
}


void graph_mesh(GRAPH_WINDOW win, MESH *mesh, const GRAPH_RGBCOLOR c,
		FLAGS flag)
{
  FUNCNAME("graph_mesh");

  switch(mesh->dim) {
#if DIM_OF_WORLD == 1
      case 1:
	if(DIM_OF_WORLD != 1) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_mesh_1d(win, mesh, c, flag);
	break;
#endif
#if DIM_OF_WORLD == 2
      case 2:
	if(DIM_OF_WORLD != 2) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_mesh_2d(win, mesh, c, flag);
	break;
#endif
      case 3:
	ERROR("Not implemented for dim == 3!\n");
	return;
      default:
	ERROR_EXIT("Illegal dim!\n");
  }
}

void graph_drv(GRAPH_WINDOW win, const DOF_REAL_VEC *u,
	       REAL min, REAL max, int refine)
{
  FUNCNAME("graph_drv");

  TEST_EXIT(u && u->fe_space && u->fe_space->admin && u->fe_space->admin->mesh,
    "no vec or fe_space or admin or mesh!\n");

  switch(u->fe_space->admin->mesh->dim) {
#if DIM_OF_WORLD == 1
      case 1:
	if(DIM_OF_WORLD != 1) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_drv_1d(win, u, min, max, refine, NULL);
	break;
#endif
#if DIM_OF_WORLD == 2
      case 2:
	if(DIM_OF_WORLD != 2) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_drv_2d(win, u, min, max, refine);
	break;
#endif
      case 3:
	ERROR("Not implemented for dim == 3!\n");
	return;
      default:
	ERROR_EXIT("Illegal dim!\n");
  }
}

void graph_drv_d(GRAPH_WINDOW win, const DOF_REAL_D_VEC *ud,
		 REAL min, REAL max, int refine)
{
  FUNCNAME("graph_drv_d");

  TEST_EXIT(ud && ud->fe_space && ud->fe_space->admin && ud->fe_space->admin->mesh,
    "no vec or fe_space or admin or mesh!\n");

  switch(ud->fe_space->admin->mesh->dim) {
#if DIM_OF_WORLD == 1
      case 1:
	if(DIM_OF_WORLD != 1) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_drv_d_1d(win, ud, min, max, refine, NULL);
	break;
#endif
#if DIM_OF_WORLD ==2
      case 2:
	if(DIM_OF_WORLD != 2) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_drv_d_2d(win, ud, min, max, refine);
	break;
#endif
      case 3:
	ERROR("Not implemented for dim == 3!\n");
	return;
      default:
	ERROR_EXIT("Illegal dim!\n");
  }
}

void graph_el_est(GRAPH_WINDOW win, MESH *mesh, REAL (*get_est)(EL *),
		  REAL min, REAL max)
{
  FUNCNAME("graph_drv_d");

  if(!mesh) {
    ERROR("No mesh given!\n");
    return;
  }

  switch(mesh->dim) {
#if DIM_OF_WORLD == 1
      case 1:
	if(DIM_OF_WORLD != 1) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_el_est_1d(win, mesh, get_est, min, max, NULL);
	break;
#endif
#if DIM_OF_WORLD == 2
      case 2:
	if(DIM_OF_WORLD != 2) {
	  ERROR("dim must equal DIM_OF_WORLD == %d!\n", DIM_OF_WORLD);
	  return;
	}
	graph_el_est_2d(win, mesh, get_est, min, max);
	break;
#endif
      case 3:
	ERROR("Not implemented for dim == 3!\n");
	return;
      default:
	ERROR_EXIT("Illegal dim!\n");
  }
}

/****************************************************************************/

void graph_point(GRAPH_WINDOW win, const REAL p0[2], const GRAPH_RGBCOLOR c,
		 float ps)
{
#if DIM_OF_WORLD >= 3
  FUNCNAME("graph_point");
  
  ERROR("Not implemented for dim == 3!\n");
#else
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  OGL_set_std_window(ogl_win);
  
  glColor3fv(c ? c : rgb_black);
  glPointSize(ps > 0 ? ps : 1.0);
  
  glBegin(GL_POINTS);
  glVertex2d(p0[0], p0[1]);
  glEnd();
  
  OGL_FLUSH(ogl_win);
#endif
  return;
}


void graph_points(GRAPH_WINDOW win, int np, REAL (*p)[2], 
		  const GRAPH_RGBCOLOR c, float ps)
{
#if DIM_OF_WORLD >= 3
  FUNCNAME("graph_points");
  
  ERROR("Not implemented for dim == 3!\n");
#else
  int        i;
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  OGL_set_std_window(ogl_win);
  
  glColor3fv(c ? c : rgb_black);
  glPointSize(ps > 0 ? ps : 1.0);
  
  glBegin(GL_POINTS);
  for (i = 0; i < np; i++)
    glVertex2d(p[i][0], p[i][1]);
  
  glEnd();
  
  OGL_FLUSH(ogl_win);
#endif

  return;
}

/****************************************************************************/

void graph_line(GRAPH_WINDOW win, const REAL p0[2], const REAL p1[2], 
		const GRAPH_RGBCOLOR c, float lw)
{
#if DIM_OF_WORLD >= 3
  FUNCNAME("graph_line");

  ERROR("Not implemented for dim == 3!\n");
#else
  float xy[2];
  int   j;
  OGL_WINDOW *ogl_win = (OGL_WINDOW *)win;

  OGL_set_std_window(ogl_win);
  
  glColor3fv(c ? c : rgb_black);
  glLineWidth(lw > 0 ? lw : 1.0);
  
  glBegin(GL_LINE_STRIP);
  for (j=0; j<2; j++) xy[j] = p0[j];
  glVertex2fv(xy);
  for (j=0; j<2; j++) xy[j] = p1[j];
  glVertex2fv(xy);
  glEnd();
  
  OGL_FLUSH(ogl_win);
#endif

  return;
} 
