/*****************************************************************************
*   An SGI 4D driver using Open GL.					     *
******************************************************************************
* (C) Gershon Elber, Technion, Israel Institute of Technology                *
******************************************************************************
* Written by:  Gershon Elber			     Ver 0.1, October 1994.  *
*****************************************************************************/

#ifdef __hpux
typedef char *caddr_t;	       /* Awful kludge. Let me know of a better way. */
#endif /* __hpux */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xresource.h>

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

#include <math.h>
#include "irit_sm.h"
#include "genmat.h"
#include "iritprsr.h"
#include "allocate.h"
#include "attribut.h"
#include "iritgrap.h"
#include "x11drvs.h"

typedef enum {
    GLXcolorIndexSingleBuffer,
    GLXcolorIndexDoubleBuffer,
    GLXrgbSingleBuffer,
    GLXrgbDoubleBuffer
} GLXWindowType;

static Colormap CMap;

static short Colors[IG_MAX_COLOR + 1][3] =
{
    { 0,   0,   0   },  /* 0. BLACK */
    { 0,   0,   170 },  /* 1. BLUE */
    { 0,   170, 0   },  /* 2. GREEN */
    { 0,   170, 170 },  /* 3. CYAN */
    { 170, 0,   0   },  /* 4. RED */
    { 170, 0,   170 },  /* 5. MAGENTA */
    { 170, 170, 0   },  /* 6. BROWN */
    { 170, 170, 170 },  /* 7. LIGHTGREY */
    { 85,  85,  85  },  /* 8. DARKGRAY */
    { 85,  85,  255 },  /* 9. LIGHTBLUE */
    { 85,  255, 85  },  /* 10. LIGHTGREEN */
    { 85,  255, 255 },  /* 11. LIGHTCYAN */
    { 255, 85,  85  },  /* 12. LIGHTRED */
    { 255, 85,  255 },  /* 13. LIGHTMAGENTA */
    { 255, 255, 85  },  /* 14. YELLOW */
    { 255, 255, 255 }   /* 15. WHITE */
};

static void SetColorRGB(int Color[3]);
static void SetColorIndex(int c);
static void SetEntry(GLXconfig* ptr, int b, int m, int a);
static Window GLXCreateWindow(Display *XDisplay,
			      Window Parent,
			      int x,
			      int y,
			      int w,
			      int h,
			      int BorderWidth,
			      GLXWindowType Type);

