#ifdef GL_GRAPHICS

#define DEBUG	1

#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <gl/gl.h>
#include <device.h>

extern int errno;

#include <sys/types.h>
#include <sys/time.h>

#include "../h/cdefs.h"
#include "../h/timeval.h"
#include "grf4.h"
#include "grf4Internal.h"
#include "grf4GlObject.h"

TRSF* glTransPtr;
int glPerspectiveMode;
float glNearPlane;

static TRSF Trans;
static long winw;
static long winh;
static long winx;
static long winy;
static long panw;
static long panh;
static long drawAreaVisible;

static int bgColor;
static int fgColor;

static int (*resizeHandler)();
static int (*redrawHandler)();
static int (*motionHandler)();
static int (*buttonHandler)();

static float setLeft;
static float setRight;
static float setBottom;
static float setTop;
static float setNear;
static float setFar;

static int screenCoordsSet = 0;
static int savedZbufferState;

static Matrix identMatrix = { 1.0, 0.0, 0.0, 0.0,
			      0.0, 1.0, 0.0, 0.0,
			      0.0, 0.0, 1.0, 0.0,
			      0.0, 0.0, 0.0, 1.0 };

static unsigned long getcpack();
static void orthoFxn();
static void windowFxn();
static void setScreenCoords();
static void clearScreenCoords();
static void drawDisplay();
static void eventHandler();
static do_lmdef();
static void getWindowSize();
static void getButtonExtent();
static void drawButton();
static void drawButtonFxn();
static void pxlFxn();
static void drawFxn();
static void moveFxn();
static void draw2Fxn();
static void move2Fxn();
static void clearFxn();
static void cycleFxn();
static void waitFxn();
static eachcycle();
static handle_event();
static void decompose();
static void doDefMaterial ();
static void *defMaterial();
static void *defMaterialD();
static void* defLight();
static void setLight();
static void resetLights();
static long capacityFxn();

static unsigned long getcpack()
{
	short r,g,b;
	gRGBcolor(&r,&g,&b);
	return (RGBPACK(r,g,b));
}

static void orthoFxn(l,r,b,t,n,f)
float l,r,b,t,n,f;
{
	setLeft = l;
	setRight = r;
	setBottom = b;
	setTop = t;
	setNear = n;
	setFar = f;

	glPerspectiveMode = 0;
	glNearPlane = n;

	ortho (l,r,b,t,n,f);
}
	
static void windowFxn(l,r,b,t,n,f)
float l,r,t,b,n,f;
{
	setLeft = l;
	setRight = r;
	setBottom = b;
	setTop = t;
	setNear = n;
	setFar = f;

	glPerspectiveMode = 1;
	glNearPlane = n;

	window (l,r,b,t,n,f);
}

static void setScreenCoords()
{
	screenCoordsSet = 1;

	savedZbufferState = getzbuffer();
	zbuffer(FALSE);
	pushviewport();
	pushmatrix();
	viewport((Screencoord)0,(Screencoord)(winw-1),
		 (Screencoord)0,(Screencoord)(winh-1));
	loadmatrix(identMatrix);
	ortho2 (-0.5,winw-0.5,-0.5,winh-0.5);
}

static void clearScreenCoords()
{
	if (glPerspectiveMode)
	 { window (setLeft,setRight,setBottom,setTop,setNear,setFar);
	 }
	else
	 { ortho (setLeft,setRight,setBottom,setTop,setNear,setFar);
	 }
	popmatrix();
	popviewport();
	zbuffer(savedZbufferState);

	screenCoordsSet = 0;
}

#define LIGHT_GREY	RGBPACK(160,160,160)
#define DARK_GREY	RGBPACK(100,100,100)
#define DEEP_GREY	RGBPACK(60,60,60)
#undef WHITE
#define WHITE		RGBPACK(255,255,255)
#undef BLACK
#define BLACK		RGBPACK(0,0,0)

