#include <stdio.h>
#include <math.h>
#include <strings.h>	
#include <sys/types.h>
#include <sys/time.h>

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

#define X11_H		1
#define SUNVIEW_H	2
#define GL_H		3
#define GL_WF_H	4

#if HLS
int grf4HiddenLineMode = 0;
#endif

static int hostType;
static int displayFlags;
static int initDone;

static int winw;
static int winh;
static int directionOfY;

static void (*redrawFxn)();
static void *redrawArg;

static struct grf4fxnTable fxntab;

static float desiredLeft;
static float desiredRight;
static float desiredBottom;
static float desiredTop;
static float desiredNear;
static float desiredFar;

static float virtL;
static float virtB;
static float virtW;
static float virtH;

static float aspectRatio;

static int perspective;

static int winPlaceX;
static int winPlaceY;
static int winPlaceW;
static int winPlaceH;
static int winPlaced;

BUTTON_DESC *buttonList;
int numButtons;
int rowNumber = 1;

int buttonSpacing;
int buttonWidth;
int buttonHeight;
int panelWidth;
int panelHeight;

#define NO_VIEW_CTRL		0
#define YAW_PITCH_CTRL		1
#define X_Y_CTRL		2
#define ZOOM_CTRL		3
#define PANY_PANX_CTRL		4
#define TILT_CTRL		5
	
static int viewCtrlMode = NO_VIEW_CTRL;
static int yCtrlButton;
static int xCtrlButton;
static int yawCtrlButton;
static int pitchCtrlButton;
static int panXCtrlButton;
static int panYCtrlButton;
static int tiltCtrlButton;
static int zoomCtrlButton;
static int xMotionEnable = 1;
static int yMotionEnable = 1;

typedef struct _TRSF_BLK {
	struct _TRSF_BLK *next;
	TRSF T;
  } TRSF_BLK;

static TRSF_BLK *trsfStack;

static TRSF E;
static TRSF T;
static TRSF L;	/* not used right now */

static drawObjCnt = 0;

static int determineHostType();
static popMaterial();
static pushMaterial();
static adjustScreenTrans();
static BUTTON_DESC *getButton();

grf4GetDrawObjCnt()
{
	return (drawObjCnt);
}

grf4PlaceWindow (x,y,w,h)
{
	winPlaceX = x;
	winPlaceY = y;
	winPlaceW = w;
	winPlaceH = h;
	winPlaced = 1;
}

static int determineHostType(flags)
int flags;
{
	extern char *getenv();

	if (flags & GRF_X11)
	 {
#ifdef X11_GRAPHICS
	   if (getenv ("DISPLAY"))
	    { return (X11_H);
	    }
#endif
	   return (0);
	 }
#ifdef GL_GRAPHICS
	if (flags & GRF_WIRE_FRAME)
	 { return (GL_WF_H);
	 }
	else
	 { return (GL_H);
	 }
#else
	if (getenv("DISPLAY"))
	 { return (X11_H);
	 }
#ifdef sun
	else if (getenv("WINDOW_PARENT"))
	 { return (SUNVIEW_H);
	 }
#endif
	else
	 { return (0);
	 }
#endif
}

typedef struct matStackElem {
	struct matStackElem *next;
	void *m;
} MatStackElem;

static MatStackElem *matStackFree;
static MatStackElem *matStack;

static void* currentMaterial;

static popMaterial()
{
	MatStackElem *se;

	if (se = matStack)
	 { matStack = se->next;
	   (*fxntab.setMaterial)(se->m);
	   se->next = matStackFree;
	   matStackFree = se;
	 }
}

static pushMaterial()
{
	MatStackElem *se;

	if ((se = matStackFree) == 0)
	 { se = ALLOC(MatStackElem);
	 }
	else
	 { matStackFree = se->next;
	 }
	se->next = matStack;
	matStack = se;
	se->m = currentMaterial;
}

static int fxnInitDone;

doFxnInit(flags)
int flags;
{
	fxnInitDone = 1;

	if ((hostType = determineHostType(flags)) == 0)
	 { return;
	 }

	/* instantiate functions depending on host type */

	switch (hostType)
	 { case GL_H:
	    { if (grf4GlSetupFxns (&fxntab) < 0)
	       { return (-1);
	       }
	      displayFlags = 0;
	      break;
	    }
	   case GL_WF_H:
	    { if (grf4GlwfSetupFxns (&fxntab) < 0)
	       { return (-1);
	       }
	      gdrSetupFxns (&fxntab);
	      displayFlags = GRF_WIRE_FRAME;
	      break;
	    }
	   case SUNVIEW_H:
	    { if (grf4SvSetupFxns (&fxntab) < 0)
	       { return (-1);
	       }
	      gdrSetupFxns (&fxntab);
	      displayFlags = GRF_WIRE_FRAME;
	      break;
	    }
	   case X11_H:
	    { if (grf4X11SetupFxns (&fxntab) < 0)
	       { return (-1);
	       }
	      gdrSetupFxns (&fxntab);
	      displayFlags = GRF_WIRE_FRAME | GRF_X11;
	      break;
	    }
	 }
}

