/*
 * $Id$
 *
 * $Date$
 * $Revision$
 *
 * (C) 1999-2005 The MiniGL team
 * All rights reserved
 *
 * This file is part of the MiniGL library project
 * See the file Licence.txt for more details
 *
 */

#include "sysinc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <proto/timer.h>
#include <proto/utility.h>

static char rcsid[] UNUSED = "$Id$";

extern void m_CombineMatrices(GLcontext context);
extern void RebindTextures(GLcontext context);
extern void tex_EstablishEnvCombine(GLcontext context);

#define CLIP_EPS (1e-7)

static GLboolean CodeRasterPos(GLcontext context)
{
	float w = context->current.RasterPos.w;
	GLuint outcode = 0;

	if (context->current.RasterPos.w < CLIP_EPS )
	{
		outcode |= MGL_CLIP_NEGW;
	}

	if (-w > context->current.RasterPos.x)
	{
		outcode |= MGL_CLIP_LEFT;
	}
	else if (context->current.RasterPos.x > w)
	{
		outcode |= MGL_CLIP_RIGHT;
	}

	if (-w > context->current.RasterPos.y)
	{
		outcode |= MGL_CLIP_BOTTOM;
	}
	else if (context->current.RasterPos.y > w)
	{
		outcode |= MGL_CLIP_TOP;
	}

	if (-w > context->current.RasterPos.z)
	{
		outcode |= MGL_CLIP_BACK;
	}
	else if (context->current.RasterPos.z > w)
	{
		outcode |= MGL_CLIP_FRONT;
	}

#if 0
	if (context->MaxClipPlane != -1)
	{
		int i;

		for (i = 0; i <= context->MaxClipPlane; i++)
		{
			if (context->UserClipPlane[i].enabled)
			{
				GLfloat d
				  = context->UserClipPlane[i].eqn[0] * v->eye.x
				  + context->UserClipPlane[i].eqn[1] * v->eye.y
				  + context->UserClipPlane[i].eqn[2] * v->eye.z
				  + context->UserClipPlane[i].eqn[3] * v->eye.w;

				if (d < 0.0)
					outcode |= context->UserClipPlane[i].outcode;
			}
		}
	}
#endif

	return (outcode == 0);
}