static void drawDisplay()
{
	long savec;

	setScreenCoords();
	savec = getcpack();

	zclear();

	frontbuffer(TRUE);
	cpack (bgColor);
	clear ();
	cpack (WHITE);
	if (panw)
	 { move2i (winw-panw,0);
	   draw2i (winw-panw,winh-1);
	   cpack (LIGHT_GREY);
	   rectfi (winw-panw+1,0,winw-1,winh-1);
	 }
	frontbuffer(FALSE);

	cpack (savec);
	clearScreenCoords();
}

static void eventHandler(code, fxn)
int code;
int (*fxn)();
{
	switch (code)
	 { case REDRAW_EVENT:
	    { redrawHandler = fxn;
	      break;
	    }
	   case RESIZE_EVENT:
	    { resizeHandler = fxn;
	      break;
	    }
	   case MOTION_EVENT:
	    { motionHandler = fxn;
	      break;
	    }
	   case BUTTON_EVENT:
	    { buttonHandler = fxn;
	      break;
	    }
	 }
}

static float light_1[] = { POSITION, 1, 1, 1, 0,
		    LMNULL };

static float light_2[] = { POSITION, -1, -1, -1, 0,
			   LCOLOR, .2, .2, .2,
		    LMNULL };

static float material_1[] = { AMBIENT, .2, .2, .2,
		       DIFFUSE, 1, 1, 1,
		       EMISSION, .3, 0, 0,
		       SHININESS, 0,
		       LMNULL };

static float lmodel_1[] = { AMBIENT, 0, 0, 0,
		       LMNULL };

#define MIN_WINW	21
#define MIN_WINH	21

#ifdef _AIX
#include <unistd.h>
#include <fcntl.h>
int winFd;
#endif

grf4GlInit (name, fgcolor, bgcolor, x,y,w,h)
char *name;
int fgcolor;
int bgcolor;
int x,y,w,h;
{
#ifdef _AIX
	char isopen[FD_SETSIZE];
	int k;

	bzero (isopen, FD_SETSIZE);
	for (k=0; k<FD_SETSIZE; k++)
	 { isopen[k] = (fcntl(k,F_GETFD,0) >= 0);
	 } 
	winFd = -1;
#endif

#ifndef _AIX
	foreground();
#endif	
	if (x != -1)
	 { prefposition (x,x+w-1,y,y+h-1);
	   printf ("called prefposition\n");
	 }
#ifdef _AIX 
/* XXX XXX XXX AIX doesn't seem to support interactive placement! */
	else
	 { prefposition (256,511,256,511);
	 }
#endif
	if (winopen (name) == -1)
	 { return (-1);
	 }
	buttonInit (&panw, &panh);
	getorigin (&winx, &winy);
	getsize (&winw, &winh);
	if (panw)
	 { /* Make sure the new window is big enough to include the
	      panel. */
	   winw += panw;
	   winh = MAX(panh,winh);
	   winposition (winx, winx+winw-1, winy, winy+winh-1);
	   minsize (panw+MIN_WINW, MAX(MIN_WINH,panh));
	   winconstraints();
	 }

	RGBmode();
	doublebuffer();
	gconfig();
	
	mmode(MVIEWING);

	bgColor = bgcolor;
	fgColor = fgcolor;

	/* setup for receiving events */

	qdevice(CURSORX);
	qdevice(CURSORY);
	qdevice(LEFTMOUSE);
	qdevice(MIDDLEMOUSE);
	qdevice(REDRAW);
	qdevice(PIECECHANGE);

	/* turn on the z-buffer, and set up default lighting */

	zbuffer(TRUE);
	zfunction(ZF_LEQUAL);
#if 0
	do_lmdef(DEFMATERIAL,1,material_1);
	do_lmdef(DEFLIGHT,1,light_1);
	do_lmdef(DEFLIGHT,2,light_2);
#endif
	do_lmdef(DEFLMODEL,1,lmodel_1);
	lmbind(LMODEL,1);

	loadmatrix(identMatrix);
	
	if (winw-panw-1 >= 0)
	 { viewport ((Screencoord)0, (Screencoord)(winw-panw-1), 
		     (Screencoord)0, (Screencoord)(winh-1));
	   drawAreaVisible = 1;
	 }
	else
	 { drawAreaVisible = 0;
	 }
	orthoFxn (-0.5, winw-0.5, -0.5, winh-0.5, -1.0, 1.0);

	drawDisplay();

#ifdef _AIX
	for (k=0; k<FD_SETSIZE; k++)
	 { if (!isopen[k] && (fcntl(k,F_GETFD,0) >= 0))
	    { if (winFd == -1)
	       { winFd = k;
	       }
	      else
	       { fprintf (stderr,"grf4Init() -- more than 1 winFd detected\n");
		 exit (-1);
	       }
	    }
	 } 
	if (winFd == -1)
	 { fprintf (stderr, "grf4Init() -- winFd not detected\n");
	   exit (-1);
	 }
#endif
	return (1);
}