grf4SetDisplay (flags)
int flags;
{
	if (!fxnInitDone)
	 { doFxnInit(flags);
	 }
	if (hostType == 0)
	 { return (-1);
	 }
	return (displayFlags | (flags & ~(GRF_WIRE_FRAME | GRF_X11)));
}

grf4Init (name, fgcolor, bgcolor, flags)
char *name;
int fgcolor;
int bgcolor;
int flags;
{
	int x,y,w,h;

	extern onRedrawEvent();
	extern onResizeEvent();
	extern onButtonEvent();
	extern onMotionEvent();
	extern onWinExitEvent();
	extern void buttonCompile();

	if (!fxnInitDone)
	 { doFxnInit(flags);
	 }
	if (winPlaced)
	 { x = winPlaceX;
	   y = winPlaceY;
	   w = winPlaceW;
	   h = winPlaceH;
	 }
	else
	 { x = y = w = h = -1;
	 }

	if (hostType == 0)
	 { return (-1);
	 }

	(*fxntab.eventHandler) (RESIZE_EVENT, onResizeEvent);
	(*fxntab.eventHandler) (REDRAW_EVENT, onRedrawEvent);
	(*fxntab.eventHandler) (MOTION_EVENT, onMotionEvent);
	(*fxntab.eventHandler) (BUTTON_EVENT, onButtonEvent);
	(*fxntab.eventHandler) (WINEXIT_EVENT, onWinExitEvent);

	if (flags & GRF_VIEW_CONTROL)
	 { int onButton();

	   grf4AddButton("View Control", BF_NOTBOXED|BF_INACTIVE, 1, 0, 0);
	   yawCtrlButton = grf4AddButton("yaw", BF_ILLUMINATED, 1,
					  onButton, YAW_PITCH_CTRL);
	   pitchCtrlButton = grf4AddButton("pitch", BF_SAMEROW|BF_ILLUMINATED,
					  1, onButton, YAW_PITCH_CTRL);
	   xCtrlButton = grf4AddButton("x", 0, 1, 
					  onButton, X_Y_CTRL);
	   yCtrlButton = grf4AddButton("y", BF_SAMEROW, 1, 
					  onButton, X_Y_CTRL);
	   zoomCtrlButton = grf4AddButton("zoom", 0, 1, onButton, ZOOM_CTRL);
	   panXCtrlButton = grf4AddButton("pan x", 0, 1, 
					  onButton, PANY_PANX_CTRL);
	   panYCtrlButton = grf4AddButton("pan y", BF_SAMEROW, 1, 
					  onButton, PANY_PANX_CTRL);
	   tiltCtrlButton = grf4AddButton("tilt", 0, 1, onButton, TILT_CTRL);
	   viewCtrlMode = YAW_PITCH_CTRL;
	 }

	switch (hostType)
	 { case GL_H:
	    { if (grf4GlInit (name, fgcolor, bgcolor, x,y,w,h) < 0)
	       { fprintf (stderr, "Could not initialize GL graphics\n");
		 return (-1);
	       }
	      break;
	    }
	   case GL_WF_H:
	    { if (grf4GlwfInit (name, fgcolor, bgcolor, x,y,w,h) < 0)
	       { fprintf (stderr, "Could not initialize GL wireframe graphics\n");
		 return (-1);
	       }
	      break;
	    }
	   case SUNVIEW_H:
	    { if (grf4SvInit (name, fgcolor, bgcolor, x,y,w,h) < 0)
	       { fprintf (stderr, "Could not initialize SunView graphics\n");
		 return (-1);
	       }
	      break;
	    }
	   case X11_H:
	    { if (grf4X11Init (name, fgcolor, bgcolor, x,y,w,h) < 0)
	       { fprintf (stderr, "Could not initialize X11 graphics\n");
		 return (-1);
	       }
	      break;
	    }
	 }
	(*fxntab.getWindowSize) (&winw, &winh, &directionOfY);
	buttonCompile (winw, winh);
	winw -= panelWidth;

	if (hostType != GL_H)
	 { gdrInit (winw, winh, directionOfY);
	 }
	(*fxntab.ortho)(-0.5, winw+0.5, -0.5, winh+0.5, -1.0, 1.0);
	identTrsf (&E);
	identTrsf (&L);	/* L not used right now */
	identTrsf (&T);
	(*fxntab.loadTrans)(&T);

	drawAllButtons ();

	currentMaterial = (*fxntab.defMaterial)(0xffffff, 0, 0, 0, 0.0, 0.0);
	(*fxntab.setMaterial)(currentMaterial);

	initDone = 1;
	return (displayFlags | (flags & ~(GRF_WIRE_FRAME | GRF_X11)));
}

long grf4Capacity()
{
	return (*fxntab.capacity)();
}

