/**
 * Code to compute the bounding box of a DLM display list.
 * Matrix commands in the display list are observed to properly
 * transform the vertices we find.
 */

#include <float.h>
#include "cr_dlm.h"
#include "cr_mem.h"
#include "cr_spu.h"
#include "cr_glstate.h"
#include "dlm.h"


static CRMatrixStack ModelViewStack;
static CRMatrixStack DummyStack;
static CRMatrixStack *CurrentStack;


static GLfloat Xmin, Xmax, Ymin, Ymax, Zmin, Zmax;


static void
MatrixMode(GLenum matrix)
{
	switch (matrix) {
	case GL_MODELVIEW:
		CurrentStack = &ModelViewStack;
		break;
	default:
		CurrentStack = &DummyStack;
	}
}


static void
LoadMatrixf(const GLfloat m[16])
{
	crMatrixInitFromFloats(CurrentStack->top, m);
}


static void
LoadMatrixd(const GLdouble m[16])
{
	crMatrixInitFromDoubles(CurrentStack->top, m);
}


static void
LoadIdentity(void)
{
	crMatrixInit(CurrentStack->top);
}


static void
PushMatrix(void)
{
	if (CurrentStack->depth < CurrentStack->maxDepth) {
		/* copy matrix */
		*(CurrentStack->top + 1) = *(CurrentStack->top);
		/* Move the stack pointer */
		CurrentStack->depth++;
		CurrentStack->top = CurrentStack->stack + CurrentStack->depth;
	}
	else {
		crWarning("Stack overflow in dlm_bbox.c");
	}
}


static void
PopMatrix(void)
{
	if (CurrentStack->depth > 0) {
		CurrentStack->depth--;
		CurrentStack->top = CurrentStack->stack + CurrentStack->depth;
	}
	else {
		crWarning("Stack underflow in dlm_bbox.c");
	}
}


static void
MultMatrixf(const GLfloat m1[16])
{
	CRmatrix *m = CurrentStack->top;
	const GLdefault lm00 = m->m00;  
	const GLdefault lm01 = m->m01;  
	const GLdefault lm02 = m->m02;	
	const GLdefault lm03 = m->m03;	
	const GLdefault lm10 = m->m10;	
	const GLdefault lm11 = m->m11;	
	const GLdefault lm12 = m->m12;	
	const GLdefault lm13 = m->m13;	
	const GLdefault lm20 = m->m20;	
	const GLdefault lm21 = m->m21;	
	const GLdefault lm22 = m->m22;	
	const GLdefault lm23 = m->m23;	
	const GLdefault lm30 = m->m30;	
	const GLdefault lm31 = m->m31;	
	const GLdefault lm32 = m->m32;		
	const GLdefault lm33 = m->m33;		
	const GLdefault rm00 = (GLdefault) m1[0];	
	const GLdefault rm01 = (GLdefault) m1[1];	
	const GLdefault rm02 = (GLdefault) m1[2];	
	const GLdefault rm03 = (GLdefault) m1[3];	
	const GLdefault rm10 = (GLdefault) m1[4];	
	const GLdefault rm11 = (GLdefault) m1[5];		
	const GLdefault rm12 = (GLdefault) m1[6];	
	const GLdefault rm13 = (GLdefault) m1[7];	
	const GLdefault rm20 = (GLdefault) m1[8];	
	const GLdefault rm21 = (GLdefault) m1[9];	
	const GLdefault rm22 = (GLdefault) m1[10];	
	const GLdefault rm23 = (GLdefault) m1[11];	
	const GLdefault rm30 = (GLdefault) m1[12];	
	const GLdefault rm31 = (GLdefault) m1[13];	
	const GLdefault rm32 = (GLdefault) m1[14];	
	const GLdefault rm33 = (GLdefault) m1[15];	

	m->m00 = lm00*rm00 + lm10*rm01 + lm20*rm02 + lm30*rm03;	
	m->m01 = lm01*rm00 + lm11*rm01 + lm21*rm02 + lm31*rm03;	
	m->m02 = lm02*rm00 + lm12*rm01 + lm22*rm02 + lm32*rm03;	
	m->m03 = lm03*rm00 + lm13*rm01 + lm23*rm02 + lm33*rm03;	
	m->m10 = lm00*rm10 + lm10*rm11 + lm20*rm12 + lm30*rm13;	
	m->m11 = lm01*rm10 + lm11*rm11 + lm21*rm12 + lm31*rm13;	
	m->m12 = lm02*rm10 + lm12*rm11 + lm22*rm12 + lm32*rm13;	
	m->m13 = lm03*rm10 + lm13*rm11 + lm23*rm12 + lm33*rm13;	
	m->m20 = lm00*rm20 + lm10*rm21 + lm20*rm22 + lm30*rm23;	
	m->m21 = lm01*rm20 + lm11*rm21 + lm21*rm22 + lm31*rm23;	
	m->m22 = lm02*rm20 + lm12*rm21 + lm22*rm22 + lm32*rm23;	
	m->m23 = lm03*rm20 + lm13*rm21 + lm23*rm22 + lm33*rm23;	
	m->m30 = lm00*rm30 + lm10*rm31 + lm20*rm32 + lm30*rm33;	
	m->m31 = lm01*rm30 + lm11*rm31 + lm21*rm32 + lm31*rm33;	
	m->m32 = lm02*rm30 + lm12*rm31 + lm22*rm32 + lm32*rm33;	
	m->m33 = lm03*rm30 + lm13*rm31 + lm23*rm32 + lm33*rm33;
}