static do_lmdef(type,index,props)
int type;
int index;
float *props;
{
	int np;

 	if (props)
	 { for (np=0;props[np]!=LMNULL;np++) ;
	   if (np > 0) np ++;
	 }
	else
	 { np = 0;
	 }
	lmdef(type,index,np,props);
}

static void getWindowSize(w,h)
int *w, *h;
{
	*w = winw;
	*h = winh;
}

#define BUTTON1			1
#define BUTTON_BORDER_WIDTH	3

static void getButtonExtent(b)
BUTTON_DESC *b;
{
	int height;
	int descent;
	int margin;

	descent = getdescender();
	height = getheight();	
	margin = 2 + BUTTON_BORDER_WIDTH;
	b->coffX = margin;
#ifdef _AIX
/* AIX didn't seem to get the character position right */
	b->coffY = margin + descent/2;
#else
	b->coffY = margin + descent;
#endif
	b->minw = 2*margin + strwidth(b->name);
	b->minh = 2*margin + (height-descent-1);
}

static void drawButton(b)
BUTTON_DESC *b;
{
	int x1,x2;
	int y1,y2;

	int baseColor;
	int charColor;
	int topEdgeColor;
	int bottomEdgeColor;

	int cbaseX;
	int cbaseY;

	x1 = b->x;
	y1 = b->y;
	x2 = x1 + b->w - 1;
	y2 = y1 + b->h - 1;

	cpack(WHITE);	/* white */

	cbaseX = b->x + b->coffX + (b->w-b->minw)/2;
	cbaseY = b->y + b->coffY + (b->h-b->minh)/2;

	if (b->flags & BF_NOTBOXED)
	 { cpack (LIGHT_GREY);
	   rectfi (x1,y1,x2,y2);
	   if (b->flags & BF_ILLUMINATED)
	    { cpack (WHITE);
	    }
	   else
	    { cpack (BLACK);
	    }
	   cmov2i (cbaseX, cbaseY);
	   charstr (b->name);
	 }
	else
	 { cpack (BLACK);
	   recti (x1,y1,x2,y2);		/* outer black box */
	   if (b->flags & BF_ILLUMINATED)
	    { baseColor = LIGHT_GREY;
	      charColor = WHITE;
	      topEdgeColor = WHITE;
	      bottomEdgeColor = DARK_GREY;
	    }
	   else
	    { baseColor = DARK_GREY;
	      charColor = BLACK;
	      topEdgeColor = DEEP_GREY;
	      bottomEdgeColor = LIGHT_GREY;
	    }
	   cpack (topEdgeColor);
	   recti (x1+1,y1+1,x2-1,y2-1);
	   cpack (bottomEdgeColor);
	   move2i (x1+1,y1+1);
	   draw2i (x2-1,y1+1);
	   cpack (baseColor);
	   rectfi (x1+2,y1+2,x2-2,y2-2);
	   cpack (charColor);
	   cmov2i (cbaseX, cbaseY);
	   charstr (b->name);
	 }
}

static void drawButtonFxn(b)
BUTTON_DESC *b;
{
	long savec;

	savec = getcpack();
	if (!screenCoordsSet)
	 { setScreenCoords();
	   frontbuffer(TRUE);
	   drawButton(b);
	   frontbuffer(FALSE);
	   clearScreenCoords();
	 }
	else
	 { drawButton(b);
	 }
	cpack (savec);
}