grf4HideLines(on)
int on;
{
	grf4HiddenLineMode = on;
}

grf4DumpPostScript(fp,name,flags,w,h)
FILE *fp;
char *name;
int flags;
float w, h;
{
	PostScriptInit (fp, name, flags, w, h);
}

grf4GetWindowSize (w, h)
int *w, *h;
{
	*w = winw;
	*h = winh;
}

grf4SetDrawFxn (fxn, arg)
void (*fxn)();
void *arg;
{
	redrawFxn = fxn;
	redrawArg = arg;
}

grf4Cycle ()
{
	(*fxntab.cycle)();
}

grf4Wait (sd)
int sd;
{
	(*fxntab.wait)(sd);
}

grf4Select (gEvnt, rfds, wfds, efds, timeout)
int *gEvnt;
fd_set *rfds, *wfds, *efds;
struct timeval *timeout;
{
	return (*fxntab.select)(gEvnt, rfds, wfds, efds, timeout);
}

grf4Window (left, right, bottom, top, near, far, aspectSet)
float left, right, bottom, top, near, far;
float aspectSet;
{
	float winAr;

	desiredLeft = left;
	desiredRight = right;
	desiredBottom = bottom;
	desiredTop = top;
	desiredNear = near;
	desiredFar = far;

	perspective = 1;

	if (aspectSet > 0.0)
	 { aspectRatio = aspectSet;
	 }
	else
	 { aspectRatio = (winw*(top-bottom))/(winh*(right-left));
	 }
	adjustScreenTrans (winw, winh);
}

grf4Ortho (left, right, bottom, top, near, far, aspectSet)
float left, right, bottom, top, near, far;
float aspectSet;
{
	desiredLeft = left;
	desiredRight = right;
	desiredBottom = bottom;
	desiredTop = top;
	desiredNear = near;
	desiredFar = far;

	perspective = 0;

	if (aspectSet > 0.0)
	 { aspectRatio = aspectSet;
	 }
	else
	 { aspectRatio = (winw*(top-bottom))/(winh*(right-left));
	 }
	adjustScreenTrans (winw, winh);
}

static adjustScreenTrans(w, h)
int w, h;
{
	/* Adjust either the width or the height so that the aspect
	   ratio defined by

	((desiredTop-desiredBottom)*w)/((desiredRight-desiredLeft)*h)

	   remains constant.
	 */

	float right   = desiredRight;
	float left    = desiredLeft;
	float bottom  = desiredBottom;
	float top     = desiredTop;
	float delta;
	float ar;

	ar = ((top-bottom)*w)/((right-left)*h);

	if (ar > aspectRatio)
	 { /* adjust by increasing the width */

	   delta = (ar/aspectRatio - 1.0)*(right-left)/2.0;
	   right += delta;
	   left -= delta;
	 }
	else if (ar < aspectRatio)
	 { /* adjust by increasing the height */

	   delta = (aspectRatio/ar - 1.0)*(top-bottom)/2.0;
	   top += delta;
	   bottom -= delta;
	 }

	if (perspective)
	 { (*fxntab.window) (left, right, bottom, top, 
			     desiredNear, desiredFar);
	 }
	else
	 { (*fxntab.ortho) (left, right, bottom, top, 
			     desiredNear, desiredFar);
	 }
	virtL = left;
	virtB = bottom;
	virtH = top - bottom;
	virtW = right - left;
}

grf4Color (color)
int color;
{
	(*fxntab.color)(color);
}

grf4Clear ()
{
	(*fxntab.clear)();
}

grf4Flush ()
{
	drawObjCnt = 0;

	(*fxntab.flush)();
}

grf4SetView (x,y,z, roll,pitch,yaw)
float x,y,z, roll,pitch,yaw;
{
	TRSF Enew;

	rpyToTrsf (&Enew, roll,pitch,yaw);
	Enew.p.x = x;
	Enew.p.y = y;
	Enew.p.z = z;

	(*fxntab.loadTrans)(&Enew);
	(*fxntab.resetLights)();
	multLiTrsf (&T, &E, &T);
	multTrsf (&T, &Enew, &T);
	(*fxntab.loadTrans)(&T);
	E = Enew;
}

grf4SetViewTrsf (Enew)
TRSF *Enew;
{
	(*fxntab.loadTrans)(Enew);
	(*fxntab.resetLights)();
	multLiTrsf (&T, &E, &T);
	multTrsf (&T, Enew, &T);
	(*fxntab.loadTrans)(&T);
	E = *Enew;
}

grf4PushTrans ()
{
	TRSF_BLK *tb;

	tb = (TRSF_BLK*)malloc(sizeof(TRSF_BLK));
	tb->next = trsfStack;
	trsfStack = tb;
	tb->T = T;
}

grf4PopTrans ()
{
	TRSF_BLK *tb;

	if (tb = trsfStack)
	 { T = tb->T;
	   trsfStack = tb->next;
	   free ((char*)tb);
	   (*fxntab.loadTrans)(&T);
	 }
}