void cgl_GLRasterPos4f(struct GLContextIFace *Self, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
{
	GLcontext context = GET_INSTANCE(Self);
	
#if 0
	/*
	** Current texture coordinates are s/t resp. This means
	** that texture wrap occurs at 0 and 1, not at 0 and width/height.
	*/
	if (context->MaxTextureUnit > -1)
	{
		for (i = 0; i <= context->MaxTextureUnit; i++)
		{
			if (context->Tex2D_State[i] == GL_TRUE)
			{
				if (context->CurTexQValid[i] == GL_TRUE)
				{
					context->RasterTexCoords[i].u = context->CurTexS[i]/context->CurTexQ[i];
					context->RasterTexCoords[i].v = context->CurTexT[i]/context->CurTexQ[i];
					context->RasterTexCoords[i].w = context->CurTexQ[i];
					context->RasterRhwValid[i][i] = GL_TRUE;
				}
				else
				{
					context->RasterTexCoords[i].u = context->CurTexS[i];
					context->RasterTexCoords[i].v = context->CurTexT[i];
					context->RasterTexCoords[i].w = 1.0;
					context->RasterRhwValid[i][i] = GL_FALSE;
				}
			}
		}
	}
#endif

	context->current.RasterColor.r = context->current.CurrentColor.r;
	context->current.RasterColor.g = context->current.CurrentColor.g;
	context->current.RasterColor.b = context->current.CurrentColor.b;
	context->current.RasterColor.a = context->current.CurrentColor.a;

	if (context->CombinedValid == GL_FALSE)
		m_CombineMatrices(context);

	#define a(x) (context->CombinedMatrix.v[OF_##x])
	context->current.RasterPos.x = a(11)*x + a(12)*y + a(13)*z + a(14)*w;
	context->current.RasterPos.y	= a(21)*x + a(22)*y + a(23)*z + a(24)*w;
	context->current.RasterPos.z = a(31)*x + a(32)*y + a(33)*z + a(34)*w;
	context->current.RasterPos.w	= a(41)*x + a(42)*y + a(43)*z + a(44)*w;

	context->current.RasterPosValid = CodeRasterPos(context);

	if (context->current.RasterPosValid)
	{
		/* If valid, project to screen */
		context->current.RasterPos.x = context->ax
									+ context->current.RasterPos.x 
									* context->current.RasterPos.w
									* context->sx;
		context->current.RasterPos.y = context->ay
									- context->current.RasterPos.y 
									* context->current.RasterPos.w
									* context->sy;
		context->current.RasterDistance
									= context->az
									+ context->current.RasterPos.z 
									* context->current.RasterPos.w
									* context->sz;
	}
}

void unpack_bitmap_16(void *_context, GLubyte *from, GLubyte *to, GLsizei num)
{
	/* FIXME: Honor Unpack skip pixels */
	GLcontext context = (GLcontext)_context;
	GLsizei i, j;
	unsigned int mask;
	GLushort *dst = (GLushort *)to;

	for (i = 0; i < num/8; i++)
	{
		if (context->pixel_store.UnpackLSBFirst)
			mask = 0x01;
		else
			mask = 0x80;

		GLubyte cur = *from++;

		for (j = 0; j < 8; j++)
		{
			if (cur & mask)
				*dst++ = 0xffff;
			else
				*dst++ = 0;

			if (context->pixel_store.UnpackLSBFirst)
				mask <<= 1;
			else
				mask >>= 1;
		}
	}
}

GLsizei next_pwr(GLsizei x)
{
	GLsizei r = 1;

	while (r < x)
		r <<= 1;

	return r;
}

/* How glBitmaps works:
 * Since the OpenGL specs say that fragments are only generated for pixels of the bitmap
 * that are '1', the bitmap is first turned into a texture. Next, it's drawn at the
 * current raster position as a Quad with chroma keying set up in such a way that only white pixels
 * pass.
 */
typedef struct
{
	float x,y,z;
	float r,g,b,a;
	float u,v,w;
} BMVertex;

#define BMVERTEX_FORMAT (W3D_VFORMAT_COLOR | W3D_VFORMAT_TCOORD_0)

void cgl_GLBitmap(struct GLContextIFace *Self, GLsizei w, GLsizei h, GLfloat xbo, GLfloat ybo, GLfloat xbi, GLfloat ybi, GLubyte *data)
{
	GLcontext context = GET_INSTANCE(Self);
	
	GLsizei dataSize;
	GLubyte *targetData;
	GLuint i;
	W3D_Texture *tex;
	uint32 oldCullState, oldTexmappingState, oldGouraudState, oldAlphaState;
	GLsizei rw, rh;
	uint32 error;

	/* Round sizes up to next power of two */
	rw = next_pwr(w);
	rh = next_pwr(h);

	/* Check for valid raster pos. If invalid, just return */
	if (!context->current.RasterPosValid)
		return;

	/* Convert the bitmap into a texture */
	dataSize = rw * rh * 2;
	
	if (context->CurrentBitmapBackingStoreSize < dataSize)
	{
		/* Need to enlarge the cache */
		if (context->CurrentBitmapBackingStore)
			IExec->FreeVec(context->CurrentBitmapBackingStore);

		context->CurrentBitmapBackingStore = IExec->AllocVec(dataSize, MEMF_ANY);
		if (!context->CurrentBitmapBackingStore)
		{
			/* Failed allocation */
			context->CurrentBitmapBackingStoreSize = 0;
			return;
		}

		context->CurrentBitmapBackingStoreSize = dataSize;
	}

	/* If we reach here, we have enough memory to convert the bitmap data. */
	targetData = context->CurrentBitmapBackingStore;
	IUtility->ClearMem(targetData, dataSize);

	for (i = 0; i < h; i++)
	{
		unpack_bitmap_16(context, data, targetData, w);
		data += (w/8) + (w%8 == 0 ? 0 : 1);
		targetData += 2*rw;
	}

	/* Allocate a texture with this data */
	tex = IWarp3D->W3D_AllocTexObjTags(context->w3dContext, NULL,
				W3D_ATO_IMAGE,		context->CurrentBitmapBackingStore,
				W3D_ATO_FORMAT,		W3D_R5G6B5,
				W3D_ATO_WIDTH,		rw,
				W3D_ATO_HEIGHT,		rh,
			TAG_DONE);
	
	if (!tex)
	{
		/* Can't allocate the texture, bail out */
		return;
	}
		
	/* Set texture parameters and chroma key to pass on white */
	IWarp3D->W3D_SetFilter(context->w3dContext, tex, W3D_NEAREST, W3D_NEAREST);
	IWarp3D->W3D_SetTexEnv(context->w3dContext, tex, W3D_MODULATE, NULL);
	IWarp3D->W3D_SetChromaTestBounds(context->w3dContext, tex, 0xffffffff, 0x80808080, W3D_CHROMATEST_INCLUSIVE);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_CHROMATEST, W3D_ENABLE);
	IWarp3D->W3D_SetWrapMode(context->w3dContext, tex, W3D_CLAMP, W3D_CLAMP, 0);

	oldCullState = IWarp3D->W3D_GetState(context->w3dContext, W3D_CULLFACE);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_CULLFACE, W3D_DISABLE);

	oldTexmappingState = IWarp3D->W3D_GetState(context->w3dContext, W3D_TEXMAPPING);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_TEXMAPPING, W3D_ENABLE);

	oldGouraudState = IWarp3D->W3D_GetState(context->w3dContext, W3D_GOURAUD);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_GOURAUD, W3D_ENABLE);

	oldAlphaState = IWarp3D->W3D_GetState(context->w3dContext, W3D_BLENDING);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_BLENDING, W3D_DISABLE);

	IWarp3D->W3D_BindTexture(context->w3dContext, 0, tex);
	IWarp3D->W3D_SetTextureBlendTags(context->w3dContext,
		W3D_BLEND_STAGE, 	0,
			W3D_ENV_MODE,	W3D_MODULATE,			
		W3D_BLEND_STAGE,	1,
			W3D_ENV_MODE,	W3D_OFF,

	TAG_DONE);
	IWarp3D->W3D_SetTextureBlendTags(context->w3dContext, TAG_DONE);
	
	/* Set up the drawing structure */
	BMVertex quad[4];

	/* Lower left corner */
	quad[0].x = context->current.RasterPos.x - xbo;
	quad[0].y = context->current.RasterPos.y - ybo;
	quad[0].z = context->current.RasterDistance;
	quad[0].w = 1.0 - context->current.RasterDistance;
	quad[0].u = 0.0;
	quad[0].v = (float)h;
	quad[0].r = context->current.RasterColor.r;
	quad[0].g = context->current.RasterColor.g;
	quad[0].b = context->current.RasterColor.b;
	quad[0].a = context->current.RasterColor.a;

	/* Lower right corner */
	quad[1].x = context->current.RasterPos.x - xbo + w;
	quad[1].y = context->current.RasterPos.y - ybo;
	quad[1].z = context->current.RasterDistance;
	quad[1].w = 1.0 - context->current.RasterDistance;
	quad[1].u = (float)w;
	quad[1].v = (float)h;
	quad[1].r = context->current.RasterColor.r;
	quad[1].g = context->current.RasterColor.g;
	quad[1].b = context->current.RasterColor.b;
	quad[1].a = context->current.RasterColor.a;
	
	/* Upper right corner */
	quad[2].x = context->current.RasterPos.x - xbo + w;
	quad[2].y = context->current.RasterPos.y - ybo + h;
	quad[2].z = context->current.RasterDistance;
	quad[2].w = 1.0 - context->current.RasterDistance;
	quad[2].u = (float)w;
	quad[2].v = 0.0;
	quad[2].r = context->current.RasterColor.r;
	quad[2].g = context->current.RasterColor.g;
	quad[2].b = context->current.RasterColor.b;
	quad[2].a = context->current.RasterColor.a;
	
	/* Upper left corner */
	quad[3].x = context->current.RasterPos.x - xbo;
	quad[3].y = context->current.RasterPos.y - ybo + h;
	quad[3].z = context->current.RasterDistance;
	quad[3].w = 1.0 - context->current.RasterDistance;
	quad[3].u = 0.0;
	quad[3].v = 0.0;
	quad[3].r = context->current.RasterColor.r;
	quad[3].g = context->current.RasterColor.g;
	quad[3].b = context->current.RasterColor.b;
	quad[3].a = context->current.RasterColor.a;
	
	if (context->w3dLocked == GL_FALSE)
		IWarp3D->W3D_LockHardware(context->w3dContext);
	
	IWarp3D->W3D_InterleavedArray(context->w3dContext,
		quad, sizeof(BMVertex), BMVERTEX_FORMAT, 0);
		
	error = IWarp3D->W3D_DrawArray(context->w3dContext, W3D_PRIMITIVE_TRIFAN,
		0, 4);
	
	if (context->w3dLocked == GL_FALSE)
		IWarp3D->W3D_UnLockHardware(context->w3dContext);
	
	// Might not need this, but it doesn't hurt.
	IWarp3D->W3D_InterleavedArray(context->w3dContext, 
		context->VertexBuffer, sizeof (MGLVertex), context->VertexFormat,
		W3D_TEXCOORD_NORMALIZED);
	
	IWarp3D->W3D_FreeTexObj(context->w3dContext, tex);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_CHROMATEST, W3D_DISABLE);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_CULLFACE, oldCullState);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_TEXMAPPING, oldTexmappingState);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_GOURAUD, oldGouraudState);
	IWarp3D->W3D_SetState(context->w3dContext, W3D_BLENDING, oldAlphaState);

	RebindTextures(context);
	tex_EstablishEnvCombine(context);

	context->current.RasterPos.x += xbi;
	context->current.RasterPos.y += ybi;
}