static void pxlFxn()
{
	fprintf (stderr, 
		"Internal software error: call to pixel level function\n");
}

static void drawFxn(x,y,z)
double x,y,z;
{
	draw(x,y,z);
}

static void moveFxn(x,y,z)
double x,y,z;
{
	move(x,y,z);
}

static void draw2Fxn(x,y)
double x,y;
{
	draw2(x,y);
}

static void move2Fxn(x,y)
double x,y;
{
	move2(x,y);
}

static void clearFxn()
{
	unsigned long savec;
	savec = getcpack();
	cpack(bgColor);
	clear();
	zclear();
	cpack(savec);
}

void loadTrans(T)
TRSF *T;
{
	Matrix m;

	m[0][0] = T->n.x;
	m[0][1] = T->n.y;
	m[0][2] = T->n.z;
	m[0][3] = 0.0;

	m[1][0] = T->o.x;
	m[1][1] = T->o.y;
	m[1][2] = T->o.z;
	m[1][3] = 0.0;

	m[2][0] = T->a.x;
	m[2][1] = T->a.y;
	m[2][2] = T->a.z;
	m[2][3] = 0.0;

	m[3][0] = T->p.x;
	m[3][1] = T->p.y;
	m[3][2] = T->p.z;
	m[3][3] = 1.0;

	Trans = *T;
	glTransPtr = &Trans;
	loadmatrix(m);
}

static void cycleFxn()
{
	eachcycle();
}

static void waitFxn(sd)
int sd;
{
	fd_set fdset;
	int qfd, n;
#ifdef _AIX
	qfd = winFd;
#else
	qfd = qgetfd();
#endif
	do
	 { FD_ZERO (&fdset);
	   if (sd >= 0)
	    { FD_SET (sd, &fdset);
	    }
	   FD_SET (qfd, &fdset);
	   while ((n = select (FD_SETSIZE, &fdset, NULL, NULL, NULL)) < 0 &&
		   errno == EINTR)
	      ;
	   if (n < 0)
	    { perror ("grf4Wait() -- error in select");
	      exit (-1);
	    }
	   if (FD_ISSET(qfd, &fdset))
	    { eachcycle();
	    }
	 }
	while (sd >= 0 ? !FD_ISSET(sd, &fdset) : 1);
}

selectFxn (gEvnt, rfds, wfds, efds, timeout)
int *gEvnt;
fd_set *rfds, *wfds, *efds;
struct timeval *timeout;
{
	fd_set rset;
	int qfd, n;

	if (!rfds)
	 { rfds = &rset;
	   FD_ZERO (rfds);
	 }
	if (gEvnt)
	 { *gEvnt = 0;
	 }

	/* set qfd to the file descriptor for queue events */
#ifdef _AIX
	qfd = winFd;
#else
	qfd = qgetfd();
#endif

	FD_SET (qfd, rfds);

	n = select (FD_SETSIZE, rfds, wfds, efds, timeout);
	if (n > 0)
	 { if (FD_ISSET(qfd, rfds))
	    { eachcycle();	/* queue event occurred; handle */
	      FD_CLR (qfd, rfds);
	      if (gEvnt)
	       { *gEvnt = 1;
	       }
	      n--;
	    }
	 }
	return(n);
}

static int motionX = -1;
static int motionY = -1;

static eachcycle()
{
	long int dev;
	short int val;

	while (qtest())
	 { dev = qread(&val);
	   handle_event(dev, val);
	 }
	if (motionHandler && (motionX != -1 || motionY != -1))
	 { if (motionX == -1)
	    { motionX = getvaluator(CURSORX) - winx;
	    }
	   if (motionY == -1)
	    { motionY = getvaluator(CURSORY) - winy;
	    }
	   (*motionHandler)(motionX, motionY);
	 }
}