grf4SetTrans (Tnew)
TRSF *Tnew;
{
	T = *Tnew;
	(*fxntab.loadTrans)(&T);
}

TRSF *grf4GetTrans (Tr)
TRSF *Tr;
{
	*Tr = T;
	return (Tr);
}

grf4MultTrans (D)
TRSF *D;
{
	multTrsf (&T, &T, D);
	(*fxntab.loadTrans)(&T);
}

grf4Translate (x,y,z)
float x,y,z;
{
	TRSF D;

	identTrsf (&D);
	D.p.x = x;
	D.p.y = y;
	D.p.z = z;

	multTrsf (&T, &T, &D);
	(*fxntab.loadTrans)(&T);
}

grf4Rotate (roll,pitch,yaw)
float roll,pitch,yaw;
{
	TRSF D;

	rpyToTrsf (&D, roll,pitch,yaw);
	D.p.x = 0.0;
	D.p.y = 0.0;
	D.p.z = 0.0;

	multTrsf (&T, &T, &D);
	(*fxntab.loadTrans)(&T);
}

TRSF *grf4GetView (Er)
TRSF *Er;
{
	*Er = E;
	return (Er);
}
	
grf4Move (x, y, z)
float x, y, z;
{
	(*fxntab.move)(x, y, z);
}

grf4Draw (x, y, z)
float x, y, z;
{
	(*fxntab.draw)(x, y, z);
}

grf4Move2 (x, y)
float x, y;
{
	(*fxntab.move2)(x, y);
}

grf4Draw2 (x, y)
float x, y;
{
	(*fxntab.draw2)(x, y);
}

Grf4Object *grf4GenDisc (npts, radius, depth, material)
int npts;
float radius, depth;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.prism = (*fxntab.genDisc)(npts, radius, depth);
	obj->type = GRF_PRISM;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenCone (npts, radius1, radius2, depth, material)
int npts;
float radius1, radius2, depth;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.prism = (*fxntab.genCone)(npts, radius1, radius2, depth);
	obj->type = GRF_PRISM;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenPrism(npts, bot, top, hidden, depth, material)
int npts;
float *bot, *top;
char *hidden;
float depth;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.prism = (*fxntab.genPrism)(npts, bot, top, hidden, depth);
	obj->type = GRF_PRISM;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenBox(lenx, leny, lenz, material)
float lenx, leny, lenz;
void *material;
{
	float bot[8];
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));

	/* generate the bottom face */

	bot[0] = 0.0;   bot[1] = 0.0;
	bot[2] = lenx;  bot[3] = 0.0;
	bot[4] = lenx;  bot[5] = leny;
	bot[6] = 0.0;   bot[7] = leny;

	obj->ptr.prism = (*fxntab.genPrism)(4, bot, (float*)0, (char*)0, lenz);
	obj->type = GRF_PRISM;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenSphere(npts, radius, material)
int npts;
float radius;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.sphere = (*fxntab.genSphere)(npts, radius);
	obj->type = GRF_SPHERE;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenPolygon(npts, vtxs, material)
int npts;
float *vtxs;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.polygon = (*fxntab.genPolygon)(npts, vtxs);
	obj->type = GRF_POLYGON;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenRegPolygon(npts, size, material)
int npts;
float size;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.polygon = (*fxntab.genRegPolygon)(npts, size);
	obj->type = GRF_POLYGON;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

Grf4Object *grf4GenLineSet (size, lines, material)
int size;
float *lines;
void *material;
{
	Grf4Object *obj;

	if (!fxnInitDone)
	 { doFxnInit(0);
	 }
	if (hostType == 0)
	 { return (0);
	 }
	obj = (Grf4Object*)calloc(1,sizeof(Grf4Object));
	obj->ptr.lineSet = (*fxntab.genLineSet)(size, lines);
	obj->type = GRF_LINESET;
	if (material)
	 { obj->material = material;
	 }
	return (obj);
}

double grf4SetPrismDepth (obj, depth)
Grf4Object *obj;
float depth;
{
	if (obj->type != GRF_PRISM)
	 { return (-1.0);
	 }
	else
	 { return (*fxntab.setDepth)(obj, depth);
	 }	
}

grf4SetObjMaterial (obj, material)
Grf4Object *obj;
void *material;
{
	obj->material = material;
}

grf4DrawObject (obj)
Grf4Object *obj;
{
	int matSaved = 0;

	drawObjCnt++;

	if (obj->material && obj->material != currentMaterial)
	 { matSaved = 1;
	   pushMaterial();
	   (*fxntab.setMaterial)(obj->material);
	 }
	switch (obj->type)
	 { case GRF_PRISM:
	    { (*fxntab.drawPrism)(obj);
	      break;
	    }
	   case GRF_DISC:
	    { (*fxntab.drawPrism)(obj);
	      break;
	    }
	   case GRF_CONE:
	    { (*fxntab.drawPrism)(obj);
	      break;
	    }
	   case GRF_SPHERE:
	    { (*fxntab.drawSphere)(obj);
	      break;
	    }
	   case GRF_POLYGON:
	    { (*fxntab.drawPolygon)(obj);
	      break;
	    }
	   case GRF_LINESET:
	    { (*fxntab.drawLineSet)(obj);
	      break;
	    }
	 }
	if (matSaved)
	 { popMaterial();
	 }
}