static void
MultMatrixd(const GLdouble m1[16])
{
	GLfloat m2[16];
	int i;
	for (i = 0; i < 16; i++)
		m2[i] = (GLfloat) m1[i];
	MultMatrixf(m2);
}


static void
Rotatef(GLfloat ang, GLfloat x, GLfloat y, GLfloat z) 
{
	crMatrixRotate(CurrentStack->top, ang, x, y, z);
}


static void
Rotated(GLdouble ang, GLdouble x, GLdouble y, GLdouble z) 
{
	crMatrixRotate(CurrentStack->top, (GLfloat) ang,
								 (GLfloat) x, (GLfloat) y, (GLfloat) z);
}


static void
Translatef(GLfloat x, GLfloat y, GLfloat z) 
{
	crMatrixTranslate(CurrentStack->top, x, y, z);
}


static void
Translated(GLdouble x, GLdouble y, GLdouble z) 
{
	crMatrixTranslate(CurrentStack->top, (GLfloat) x, (GLfloat) y, (GLfloat) z);
}


static void
Scalef(GLfloat x, GLfloat y, GLfloat z) 
{
	crMatrixScale(CurrentStack->top, x, y, z);
}


static void
Scaled(GLdouble x, GLdouble y, GLdouble z) 
{
	crMatrixScale(CurrentStack->top, (GLfloat) x, (GLfloat) y, (GLfloat) z);
}


/**
 * Transform given (x,y,z,w) by current matrix and update the bounding box.
 */
static void
DoVertex(GLfloat x, GLfloat y, GLfloat z, GLfloat w)
{
	const CRmatrix *m = CurrentStack->top;
	const GLfloat x2 = m->m00 * x + m->m10 * y + m->m20 * z + m->m30 * w;
	const GLfloat y2 = m->m01 * x + m->m11 * y + m->m21 * z + m->m31 * w;
	const GLfloat z2 = m->m02 * x + m->m12 * y + m->m22 * z + m->m32 * w;
	/*const GLfloat w2 = m->m03 * x + m->m13 * y + m->m23 * z + m->m33 * w;*/

	if (x2 < Xmin) Xmin = x2;
	if (x2 > Xmax) Xmax = x2;
	if (y2 < Ymin) Ymin = y2;
	if (y2 > Ymax) Ymax = y2;
	if (z2 < Zmin) Zmin = z2;
	if (z2 > Zmax) Zmax = z2;
}