/*****************************************************************************
* DESCRIPTION:                                                               M
* Draw a single Point/Vector object using current modes and transformations. M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:     A point/vector object to draw.                                 M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGDrawPtVec                                                              M
*****************************************************************************/
void IGDrawPtVec(IPObjectStruct *PObj)
{
    int i;
    PointType Ends[6], Zero;
    RealType
	*Pt = PObj -> U.Pt;

    for (i = 0; i < 6; i++)
	PT_COPY(Ends[i], Pt);

    Ends[0][0] -= IGGlblPointWidth;
    Ends[1][0] += IGGlblPointWidth;
    Ends[2][1] -= IGGlblPointWidth;
    Ends[3][1] += IGGlblPointWidth;
    Ends[4][2] -= IGGlblPointWidth;
    Ends[5][2] += IGGlblPointWidth;

    for (i = 0; i < 6; i += 2) {
	glBegin(GL_LINES);
	glVertex3dv(Ends[i]);
	glVertex3dv(Ends[i+1]);
	glEnd();
    }

    if (IP_IS_VEC_OBJ(PObj)) {
	glBegin(GL_LINES);
	glVertex3dv(Pt);
	Zero[0] = Zero[1] = Zero[2] = 0.0;
	glVertex3dv(Zero);
	glEnd();
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Draw a single Poly object using current modes and transformations.	     M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:     A poly object to draw.                                         M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGDrawPoly                                                               M
*****************************************************************************/
void IGDrawPoly(IPObjectStruct *PObj)
{
    IPVertexStruct *V;
    IPPolygonStruct
	*Pl = PObj -> U.Pl;

    if (IP_IS_POLYLINE_OBJ(PObj)) {
	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    glBegin(GL_LINE_STRIP);
	    for (V = Pl -> PVertex; V != NULL; V = V -> Pnext)
	        glVertex3dv(V -> Coord);
	    glEnd();
	}
    }
    else if (IP_IS_POINTLIST_OBJ(PObj)) {
	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		int i;
		PointType Ends[6];
		RealType
		    *Pt = V -> Coord;

		for (i = 0; i < 6; i++)
		    PT_COPY(Ends[i], Pt);

		Ends[0][0] -= IGGlblPointWidth;
		Ends[1][0] += IGGlblPointWidth;
		Ends[2][1] -= IGGlblPointWidth;
		Ends[3][1] += IGGlblPointWidth;
		Ends[4][2] -= IGGlblPointWidth;
		Ends[5][2] += IGGlblPointWidth;

		for (i = 0; i < 6; i += 2) {
		    glBegin(GL_LINES);
		    glVertex3dv(Ends[i]);
		    glVertex3dv(Ends[i+1]);
		    glEnd();
		}
	    }
	}
    }
    else if (IP_IS_POLYGON_OBJ(PObj)) {
	int i, j,
	    NumOfVertices;
	PointType PNormal, VNormal;

	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    if (IGGlblDrawPNormal) {
		NumOfVertices = 0;
		PNormal[0] = PNormal[1] = PNormal[2] = 0.0;
	    }

	    if (IGGlblDrawSolid) {
		glBegin(GL_POLYGON);
		for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		    glNormal3d(-V -> Normal[0],
			       -V -> Normal[1],
			       -V -> Normal[2]);
		    glVertex3dv(V -> Coord);

		    if (IGGlblDrawPNormal) {
			for (j = 0; j < 3; j++)
			    PNormal[j] += V -> Coord[j];
			NumOfVertices++;
		    }
		}
		glEnd();
	    }
	    else {
		glBegin(GL_LINE_STRIP);
		for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		    glVertex3dv(V -> Coord);
		    if (IP_IS_INTERNAL_VRTX(V) && !IGGlblDrawInternal) {
			glEnd();
			glBegin(GL_LINE_STRIP);
		    }

		    if (IGGlblDrawPNormal) {
			for (j = 0; j < 3; j++)
			    PNormal[j] += V -> Coord[j];
			NumOfVertices++;
		    }
		}
		glVertex3dv(Pl -> PVertex -> Coord);
		glEnd();
	    }

	    if (IGGlblDrawPNormal && IP_HAS_PLANE_POLY(Pl)) {
		glBegin(GL_LINES);
		for (i = 0; i < 3; i++)
		    PNormal[i] /= NumOfVertices;
		glVertex3dv(PNormal);
		for (i = 0; i < 3; i++)
		    PNormal[i] += Pl -> Plane[i] * IGGlblNormalLen;
		glVertex3dv(PNormal);
		glEnd();
	    }

	    if (IGGlblDrawVNormal) {
		for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		    if (IP_HAS_NORMAL_VRTX(V)) {
			for (j = 0; j < 3; j++)
			    VNormal[j] = V -> Coord[j] +
				         V -> Normal[j] * IGGlblNormalLen;
			glBegin(GL_LINES);
			glVertex3dv(V ->Coord);
			glVertex3dv(VNormal);
			glEnd();
		    }
		}
	    }
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Sets the color of an object according to its color/rgb attributes.	     M
*   If object has an RGB attribute it will be used. Otherwise, if the object M
* has a COLOR attribute it will use. Otherwise, WHITE will be used.	     M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:      To set the drawing color to its color.                        M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGSetColorObj                                                            M
*****************************************************************************/
void IGSetColorObj(IPObjectStruct *PObj)
{
    int c, Color[3];

    if (AttrGetObjectRGBColor(PObj, &Color[0], &Color[1], &Color[2])) {
	SetColorRGB(Color);
    }
    else if ((c = AttrGetObjectColor(PObj)) != IP_ATTR_NO_COLOR) {
	SetColorIndex(c);
    }
    else {
	/* Use white as default color: */
	SetColorIndex(IG_IRIT_WHITE);
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Sets the line width to draw the given object, in pixels.		     M
*                                                                            *
* PARAMETERS:                                                                M
*   Width:    In pixels of lines to draw with.                               M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGSetWidthObj                                                            M
*****************************************************************************/
void IGSetWidthObj(int Width)
{
    glLineWidth(Width);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets the color according to the given color index.		    	     *
*                                                                            *
* PARAMETERS:                                                                *
*   color:     Index of color to use. Must be between 0 and IG_MAX_COLOR.    *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SetColorIndex(int color)
{
    int Color[3];

    if (color < 0 || color > IG_MAX_COLOR)
        color = IG_IRIT_WHITE;

    Color[0] = Colors[color][0];
    Color[1] = Colors[color][1];
    Color[2] = Colors[color][2];

    SetColorRGB(Color);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets the color according to the given RGB values.			     *
*                                                                            *
* PARAMETERS:                                                                *
*   Color:      An RGB vector of integer values between 0 and 255.           *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SetColorRGB(int Color[3])
{
    int i;

    if (IGGlblDrawSolid) {
	GLfloat MatAmbient[4], MatDiffuse[4], MatSpecular[4];
	static GLfloat
	    MatShininess[] = { 15.0 };

	for (i = 0; i < 3; i++) {
	    MatAmbient[i] = 0.2 * Color[0] / 255.0;
	    MatDiffuse[i] = 0.4 * Color[i] / 255.0;
	    MatSpecular[i] = Color[i] / 255.0;
	}
	MatAmbient[3] = MatDiffuse[3] = MatSpecular[3] = 1.0;

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, MatAmbient);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, MatDiffuse);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, MatSpecular);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, MatShininess);
    }
    else
	glColor3f(Color[0] / 255.0, Color[1] / 255.0, Color[2] / 255.0);

    if (IGGlblDepthCue) {
	GLfloat fogColor[4];

	for (i = 0; i < 3; i++)
	    fogColor[i] = (float) IGGlblBackGroundColor[i] / 255.0;
	fogColor[3] = (float) 1.0;

	glEnable(GL_FOG);
	glFogi(GL_FOG_MODE, GL_LINEAR);
	glHint(GL_FOG_HINT, GL_NICEST);
	glFogf(GL_FOG_START, (float) IGGlblZMinClip);
	glFogf(GL_FOG_END, (float) IGGlblZMaxClip);
	glFogfv(GL_FOG_COLOR, fogColor);
    }
    else
	glDisable(GL_FOG);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* This routine (along with SetEntry) was copied from the GLX examples of SGI M
* under 4DGifts. Why is it so difficult to open a window!?		     M
*                                                                            *
* PARAMETERS:                                                                M
*   XDisplay:      Display                                                   M
*   Parent:        Parent window.                                            M
*   x, y:          Location.                                                 M
*   w, h:          Size.                                                     M
*   BorderWidth:   Border width.                                             M
*   Type:          Type of window requested.                                 M
*                                                                            *
* RETURN VALUE:                                                              M
*   Window:        Created window, NULL if failed.                           M
*                                                                            *
* KEYWORDS:                                                                  M
*   GLXCreateWindow                                                          M
*****************************************************************************/
static void SetEntry(GLXconfig* ptr, int b, int m, int a)
{
    ptr->buffer = b;
    ptr->mode = m;
    ptr->arg = a;
}

static Window GLXCreateWindow(Display *XDisplay,
			      Window Parent,
			      int x,
			      int y,
			      int w,
			      int h,
			      int BorderWidth,
			      GLXWindowType Type)
{
    GLXconfig params[50];
    GLXconfig *next;
    GLXconfig *retconfig;
    XVisualInfo *vis;
    XVisualInfo template;
    XColor white;
    XSetWindowAttributes cwa;
    XWindowAttributes pwa;
    int i, nret;
    Window win;
    static char
        *TypeToName[] = {
	    "color index single buffer",
	    "color index double buffer",
	    "rgb single buffer",
	    "rgb double buffer",
	};

    CMap = DefaultColormap(XDisplay, DefaultScreen(XDisplay));

    /*
     * This builds an array in "params" that describes for GLXgetconfig(3G)
     * the type of GL drawing that will be done.
     */
    next = params;
    switch (Type) {
	case GLXcolorIndexSingleBuffer:
	    SetEntry(next++, GLX_NORMAL, GLX_RGB, FALSE);
	    SetEntry(next++, GLX_NORMAL, GLX_DOUBLE, FALSE);
	    break;
	case GLXcolorIndexDoubleBuffer:
	    SetEntry(next++, GLX_NORMAL, GLX_RGB, FALSE);
	    SetEntry(next++, GLX_NORMAL, GLX_DOUBLE, TRUE);
	    break;
	case GLXrgbSingleBuffer:
	    SetEntry(next++, GLX_NORMAL, GLX_RGB, TRUE);
	    SetEntry(next++, GLX_NORMAL, GLX_DOUBLE, FALSE);
	    break;
	case GLXrgbDoubleBuffer:
	    SetEntry(next++, GLX_NORMAL, GLX_RGB, TRUE);
	    SetEntry(next++, GLX_NORMAL, GLX_DOUBLE, TRUE);
	    break;
    }
    SetEntry(next, 0, 0, 0); /* The input to GLXgetconfig is null terminated */

    /*
     * Get configuration data for a window based on above parameters
     * First we have to find out which screen the parent window is on,
     * then we can call GXLgetconfig()
     */
    XGetWindowAttributes(XDisplay, Parent, &pwa);
    retconfig = GLXgetconfig(XDisplay, XScreenNumberOfScreen(pwa.screen),
			     params);
    if (retconfig == 0) {
	printf("Sorry, can't support %s type of windows\n", TypeToName[Type]);
	exit(-1);
    }
    /*
     * The GL sets its own X error handlers, which aren't as informative
     * when errors happen.  Calling XSetErrorHandler(0) here will
     * reset back to the default Xlib version.
     */
    XSetErrorHandler(0);

    /*
     * Scan through config info, pulling info needed to create a window
     * that supports the rendering mode.
     */
    for (next = retconfig; next->buffer; next++) {
	unsigned long buffer = next->buffer;
	unsigned long mode = next->mode;
	unsigned long value = next->arg;
	switch (mode) {
	  case GLX_COLORMAP:
	    if (buffer == GLX_NORMAL) {
		CMap = value;
	    }
	    break;
	  case GLX_VISUAL:
	    if (buffer == GLX_NORMAL) {
		template.visualid = value;
		template.screen = DefaultScreen(XDisplay);
		vis = XGetVisualInfo(XDisplay, VisualScreenMask | VisualIDMask,
				     &template, &nret);
	    }
	    break;
	}
    }

    /*
     * Create the window
     */
    cwa.colormap = CMap;
    cwa.border_pixel = 0;  /* Even if we don't use it, it must be something */
    win = XCreateWindow(XDisplay, Parent, x, y, w, h,
			BorderWidth, vis->depth, InputOutput, vis->visual,
			CWColormap|CWBorderPixel, &cwa);

    /*
     * Rescan configuration info and find window slot that getconfig
     * provided.  Fill it in with the window we just created.
     */
    for (next = retconfig; next->buffer; next++) {
	if ((next->buffer == GLX_NORMAL) && (next->mode == GLX_WINDOW)) {
	    next->arg = win;
	    break;
	}
    }

    /*
     * Now "retconfig" contains all the information the GL needs to
     * configure the window and its own internal state.
     */
    i = GLXlink(XDisplay, retconfig);
    if (i < 0) {
	printf("GLXlink returned %d\n", i);
	exit(-1);
    }

    return win;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Sets up a view window.						     M
*                                                                            *
* PARAMETERS:                                                                M
*   argc, argv:   Command line,                                              M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   SetViewWindow                                                            M
*****************************************************************************/
void SetViewWindow(int argc, char **argv)
{
    static int
	UpdateLightPos = FALSE;
    static GLfloat
	Light0Position[4] = { 1.0, 2.0, 10.0, 0.0 },
	Light1Position[4] = { -5.0, -1.0, -10.0, 0.0 },
	LightAmbient[] = { 1.0, 1.0, 1.0, 1.0 },
	LightDiffuse[] = { 1.0, 1.0, 1.0, 1.0 },
	LightSpecular[] = { 1.0, 1.0, 1.0, 1.0 },
	LModelAmbient[] = { 0.2, 0.2, 0.2, 1.0 };
    Cursor XCursor;

    ViewWndw = GLXCreateWindow(XDisplay, XRoot,
			       ViewPosX, ViewPosY,
			       ViewWidth, ViewHeight,
			       0, GLXrgbDoubleBuffer);

    /* Set our own cursor: */
    XCursor = XCreateFontCursor(XDisplay, XC_iron_cross);
    XDefineCursor(XDisplay, ViewWndw, XCursor);
    if (ViewCursorColor != NULL)
	XRecolorCursor(XDisplay, XCursor, ViewCursorColor, &BlackColor);

    XSelectInput(XDisplay, ViewWndw, ExposureMask | ButtonPressMask);
    
    XMapWindow(XDisplay, ViewWndw);

    if (!UpdateLightPos) {
	int i;
	
	for (i = 0; i < 4; i++)
	    Light0Position[i] = IGGlblLightSrcPos[i];
	UpdateLightPos = TRUE;
    }

    glXMakeCurrent(XDisplay, None, NULL);

    glLightfv(GL_LIGHT0, GL_POSITION, Light0Position);
    glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpecular);
    glLightfv(GL_LIGHT1, GL_POSITION, Light1Position);
    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
    glLightfv(GL_LIGHT1, GL_SPECULAR, LightSpecular);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, LModelAmbient);

    glDepthFunc(GL_GREATER);
    glClearDepth(0.0);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Redraw the view window.                                                  M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*****************************************************************************/
void IGRedrawViewWindow(void)
{
    IPObjectStruct *PObj;
    GLdouble CrntView[16];
    int i, j, k;

    glDrawBuffer(IGGlblDoDoubleBuffer ? GL_BACK : GL_FRONT);

    /* Clear viewing area. */
    glClear(GL_COLOR_BUFFER_BIT |
	    (IGGlblDrawSolid ? GL_DEPTH_BUFFER_BIT : 0));

    /* activate zbuffer only if we are in solid drawing mode. */
    if (IGGlblDrawSolid) {
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	glClear(GL_DEPTH_BUFFER_BIT);
    }
    else {
	glDisable(GL_LIGHTING);
	glDisable(GL_LIGHT0);
	glDisable(GL_LIGHT1);
	glDisable(GL_DEPTH_TEST);
    }

    if (IGGlblViewMode == IG_VIEW_PERSPECTIVE) {
	for (i = 0; i < 4; i++)
	    for (j = 0; j < 4; j++) {
		CrntView[i * 4 + j] = 0;
		for (k = 0; k < 4; k++)
		    CrntView[i * 4 + j] += IritPrsrViewMat[i][k] *
			                   IritPrsrPrspMat[k][j];
	    }
    }
    else {
	for (i = 0; i < 4; i++)
	    for (j = 0; j < 4; j++)
		CrntView[i * 4 + j] = IritPrsrViewMat[i][j];
    }

    glLoadMatrixd(CrntView);
    glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

    for (PObj = IGGlblDisplayList; PObj != NULL; PObj = PObj -> Pnext)
	IGDrawObject(PObj);
    glFlush();

    if (IGGlblDoDoubleBuffer)
	glXSwapBuffers(XDisplay, ViewWndw);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* To handle internal events. Should not block.                               M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*****************************************************************************/
void IGHandleInternalEvents(void)
{
}