/* -------------------- button module ------------------ */

/* The button panel is laid out, horizontally, as follows:
	
	|<------ winw ------>||<-----panelWidth------>|
	XXXXXXXXXXXXXXXXXXXXXXO.....BBBBBBBBBBBBBB.....
			       |<->||<-- b->w -->|
		    buttonSpacing|

   The vertical layout looks like this:
	._
	.|
	.| buttonSpacing
	.- 
	B- 
	B| b->h
	B| 
	B- b->y
*/

static BUTTON_DESC *getButton(num)
int num;
{
	BUTTON_DESC *b;

	for (b=buttonList; b; b=b->next)
	 { if (b->num == num)
	    { return (b);
	    }
	 }
	return(0);
}

grf4AddButton(name, flags, sizeK, fxn, arg)
char *name;
int flags;
int sizeK;
int (*fxn)();
long arg;
{
	BUTTON_DESC *b;

	if (initDone)
	 { fprintf (stderr, 
		    "grf4AddButton(): must be called before grf4Init()\n");
	   exit(-1);
	 }
	if (sizeK < 1)
	 { fprintf (stderr, "grf4AddButton(): bad value for sizeK\n");
	   exit (-1);
	 }

	b = ALLOC(BUTTON_DESC);
	b->next = buttonList;
	buttonList = b;

	b->name = (char*)malloc(strlen(name)+1);
	strcpy (b->name, name);
	b->flags = flags;
	b->fxn = fxn;
	b->arg = arg;
	b->num = ++numButtons;
	b->sizeK = sizeK;
	return (b->num);
}

void grf4SetButtonFlags (num, flags)
int num, flags;
{
	BUTTON_DESC *b;

	if (b = getButton (num))
	 { b->flags = flags;
	   if (initDone)	/* then grf4Init() has been called */
	    { (*fxntab.drawButton)(b);
	    }
	 }
}

grf4GetButtonFlags (num)
int num;
{
	BUTTON_DESC *b;

	if (b = getButton (num))
	 { return (b->flags);
	 }
	return (-1);
}

void grf4SetButtonName (num, name)
int num;
char *name;
{
	BUTTON_DESC *b;

	if (b = getButton (num))
	 { if (b->name)
	    { free (b->name);
	    }
	   b->name = (char*)malloc(strlen(name)+1);
	   strcpy (b->name, name);
	   if (initDone)	/* then grf4Init() has been called */
	    { (*fxntab.getButtonExtent)(b);
	      (*fxntab.drawButton)(b);
	    }
	 }
}

void buttonInit(w, h)
int *w, *h;
{
	/* Called by the local init function whenever it is safe
	   to call getButtonExtent(). It returns the desired height
	   and width of the button panel. */

	BUTTON_DESC *b, *b_next, *bRowBegin;
	int rowSizeK, n, kw, kwMax, row, maxH, maxW;

	/* If there are no buttons, return a panelWidth of 0 */

	if (buttonList == 0)
	 { *w = panelWidth = 0;
	   *h = panelHeight = 0;
	   return;
	 }

	/* Reverse the direction of the button list */

	b = buttonList;
	buttonList = 0;
	for ( ; b; b=b_next)
	 { b_next = b->next;
	   b->next = buttonList;
	   buttonList = b;
	 }

	buttonSpacing = 4; /* MAX(4,0.2*maxH) */

	/* Compute the  minimum height and width for each button,
	   and row number, for each button. Compute a few width
	   parameters relevant for each row as well. */

	row = -1;
	maxW = maxH = 0;
	for (b=buttonList; b; b=b->next)
	 { if (!(b->flags & BF_SAMEROW) || row == -1)
	    { row++;
	      bRowBegin = b;
	      rowSizeK = kwMax = n = 0;
	    }
	   (*fxntab.getButtonExtent)(b);
	   if (b->minh > maxH)
	    { maxH = b->minh;
	    }
	   b->row = row;
	   rowSizeK += b->sizeK;
	   n++;
	   if ((kw = ceil(b->minw/(double)b->sizeK)) > kwMax)
	    { kwMax = kw;
	    }
	   if (!b->next || !(b->next->flags & BF_SAMEROW))
	    { bRowBegin->rowSizeK = rowSizeK;
	      bRowBegin->rowMinW = rowSizeK*kwMax + (n-1)*buttonSpacing;
	      bRowBegin->rowN = n;
	      if (bRowBegin->rowMinW > maxW)
	       { maxW = bRowBegin->rowMinW;
	       }
	    }
	 }

	buttonWidth = maxW;
	buttonHeight = maxH;
	/* the extra 1 on panelWidth is for the dividing line */
	panelWidth = maxW + 2*buttonSpacing + 1;
	panelHeight = (row+2)*buttonSpacing + (row+1)*buttonHeight;

	*w = panelWidth;
	*h = panelHeight;
}