static void
Vertex2f(GLfloat x, GLfloat y)
{
	DoVertex(x, y, 0.0f, 1.0f);
}

static void
Vertex2fv(const GLfloat *v)
{
	DoVertex(v[0], v[1], 0.0f, 1.0f);
}

static void
Vertex3f(GLfloat x, GLfloat y, GLfloat z)
{
	DoVertex(x, y, z, 1.0f);
}

static void
Vertex3fv(const GLfloat *v)
{
	DoVertex(v[0], v[1], v[2], 1.0f);
}

static void
Vertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w)
{
	DoVertex(x, y, z, w);
}

static void
Vertex4fv(const GLfloat *v)
{
	DoVertex(v[0], v[1], v[2], v[3]);
}


/**
 ** XXX TO DO:
 **
glVertex2d( GLdouble x, GLdouble y );
glVertex2f( GLfloat x, GLfloat y );
glVertex2i( GLint x, GLint y );
glVertex2s( GLshort x, GLshort y );
glVertex3d( GLdouble x, GLdouble y, GLdouble z );
glVertex3f( GLfloat x, GLfloat y, GLfloat z );
glVertex3i( GLint x, GLint y, GLint z );
glVertex3s( GLshort x, GLshort y, GLshort z );
glVertex4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w );
glVertex4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w );
glVertex4i( GLint x, GLint y, GLint z, GLint w );
glVertex4s( GLshort x, GLshort y, GLshort z, GLshort w );
glVertex2dv( const GLdouble *v );
glVertex2fv( const GLfloat *v );
glVertex2iv( const GLint *v );
glVertex2sv( const GLshort *v );
glVertex3dv( const GLdouble *v );
glVertex3fv( const GLfloat *v );
glVertex3iv( const GLint *v );
glVertex3sv( const GLshort *v );
glVertex4dv( const GLdouble *v );
glVertex4fv( const GLfloat *v );
glVertex4iv( const GLint *v );
glVertex4sv( const GLshort *v );

glVertexAttrib1dARB(GLuint, GLdouble);
glVertexAttrib1dvARB(GLuint, const GLdouble *);
glVertexAttrib1fARB(GLuint, GLfloat);
glVertexAttrib1fvARB(GLuint, const GLfloat *);
glVertexAttrib1sARB(GLuint, GLshort);
glVertexAttrib1svARB(GLuint, const GLshort *);
glVertexAttrib2dARB(GLuint, GLdouble, GLdouble);
glVertexAttrib2dvARB(GLuint, const GLdouble *);
glVertexAttrib2fARB(GLuint, GLfloat, GLfloat);
glVertexAttrib2fvARB(GLuint, const GLfloat *);
glVertexAttrib2sARB(GLuint, GLshort, GLshort);
glVertexAttrib2svARB(GLuint, const GLshort *);
glVertexAttrib3dARB(GLuint, GLdouble, GLdouble, GLdouble);
glVertexAttrib3dvARB(GLuint, const GLdouble *);
**/

static void
VertexAttrib3fARB(GLuint index, GLfloat x, GLfloat y, GLfloat z)
{
	if (index == 0)
		DoVertex(x, y, z, 1.0f);
}