static handle_event(dev, val)
long int dev;
short int val;
{
 switch (dev)
  { default:
     { break;
     }
    case PIECECHANGE:
    case REDRAW:
     { long w, h;
       int resizeEvent;

       getsize(&w, &h);
       getorigin(&winx,&winy);
       if (w != winw || h != winh)
	{ /* resize event */
	  winw = w;
	  winh = h;
	  if (winw-panw-1 >= 0)
	   { viewport ((Screencoord)0, (Screencoord)(winw-panw-1), 
		       (Screencoord)0, (Screencoord)(winh-1));
	     drawAreaVisible = 1;
	   }
	  else
	   { drawAreaVisible = 0;
	   }
	  if (resizeHandler)
	   { (*resizeHandler)(w,h);
	   }
	}
       drawDisplay();
       if (redrawHandler)
        { (*redrawHandler)();
	}
       break;
     }
    case CURSORX:
     { motionX = (val-winx);
       break;
     }
    case CURSORY:
     { motionY = (val-winy);
       break;
     }
    case LEFTMOUSE:
     { if (buttonHandler)
	{ (*buttonHandler)(1, (val!=0), getvaluator(CURSORX)-winx,
					getvaluator(CURSORY)-winy);
	}
       break;
     }
    case MIDDLEMOUSE:
     { if (buttonHandler)
	{ (*buttonHandler)(2, (val!=0), getvaluator(CURSORX)-winx,
					getvaluator(CURSORY)-winy);
	}
       break;
     }
    case RIGHTMOUSE:
     { if (buttonHandler)
	{ (*buttonHandler)(3, (val!=0), getvaluator(CURSORX)-winx,
					getvaluator(CURSORY)-winy);
	}
       break;
     }
  }
}

/* Lighting and material functions */

#define BLUE_PART(m)	(((m)&0xff0000)>>16)
#define GREEN_PART(m)	(((m)&0xff00)>>8)
#define RED_PART(m)	(((m)&0xff))

static void decompose(v,m)
float *v;
unsigned long m;
{
	v[0] = RED_PART(m)/255.0;
	v[1] = GREEN_PART(m)/255.0;
	v[2] = BLUE_PART(m)/255.0;
}

static void doDefMaterial (idx, d, a, e, s, shine, alpha)
int idx;
unsigned long d, a, e, s;
float shine, alpha;
{
	float proplist[21];

	proplist[0] = DIFFUSE;
	decompose(&proplist[1], d);
	proplist[4] = AMBIENT;
	decompose(&proplist[5], a);
	proplist[8] = EMISSION;
	decompose(&proplist[9], e);
	proplist[12] = SPECULAR;
	decompose(&proplist[13], s);
	proplist[16] = SHININESS;
	proplist[17] = shine;
	proplist[18] = ALPHA;
	proplist[19] = alpha;
	proplist[20] = LMNULL;
	
	lmdef(DEFMATERIAL, idx, ARRAY_SIZE(proplist), proplist);
}

static int materialIdx = 0;

static void *defMaterial(diffuse, ambient, emission, specular, shine, alpha)
unsigned long diffuse, ambient, emission, specular;
float shine, alpha;
{
	int *ip;

	ip = ALLOC(int);
	*ip = ++materialIdx;
	doDefMaterial (*ip, diffuse, ambient, emission, 
			    specular, shine, alpha);
	return ((void*)ip);
}

static void *defMaterialD(md)
MATERIAL_DEF *md;
{
	int *ip;

	ip = ALLOC(int);
	*ip = ++materialIdx;
	doDefMaterial (*ip, md->diffuse, md->ambient, md->emission, 
			    md->specular, md->shine, md->alpha);
	return ((void*)ip);
}

void glSetMaterial(ptr)
void *ptr;
{
	lmbind(MATERIAL, *(int*)ptr);
}

struct lampDesc {
	int code;
	int idx;
  };

static struct lampDesc lampPool[] = {
	LIGHT0, 0, LIGHT1, 0, LIGHT2, 0, LIGHT3, 0, 
	LIGHT4, 0, LIGHT5, 0, LIGHT6, 0, LIGHT7, 0,
};

static int lightIdx = 0;