void buttonCompile(w, h)
int w;
int h;
{
	/* Called from the init routine once we know the overall width 
	   of the window. Sets the sizes and offsets so that drawButton() 
	   will work.

	   'w' is the entire width of the window, including the 
	    button panel width. */

	BUTTON_DESC *b;
	int baseX, baseY;
	int row;
	int kernW, dkw, dsw, ns, deltaW, n;
	
	baseY = h;
	row = -1;
	for (b=buttonList; b; b=b->next)
	 { if (b->row != row)
	    { row = b->row;
	      baseY -= (buttonHeight + buttonSpacing);
	      baseX = w-(buttonSpacing+buttonWidth);
	      kernW = (b->rowMinW - (b->rowN-1)*buttonSpacing)/(b->rowSizeK);
	      deltaW = buttonWidth - b->rowMinW;
	      dkw = deltaW/b->rowSizeK;
	      n = 0;
	      if (b->rowN > 1)
	       { deltaW = deltaW%b->rowSizeK;
		 dsw = deltaW/(b->rowN-1);
		 ns = deltaW%(b->rowN-1);
	       }
	      else
	       { dsw = ns = 0;
	       }
	    }
	   b->w = b->sizeK*(kernW + dkw);
	   b->h = buttonHeight;
	   b->x = baseX;
	   b->y = baseY;
	   baseX += (buttonSpacing + dsw + b->w);
	   if (n++ < ns)
	    { baseX += 1;
	    }
	 }
}

void buttonResize(dw,dh)
int dw,dh;
{
	/* Called whenever we resize the window, to update the 
	   button offsets. */

	BUTTON_DESC *b;

	for (b=buttonList; b; b=b->next)
	 { b->x += dw;
	   b->y += dh;
	 }
}

drawAllButtons()
{
	BUTTON_DESC *b;

	for (b=buttonList; b; b=b->next)
	 { (*fxntab.drawButton)(b);
	 }
}

BUTTON_DESC *overButton(x, y)
int x, y;
{
	register BUTTON_DESC *b;

	if (x <= winw+buttonSpacing ||
	    x >= winw+panelWidth-buttonSpacing)
	 { return (0);
	 }
	for (b=buttonList; b; b=b->next)
	 { if (!(b->flags & BF_INACTIVE) &&
		(x >= b->x && x < b->x+b->w) && (y >= b->y && y < b->y+b->h))
	    { return (b);
	    }
	 }
	return (0);
}

onResizeEvent(w, h)
int w, h;
{
	int dw, dh;

	dw = w-panelWidth-winw;
	dh = h-winh;
	winw = w-panelWidth;
	winh = h;

	if (hostType != GL_H)
	 { gdrResize (winw, winh);
	 }
	adjustScreenTrans (winw, winh);
	buttonResize (dw, dh);
}

onRedrawEvent()
{
	if (*redrawFxn)
	 { (*redrawFxn)(redrawArg);
	 }
	drawAllButtons();
}

static int dragX0;
static int dragY0;
static int dragging;
static BUTTON_DESC *buttonPressed;
static BUTTON_DESC *buttonInFocus;
static int mouseButtonDown;

#define BUTTON1	1
#define BUTTON2	2
#define BUTTON3	3

onButtonEvent(num, pressed, x, y)
int num;
int pressed;
int x, y;
{
	BUTTON_DESC *bd;

	if (pressed)
	 { if (mouseButtonDown)
	    { return;
	    }
	   if (x < winw)
	    { if (num == BUTTON1 || num == BUTTON2)
	       { dragX0 = x;
		 dragY0 = y;
		 dragging = 1;
	       }
	    }
	   else
	    { if (num == BUTTON1)
	       { bd = overButton(x, y);
		 if (bd && bd->fxn)
		  { (*bd->fxn)(bd->num, BF_PRESSED, bd->arg);
		  }
		 buttonPressed = bd;
	       }
	    }
	   mouseButtonDown = num;
	 }
	else
	 { if (mouseButtonDown != num)
	    { return;
	    }
	   if (dragging > 0)
	    { dragging = 0;
	    }
	   else if (dragging == 1)
	    { /* onSelect (dragX0, dragY0); */
	      dragging = 0;
	    }
	   else if (bd = buttonPressed)
	    { if (bd->fxn)
	       { (*bd->fxn)(bd->num, BF_RELEASED, bd->arg);
	       }
	      buttonPressed = 0;
	    }
	   mouseButtonDown = 0;
	 }
}