/**
glVertexAttrib3fvARB(GLuint, const GLfloat *);
glVertexAttrib3sARB(GLuint, GLshort, GLshort, GLshort);
glVertexAttrib3svARB(GLuint, const GLshort *);
glVertexAttrib4NbvARB(GLuint, const GLbyte *);
glVertexAttrib4NivARB(GLuint, const GLint *);
glVertexAttrib4NsvARB(GLuint, const GLshort *);
glVertexAttrib4NubARB(GLuint, GLubyte, GLubyte, GLubyte, GLubyte);
glVertexAttrib4NubvARB(GLuint, const GLubyte *);
glVertexAttrib4NuivARB(GLuint, const GLuint *);
glVertexAttrib4NusvARB(GLuint, const GLushort *);
glVertexAttrib4bvARB(GLuint, const GLbyte *);
glVertexAttrib4dARB(GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
glVertexAttrib4dvARB(GLuint, const GLdouble *);
glVertexAttrib4fARB(GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
glVertexAttrib4fvARB(GLuint, const GLfloat *);
glVertexAttrib4ivARB(GLuint, const GLint *);
glVertexAttrib4sARB(GLuint, GLshort, GLshort, GLshort, GLshort);
glVertexAttrib4svARB(GLuint, const GLshort *);
glVertexAttrib4ubvARB(GLuint, const GLubyte *);
glVertexAttrib4uivARB(GLuint, const GLuint *);
glVertexAttrib4usvARB(GLuint, const GLushort *);

glVertexAttrib1dNV(GLuint, GLdouble);
glVertexAttrib1dvNV(GLuint, const GLdouble *);
glVertexAttrib1fNV(GLuint, GLfloat);
glVertexAttrib1fvNV(GLuint, const GLfloat *);
glVertexAttrib1sNV(GLuint, GLshort);
glVertexAttrib1svNV(GLuint, const GLshort *);
glVertexAttrib2dNV(GLuint, GLdouble, GLdouble);
glVertexAttrib2dvNV(GLuint, const GLdouble *);
glVertexAttrib2fNV(GLuint, GLfloat, GLfloat);
glVertexAttrib2fvNV(GLuint, const GLfloat *);
glVertexAttrib2sNV(GLuint, GLshort, GLshort);
glVertexAttrib2svNV(GLuint, const GLshort *);
glVertexAttrib3dNV(GLuint, GLdouble, GLdouble, GLdouble);
glVertexAttrib3dvNV(GLuint, const GLdouble *);
glVertexAttrib3fNV(GLuint, GLfloat, GLfloat, GLfloat);
glVertexAttrib3fvNV(GLuint, const GLfloat *);
glVertexAttrib3sNV(GLuint, GLshort, GLshort, GLshort);
glVertexAttrib3svNV(GLuint, const GLshort *);
glVertexAttrib4dNV(GLuint, GLdouble, GLdouble, GLdouble, GLdouble);
glVertexAttrib4dvNV(GLuint, const GLdouble *);
glVertexAttrib4fNV(GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
glVertexAttrib4fvNV(GLuint, const GLfloat *);
glVertexAttrib4sNV(GLuint, GLshort, GLshort, GLshort, GLshort);
glVertexAttrib4svNV(GLuint, const GLshort *);
glVertexAttrib4ubNV(GLuint, GLubyte, GLubyte, GLubyte, GLubyte);
glVertexAttrib4ubvNV(GLuint, const GLubyte *);

glVertexAttribs1dvNV(GLuint, GLsizei, const GLdouble *);
glVertexAttribs1fvNV(GLuint, GLsizei, const GLfloat *);
glVertexAttribs1svNV(GLuint, GLsizei, const GLshort *);
glVertexAttribs2dvNV(GLuint, GLsizei, const GLdouble *);
glVertexAttribs2fvNV(GLuint, GLsizei, const GLfloat *);
glVertexAttribs2svNV(GLuint, GLsizei, const GLshort *);
glVertexAttribs3dvNV(GLuint, GLsizei, const GLdouble *);
glVertexAttribs3fvNV(GLuint, GLsizei, const GLfloat *);
glVertexAttribs3svNV(GLuint, GLsizei, const GLshort *);
glVertexAttribs4dvNV(GLuint, GLsizei, const GLdouble *);
glVertexAttribs4fvNV(GLuint, GLsizei, const GLfloat *);
glVertexAttribs4svNV(GLuint, GLsizei, const GLshort *);
glVertexAttribs4ubvNV(GLuint, GLsizei, const GLubyte *);
**/