static void* defLight(color, ambient, x,y,z,w)
unsigned long color, ambient;
float x,y,z,w;
{
	int *ip;
	float proplist[14];

	ip = ALLOC(int);
	*ip = ++lightIdx;

	proplist[0] = LCOLOR;
	decompose(&proplist[1], color);
	proplist[4] = AMBIENT;	
	decompose(&proplist[5], ambient);
	proplist[8] = POSITION;
	proplist[9] = x;
	proplist[10] = y;
	proplist[11] = z;
	proplist[12] = w;
	proplist[13] = LMNULL;

	lmdef(DEFLIGHT, *ip, ARRAY_SIZE(proplist), proplist);

	return ((void*)ip);
}

static void setLight(n, ptr)
int n;
void *ptr;
{
	if (n >= ARRAY_SIZE(lampPool))
	 { fprintf (stderr, "grf4SetLight(): don't have that many lamps\n");
	   return;
	 }
	if (n < 0)
	 { fprintf (stderr, "grf4SetLight(): negative lamp index\n");
	   return;
	 }
	if (ptr)
	 { lmbind (lampPool[n].code, (*(int*)ptr));
	   lampPool[n].idx = *(int*)ptr;
	 }
	else
	 { lmbind (lampPool[n].code, 0);
	   lampPool[n].idx = 0;
	 }
}	   

static void resetLights()
{
	int i;

	for (i=0; i<ARRAY_SIZE(lampPool); i++)
	 { if (lampPool[i].idx)
	    { lmbind (lampPool[i].code, lampPool[i].idx);
	    }
	 }
}

static long capacityFxn()
{
	return (GRF_HARDWARE_A | GRF_COLOR | GRF_LIGHTING);
}

grf4GlSetupFxns (ftab)
struct grf4fxnTable *ftab;
{
	ftab->capacity = capacityFxn;
	ftab->getWindowSize = getWindowSize;
	ftab->cycle = cycleFxn;
	ftab->wait = waitFxn;
	ftab->select = selectFxn;
	ftab->ortho = orthoFxn;
	ftab->window = windowFxn;
	ftab->loadTrans = loadTrans;
	ftab->color = cpack;
	ftab->clear = clearFxn;
	ftab->flush = swapbuffers;
	ftab->move = moveFxn;
	ftab->draw = drawFxn;
	ftab->move2 = move2Fxn;
	ftab->draw2 = draw2Fxn;
	ftab->drawPrism = glDrawPrism;
	ftab->drawSphere = glDrawSphere;
	ftab->drawPolygon = glDrawPolygon;
	ftab->drawLineSet = glDrawLineSet;
	ftab->genDisc = glGenDisc;
	ftab->genCone = glGenCone;
	ftab->genPrism = glGenPrism;
	ftab->genSphere = glGenSphere;
	ftab->genPolygon = glGenPolygon;
	ftab->genRegPolygon = glGenRegPolygon;
	ftab->genLineSet = glGenLineSet;
	ftab->setDepth = glSetDepth;
	ftab->eventHandler = eventHandler;

	ftab->drawButton = drawButtonFxn;
	ftab->getButtonExtent = getButtonExtent;
	
	ftab->pxlDraw = pxlFxn;
	ftab->pxlClear = pxlFxn;
	ftab->pxlFlush = pxlFxn;

	/* lighting and material functions are not implemented */

	ftab->defLight = defLight;
	ftab->defMaterial = defMaterial;
	ftab->defMaterialD = defMaterialD;
	ftab->setLight = setLight;
	ftab->resetLights = resetLights;
	ftab->setMaterial = glSetMaterial;

	return (1);
}

lightingHack()
{
	lmbind(MATERIAL,1);
 	lmbind(LIGHT0,1);
 	lmbind(LIGHT1,2);
	lmbind(LMODEL,1);
}

#else

#include <stdio.h>

grf4GlInit()
{
	return(-1);	
}

grf4GlSetupFxns()
{
	fprintf (stderr, "grf4Init(): GL support not available\n");
	return (-1);
}

#endif	/* GL_GRAPHICS */