onMotionEvent (x, y)
int x, y;
{
	BUTTON_DESC *bd;

	if (dragging && viewCtrlMode)
	 { onDrag (mouseButtonDown, dragX0, dragY0, x, y);
	   dragX0 = x;
	   dragY0 = y;
	   dragging++;
	 }
	else if (x > winw)
	 { bd = overButton (x, y);
	   if (bd != buttonInFocus)
	    { if (buttonInFocus && buttonInFocus->fxn)
	       { (*buttonInFocus->fxn)(buttonInFocus->num, BF_OUTFOCUS,
				       buttonInFocus->arg);
	       }
	      if (bd && bd->fxn)
	       { (*bd->fxn)(bd->num, BF_INFOCUS, bd->arg);
	       }
	      buttonInFocus = bd;	     
	    }
	 }
}

onWinExitEvent ()
{
	if (dragging)
	 { dragging = 0;
	 }
}

onButton(n, code, mode)
int n, code;
long mode;
{
	if (code == BF_PRESSED)
	 { if (viewCtrlMode != mode)
	    { switch (viewCtrlMode)
	       { case YAW_PITCH_CTRL:
		  { clearButton (yawCtrlButton);
		    clearButton (pitchCtrlButton);
		    break;
		  }
		 case X_Y_CTRL:
		  { clearButton (xCtrlButton);
		    clearButton (yCtrlButton);
		    break;
		  }
		 case PANY_PANX_CTRL:
		  { clearButton (panXCtrlButton);
		    clearButton (panYCtrlButton);
		    break;
		  }
		 case ZOOM_CTRL:
		  { clearButton (zoomCtrlButton);
		    break;
		  }
		 case TILT_CTRL:
		  { clearButton (tiltCtrlButton);
		    break;
		  }
	       }
	      switch (mode)
	       { case YAW_PITCH_CTRL:
		  { lightButton (yawCtrlButton);
		    lightButton (pitchCtrlButton);
		    break;
		  }
		 case X_Y_CTRL:
		  { lightButton (xCtrlButton);
		    lightButton (yCtrlButton);
		    break;
		  }
		 case PANY_PANX_CTRL:
		  { lightButton (panXCtrlButton);
		    lightButton (panYCtrlButton);
		    break;
		  }
		 case ZOOM_CTRL:
		  { lightButton (zoomCtrlButton);
		    break;
		  }
		 case TILT_CTRL:
		  { lightButton (tiltCtrlButton);
		    break;
		  }
	       }
	      viewCtrlMode = mode;
	      xMotionEnable = yMotionEnable = 1;
	    }
	   else
	    { int butx, buty;

	      switch (viewCtrlMode)
	       { case YAW_PITCH_CTRL:
		  { butx = yawCtrlButton;
		    buty = pitchCtrlButton;
		    break;
		  }
		 case X_Y_CTRL:
		  { butx = xCtrlButton;
		    buty = yCtrlButton;
		    break;
		  }
		 case PANY_PANX_CTRL:
		  { butx = panXCtrlButton;
		    buty = panYCtrlButton;
		    break;
		  }
		 default:
		  { return;
		  }
	       }
	      if (n == butx)
	       { if (xMotionEnable)
		  { clearButton(butx);
		    xMotionEnable = 0;
		  }
		 else
		  { lightButton(butx);
		    xMotionEnable = 1;
		  }
	       }
	      else if (n == buty)
	       { if (yMotionEnable)
		  { clearButton(buty);
		    yMotionEnable = 0;
		  }
		 else
		  { lightButton(buty);
		    yMotionEnable = 1;
		  }
	       }
	      else
	       { printf ("Error: onButton(): bogus button no.\n");
		 exit (-1);
	       }
	    }
	 }
}

clearButton(num)
int num;
{
	int flags;
	
	flags = grf4GetButtonFlags (num);
	grf4SetButtonFlags (num, flags & ~(BF_ILLUMINATED));
}

lightButton(num)
int num;
{
	int flags;
	
	flags = grf4GetButtonFlags (num);
	grf4SetButtonFlags (num, flags | BF_ILLUMINATED);
}

