/* Copyright (c) 2001, Stanford University
 * All rights reserved
 *
 * See the file LICENSE.txt for information on redistributing this software.
 */

#include "server_dispatch.h"
#include "server.h"
#include "cr_error.h"
#include "state/cr_statetypes.h"


static const CRmatrix identity_matrix = { 
	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
};


/*
 * Clip the rectangle q against the given image window.
 */
static void
glStubViewportClipToWindow( const CRrecti *imagewindow,	CRrecti *q) 
{
	if (q->x1 < imagewindow->x1) q->x1 = imagewindow->x1;
	if (q->x1 > imagewindow->x2) q->x1 = imagewindow->x2;

	if (q->x2 > imagewindow->x2) q->x2 = imagewindow->x2;
	if (q->x2 < imagewindow->x1) q->x2 = imagewindow->x1;
	
	if (q->y1 < imagewindow->y1) q->y1 = imagewindow->y1;
	if (q->y1 > imagewindow->y2) q->y1 = imagewindow->y2;

	if (q->y2 > imagewindow->y2) q->y2 = imagewindow->y2;
	if (q->y2 < imagewindow->y1) q->y2 = imagewindow->y1;
}


/*
 * Translate the rectangle q from the image window space to the outputwindow
 * space.
 */
static void
glStubConvertToOutput( const CRrecti *imagewindow,
												 const CRrecti *outputwindow,
												 CRrecti *q )
{
	q->x1 = q->x1 - imagewindow->x1 + outputwindow->x1;
	q->x2 = q->x2 - imagewindow->x2 + outputwindow->x2;
	q->y1 = q->y1 - imagewindow->y1 + outputwindow->y1;
	q->y2 = q->y2 - imagewindow->y2 + outputwindow->y2;
}


/*
 * Compute clipped image window, scissor, viewport and base projection
 * info for each tile in the mural.
 * Need to call this when either the viewport or mural is changed.
 */
void
glStubComputeViewportBounds(const CRViewportState *v, CRMuralInfo *mural)
{
	static GLuint serialNo = 1;
	int i;

	for (i = 0; i < mural->numExtents; i++) {
		CRExtent *extent = &mural->extents[i];
		CRrecti q;

		/* If the scissor is disabled set it to the whole output.
		** We might as well use the actual scissorTest rather than
		** scissorValid - it never gets reset anyway.
		*/
		if (!v->scissorTest)
		{
			extent->scissorBox = extent->outputwindow;
		} 
		else 
		{
			q.x1 = v->scissorX;
			q.x2 = v->scissorX + v->scissorW;
			q.y1 = v->scissorY;
			q.y2 = v->scissorY + v->scissorH;

			glStubViewportClipToWindow(&(extent->imagewindow), &q);
			glStubConvertToOutput(&(extent->imagewindow),
															&(extent->outputwindow), &q);
			extent->scissorBox = q;
		}

		/* if the viewport is not valid,
		** set it to the entire output.
		*/
		if (!v->viewportValid) 
		{
			extent->clippedImagewindow = extent->imagewindow;
			extent->viewport = extent->outputwindow;
		}
		else
		{
			q.x1 = v->viewportX;
			q.x2 = v->viewportX + v->viewportW;
			q.y1 = v->viewportY;
			q.y2 = v->viewportY + v->viewportH;

			/* This is where the viewport gets clamped to the max size. */
			glStubViewportClipToWindow(&(extent->imagewindow), &q);

			extent->clippedImagewindow = q;

			glStubConvertToOutput(&(extent->imagewindow),
															&(extent->outputwindow), &q);

			extent->viewport = q;
		}

		/*
		** Now, compute the base projection.
		*/
		if (extent->clippedImagewindow.x1 == extent->clippedImagewindow.x2 ||
				extent->clippedImagewindow.y1 == extent->clippedImagewindow.y2) {
			/* zero-area extent, use identity matrix (doesn't really matter) */
			extent->baseProjection = identity_matrix;
		}
		else
		{
			const int vpx = v->viewportX;
			const int vpy = v->viewportY;
			const int vpw = v->viewportW;
			const int vph = v->viewportH;
			GLfloat xscale, yscale;
			GLfloat xtrans, ytrans;
			CRrectf p;

			/* 
			 * We need to take account of the current viewport parameters,
			 * and they are passed to this function as x, y, w, h.
			 * In the default case (from main.c) we pass the the
			 * full muralsize of 0, 0, width, height
			 */
			p.x1 = (GLfloat) (extent->clippedImagewindow.x1 - vpx) / vpw;
			p.y1 = (GLfloat) (extent->clippedImagewindow.y1 - vpy) / vph;
			p.x2 = (GLfloat) (extent->clippedImagewindow.x2 - vpx) / vpw;
			p.y2 = (GLfloat) (extent->clippedImagewindow.y2 - vpy) / vph;

			/* XXX not sure this clamping is really neeed anymore
			 */
			if (p.x1 < 0.0) { 
				p.x1 = 0.0;
				if (p.x2 > 1.0) p.x2 = 1.0;
			}

			if (p.y1 < 0.0) {
				p.y1 = 0.0; 
				if (p.y2 > 1.0) p.y2 = 1.0;
			}

			/* Rescale [0,1] -> [-1,1] */
			p.x1 = p.x1 * 2.0f - 1.0f;
			p.x2 = p.x2 * 2.0f - 1.0f;
			p.y1 = p.y1 * 2.0f - 1.0f;
			p.y2 = p.y2 * 2.0f - 1.0f;

			xscale = 2.0f / (p.x2 - p.x1);
			yscale = 2.0f / (p.y2 - p.y1);
			xtrans = -(p.x2 + p.x1) / 2.0f;
			ytrans = -(p.y2 + p.y1) / 2.0f;

			CRASSERT(xscale == xscale); /* NaN test */
			CRASSERT(yscale == yscale);

			extent->baseProjection = identity_matrix;
			extent->baseProjection.m00 = xscale;
			extent->baseProjection.m11 = yscale;
			extent->baseProjection.m30 = xtrans * xscale;
			extent->baseProjection.m31 = ytrans * yscale;
		}

		extent->serialNo = serialNo++;
	}
	mural->viewportValidated = GL_TRUE;
}