/**
 ** XXX also need to track evaluator coordinates (glutTeapot)
 **/


static void
InitDispatchTable(SPUDispatchTable *t)
{
	crMemZero(t, sizeof(*t));
	crSPUInitDispatchNops(t);
  /* drm1 */
	t->MatrixMode = (MatrixModeFunc_t)MatrixMode;
	t->LoadIdentity = (LoadIdentityFunc_t)LoadIdentity;
	t->LoadMatrixf = (LoadMatrixfFunc_t)LoadMatrixf;
	t->LoadMatrixd = (LoadMatrixdFunc_t)LoadMatrixd;
	t->PushMatrix = (PushMatrixFunc_t)PushMatrix;
	t->PopMatrix = (PopMatrixFunc_t)PopMatrix;
	t->MultMatrixf = (MultMatrixfFunc_t)MultMatrixf;
	t->MultMatrixd = (MultMatrixdFunc_t)MultMatrixd;
	t->Rotatef = (RotatefFunc_t)Rotatef;
  t->Rotated = (RotatedFunc_t)Rotated;
	t->Translatef = (TranslatefFunc_t)Translatef;
	t->Translated = (TranslatedFunc_t)Translated;
	t->Scalef = (ScalefFunc_t)Scalef;
	t->Scaled = (ScaledFunc_t)Scaled;
	t->Vertex2f = (Vertex2fFunc_t)Vertex2f;
	t->Vertex2fv = (Vertex2fvFunc_t)Vertex2fv;
	t->Vertex3f = (Vertex3fFunc_t)Vertex3f;
	t->Vertex3fv = (Vertex3fvFunc_t)Vertex3fv;
	t->Vertex4f = (Vertex4fFunc_t)Vertex4f;
	t->Vertex4fv = (Vertex4fvFunc_t)Vertex4fv;
	t->VertexAttrib3fARB = (VertexAttrib3fARBFunc_t)VertexAttrib3fARB;
}


void
crDLMComputeBoundingBox(unsigned long listId)
{
	static GLboolean tableInitialized = GL_FALSE;
	static SPUDispatchTable t;
	CRDLMContextState *listState = CURRENT_STATE();
	CRDLM *dlm = listState->dlm;
	DLMListInfo *listInfo
		= (DLMListInfo *) crHashtableSearch(dlm->displayLists, listId);

	if (!tableInitialized) {
		InitDispatchTable(&t);
		crStateInitMatrixStack(&ModelViewStack, CR_MAX_MODELVIEW_STACK_DEPTH);
		crStateInitMatrixStack(&DummyStack, CR_MAX_MODELVIEW_STACK_DEPTH);
		tableInitialized = GL_TRUE;
	}

	CurrentStack = &ModelViewStack;

	Xmin = Ymin = Zmin = FLT_MAX;
	Xmax = Ymax = Zmax = -FLT_MAX;
	
	crDLMReplayDLMList(listState->dlm, listId, &t);

	if (Xmin == FLT_MAX) {
		/* XXX review this choice of default bounds */
		/*
		crDebug("Warning: no bounding box!");
		*/
		Xmin = -100;
		Xmax =  100;
		Ymin = -100;
		Ymax =  100;
		Zmin = -100;
		Zmax =  100;
	}
	/*
	crDebug("List %d bbox: %f, %f, %f .. %f, %f, %f", (int) listId,
					Xmin, Ymin, Zmin, Xmax, Ymax, Zmax);
	*/

	listInfo->bbox.xmin = Xmin;
	listInfo->bbox.ymin = Ymin;
	listInfo->bbox.zmin = Zmin;
	listInfo->bbox.xmax = Xmax;
	listInfo->bbox.ymax = Ymax;
	listInfo->bbox.zmax = Zmax;
}