onDrag(button, x1, y1, x2, y2)
int button;
int x1, y1, x2, y2;
{
	TRSF D, Enew;
	float roll, pitch, yaw;
	float px, py, pz;
	int deltaX, deltaY;
	float scale;

	deltaX = x2-x1;
	deltaY = y2-y1;
	
	if (viewCtrlMode == NO_VIEW_CTRL)
	 { return;
	 }

	if (button == BUTTON1)
	 { scale = 1.0;
	 }
	else
	 { scale = 0.1;
	 }

#define DEADBAND	5

	switch (viewCtrlMode)
	 { case YAW_PITCH_CTRL:
	    { 
	      yaw = pitch = 0.0;
	      deadBandClip (&deltaX, &deltaY, DEADBAND);
	      if (yMotionEnable)
	       { yaw = -scale*90.0*deltaY/winh;
	       }
	      if (xMotionEnable)
	       { pitch = scale*90.0*deltaX/winw;
	       }
	      zeroVect (&D.p);
	      rpyToTrsf (&D, 0.0, pitch, yaw);
	      Enew = E;
	      zeroVect (&Enew.p);
	      multTrsf (&Enew, &D, &Enew);
	      Enew.p = E.p;
	      break;
	    }
	   case PANY_PANX_CTRL:
	    { 
	      yaw = pitch = 0.0;
	      deadBandClip (&deltaX, &deltaY, DEADBAND);
	      if (yMotionEnable)
	       { yaw = scale*90.0*deltaY/winh;
	       }
	      if (xMotionEnable)
	       { pitch = -scale*90.0*deltaX/winw;
	       }
	      zeroVect (&D.p);
	      rpyToTrsf (&D, 0.0, pitch, yaw);
	      Enew = E;
	      multTrsf (&Enew, &D, &Enew);
	      break;
	    }
	   case TILT_CTRL:
	    { 
#if 0
#define DZ	10	/* dead zone */
	      x1 -= winw/2;
	      y1 -= winh/2;
	      x2 -= winw/2;
	      y2 -= winh/2;

	      if (x1*x1 + y1*y1 >= DZ*DZ && x2*x2 + y2*y2 >= DZ*DZ)
	       { roll = scale*RTOD*(atan2((double)y2,(double)x2) -
				    atan2((double)y1,(double)x1));
	       }
#endif
	      roll = scale*90.0*deltaX/winw;
	      zeroVect (&D.p);
	      rpyToTrsf (&D, roll, 0.0, 0.0);
	      Enew = E;
	      multTrsf (&Enew, &D, &Enew);
	      break;
	    }
	   case X_Y_CTRL:
	    { if (perspective)
	       { scale *= (desiredNear+desiredFar)/(2*desiredNear);
	       }
	      else
	       { scale = 1.0;
	       }
	      Enew = E;
	      if (xMotionEnable)
	       { Enew.p.x += scale*virtW*deltaX/winw;
	       }
	      if (yMotionEnable)
	       { Enew.p.y += scale*virtH*deltaY/winh;
	       }
	      break;
	    }
	   case ZOOM_CTRL:
	    { float r1, r2;

	      scale *= (desiredFar-desiredNear)/(2*virtW);
	      Enew = E;
	      x1 -= winw/2;
	      x2 -= winw/2;
	      y1 -= winh/2;
	      y2 -= winh/2;
	      r1 = sqrt((double)(x1*x1+y1*y1));
	      r2 = sqrt((double)(x2*x2+y2*y2));
	      Enew.p.z += scale*(r2-r1)*virtW/winw;
	    }
	 }
	grf4SetViewTrsf (&Enew);
	if (redrawFxn)
	 { (*redrawFxn)(redrawArg);
         }
}

#if 0

onSelect(scrX, scrY)
int scrX, scrY;
{
	OBJ_NODE *n;
	float x, y, depth;

	x = virtW*(scrX-0.5)/(winw-1) + virtL;
	y = virtH*(scrY-0.5)/(winh-1) + virtB;

	for (n=objList; n; n=n->next)
	 { switch (n->type)
	    { case GRF_PRISM:
	       { if ((*fxntab.overPrism)(&depth, (PRISM*)n->obj, x, y))
		  { printf ("over cylinder, depth = %g\n", depth);
		  }
		 break;
	       }
	      case GRF_SPHERE:
	       { if ((*fxntab.overSphere)(&depth, (SPHERE*)n->obj, x, y))
		  { printf ("over sphere, depth = %g\n", depth);
		  }
		 break;
	       }
	    }
	 }
}

#endif

void *grf4DefMaterialD(matd)
MATERIAL_DEF *matd;
{
	return (*fxntab.defMaterialD)(matd);
}

void *grf4DefMaterial(diffuse, ambient, emission, specular, shine, alpha)
unsigned long diffuse, ambient, emission, specular;
float shine, alpha;
{
	return (*fxntab.defMaterial)(diffuse, ambient, emission,
				     specular, shine, alpha);
}

grf4SetMaterial(m)
void *m;
{
	currentMaterial = m;
	(*fxntab.setMaterial)(m);
}

grf4PushMaterial()
{
	pushMaterial();
}

grf4PopMaterial()
{
	popMaterial();
}

void *grf4DefLight(color, ambient, x,y,z,w)
unsigned long color, ambient;
float x,y,z,w;
{
	return (*fxntab.defLight)(color, ambient, x,y,z,w);
}

void grf4SetLight(n,l)
int n;
void *l;
{
	(*fxntab.loadTrans)(&E);
	(*fxntab.setLight)(n,l);
	(*fxntab.loadTrans)(&T);
}


deadBandClip (xp, yp, band)
int *xp;
int *yp;
int band;
{
#if 0
	if (*xp > 0)
	 { *xp = MAX (0, *xp-band);
	 }
	else
	 { *xp = MIN (0, *xp+band);
	 }
	if (*yp > 0)
	 { *yp = MAX (0, *yp-band);
	 }
	else
	 { *yp = MIN (0, *yp+band);
	 }
#endif
}