/*
 * Issue the glScissor, glViewport and projection matrix needed for
 * rendering the tile specified by extNum.  We computed the scissor,
 * viewport and projection parameters above in glStubComputeViewportBounds.
 */
void
glStubSetOutputBounds( const CRMuralInfo *mural, int extNum )
{
	const CRExtent *extent = mural->extents + extNum;
	CRASSERT(mural->viewportValidated);

	/*
	 * Serial Number info:
	 * Everytime we compute new scissor, viewport, projection matrix info for
	 * a tile, we give that tile a new serial number.
	 * When we're about to render into a tile, we only update the scissor,
	 * viewport and projection matrix if the tile's serial number doesn't match
	 * the current serial number.  This avoids a _LOT_ of redundant calls to
	 * those three functions.
	 */
	if (extent->serialNo != cr_server.currentSerialNo) {
		cr_server.head_spu->dispatch_table.Scissor(extent->scissorBox.x1,
																 extent->scissorBox.y1,
																 extent->scissorBox.x2 - extent->scissorBox.x1,
																 extent->scissorBox.y2 - extent->scissorBox.y1);

		cr_server.head_spu->dispatch_table.Viewport(extent->viewport.x1,
																 extent->viewport.y1,
																 extent->viewport.x2 - extent->viewport.x1,
																 extent->viewport.y2 - extent->viewport.y1);

		glStubApplyBaseProjection(&(extent->baseProjection));
		cr_server.currentSerialNo = extent->serialNo;
	}		
}


/*
 * Pre-multiply the current projection matrix with the current client's
 * base projection.  I.e.  P' = b * P.  Note that OpenGL's glMultMatrix
 * POST-multiplies.
 */
void
glStubApplyBaseProjection(const CRmatrix *baseProj)
{
	const CRmatrix *projMatrix;
	if (cr_server.projectionOverride) {
		int eye = glStubGetCurrentEye();
		projMatrix = &cr_server.projectionMatrix[eye];
	}
	else
		projMatrix = cr_server.curClient->currentCtx->transform.projectionStack.top;

	cr_server.head_spu->dispatch_table.PushAttrib( GL_TRANSFORM_BIT );
	cr_server.head_spu->dispatch_table.MatrixMode( GL_PROJECTION );
	cr_server.head_spu->dispatch_table.LoadMatrixf( (const GLfloat *) baseProj );
	cr_server.head_spu->dispatch_table.MultMatrixf( cr_server.alignment_matrix );
	cr_server.head_spu->dispatch_table.MultMatrixf( (const GLfloat *) projMatrix );
	cr_server.head_spu->dispatch_table.PopAttrib();
}


void
glStubApplyViewMatrix(const CRmatrix *view)
{
	const CRmatrix *modelview = cr_server.curClient->currentCtx->transform.modelViewStack.top;

	cr_server.head_spu->dispatch_table.PushAttrib( GL_TRANSFORM_BIT );
	cr_server.head_spu->dispatch_table.MatrixMode( GL_MODELVIEW );
	cr_server.head_spu->dispatch_table.LoadMatrixf( (const GLfloat *) view );
	cr_server.head_spu->dispatch_table.MultMatrixf( (const GLfloat *) modelview );
	cr_server.head_spu->dispatch_table.PopAttrib();
}


/*
 * Called via unpacker module.
 * Note: when there's a tilesort SPU upstream, the viewport dimensions
 * will typically match the mural size.  That is, the viewport dimensions
 * probably won't be the same values that the application issues.
 */
void SERVER_DISPATCH_APIENTRY glStubDispatchViewport( GLint x, GLint y, GLsizei width, GLsizei height )
{
	CRMuralInfo *mural = cr_server.curClient->currentMural;
	CRContext *ctx = crStateGetCurrent();

	if (ctx->viewport.viewportX != x ||
			ctx->viewport.viewportY != y ||
			ctx->viewport.viewportW != width ||
			ctx->viewport.viewportH != height) {
		 /* Note -- If there are tiles, this will be overridden in the 
			* process of decoding the BoundsInfo packet, so no worries. */
		 crStateViewport( x, y, width, height );

		 mural->viewportValidated = GL_FALSE;
	}

	/* always dispatch to be safe */
	cr_server.head_spu->dispatch_table.Viewport( x, y, width, height );
}
