/*
 * $Id$
 *
 * $Date$
 * $Revision$
 *
 * (C) 1999 by Hyperion
 * 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 <proto/timer.h>

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


extern void d_DrawPoints        (struct GLcontext_t);
extern void d_DrawLines         (struct GLcontext_t);
extern void d_DrawLineStrip     (struct GLcontext_t);
extern void d_DrawTriangles     (struct GLcontext_t);
extern void d_DrawTriangleFan   (struct GLcontext_t);
extern void d_DrawTriangleStrip (struct GLcontext_t);
extern void d_DrawQuads         (struct GLcontext_t);
extern void d_DrawPolygon       (struct GLcontext_t);
extern void d_DrawFlatFan       (struct GLcontext_t);
extern void d_DrawQuadStrip     (struct GLcontext_t);

//extern void tex_ConvertTexture  (GLcontext);
extern void fog_Set             (GLcontext);

extern void light_UpdateColorMaterial(GLcontext);

void TMA_Start(LockTimeHandle *handle);
GLboolean TMA_Check(LockTimeHandle *handle);

/*
Timer based locking stuff.

TMA_Start starts the time measuring for the lock time. The 68k uses the ReadEClock
function due to its low overhead. PPC will use GetSysTimePPC for the same reasons.

The EClock version reads the eclock and stores the current values in the handle.
It then calculates a maximum lock time based on the assumption that the lock
should be unlocked after 0.05 seconds (i.e. twenty times per second).

TMA_Check checks if the specified time has expired. If it returns GL_FALSE,
the lock may be kept alive. On return of GL_TRUE, the lock must be released.

Note that this routine handles the case where the ev_hi values has changed, i.e.
the eV_lo value had an overrun. This code also assumes, however, that the difference
between the current and former ev_hi is no more than 1. This is, however, a very
safe assumption; It takes approx. 100 minutes for the ev_hi field to increment,
and it is extremely unlikely that the ev_hi field overruns - this will happen after
approx. 820,000 years uptime (of course, a reliable system should be prepared for
this)

*/


void TMA_Start(LockTimeHandle *handle)
{
	struct EClockVal eval;
	
	handle->e_freq = ITimer->ReadEClock(&eval);
	handle->s_hi = eval.ev_hi;
	handle->s_lo = eval.ev_lo;
	handle->e_freq /= 20;
}

GLboolean TMA_Check(LockTimeHandle *handle)
{
	struct EClockVal eval;
	ULONG ticks;

	ITimer->ReadEClock(&eval);

	if (eval.ev_hi == handle->s_hi)
		ticks = eval.ev_lo - handle->s_lo;
	else
		ticks = (~0)-handle->s_lo + eval.ev_lo;

	if (ticks > handle->e_freq) 
		return GL_TRUE;

	return GL_FALSE;
}


float CLAMPF(float a)
{
    float res;

    asm(
    "fsel    %0,%2,%2,%1"
    :
    "=f" (res):
    "f" (0.0f), "f" (a));

    asm(
    "fsub    %0,%1,%2"
    :
    "=f" (a):
    "f" (res), "f" (1.0f));

    asm(
    "fsel    %0,%1,%2,%3"
    :
    "=f" (res):
    "f" (a),"f" (1.0f),"f" (res));

    return res;
}


void cgl_GLBegin(struct GLContextIFace *Self, GLenum mode)
{
	GLcontext context = GET_INSTANCE(Self);

	extern void tex_EstablishEnvCombine(GLcontext context);
	extern void checkNeedEye(GLcontext context);
	
//    GLFlagError(context, context->CurrentPrimitive != GL_BASE, GL_INVALID_OPERATION);

	context->VertexBufferPointer = 0;
	
	context->FrameCode ++;
	context->TexFrameCode ++;
	
	tex_EstablishEnvCombine(context);
	checkNeedEye(context);
	
	switch((int)mode)
	{
		case GL_POINTS:
			//LOG(1, glBegin, "GL_POINTS");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawPoints;
			break;
		case GL_LINES:
			//LOG(1, glBegin, "GL_LINES");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawLines;
			break;
		case GL_LINE_STRIP:
			//LOG(1, glBegin, "GL_LINE_STRIP");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawLineStrip;
			break;
		case GL_LINE_LOOP:
			//LOG(1, glBegin, "GL_LINE_LOOP");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawLineStrip;
			break;
		case GL_TRIANGLES:
			//LOG(1, glBegin, "GL_TRIANLES");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawTriangles;
			break;
		case GL_TRIANGLE_STRIP:
			//LOG(1, glBegin, "GL_TRIANGLE_STRIP");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawTriangleStrip;
			break;
		case GL_TRIANGLE_FAN:
			//LOG(1, glBegin, "GL_TRIANGLE_FAN");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawTriangleFan;
			break;
		case GL_QUADS:
			//LOG(1, glBegin, "GL_QUADS");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawQuads;
			break;
		case GL_QUAD_STRIP:
			//LOG(1, glBegin, "GL_QUAD_STRIP");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawQuadStrip;
			break;
		case GL_POLYGON:
			//LOG(1, glBegin, "GL_POLYGON");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawPolygon;
			break;
		case MGL_FLATFAN:
			//LOG(1, glBegin, "MGL_FLATFAN");
			context->CurrentPrimitive = mode;
			context->CurrentDraw = (DrawFn)d_DrawFlatFan;
			break;
		default:
			//LOG(1, glBegin, "Error GL_INVALID_OPERATION");
			GLFlagError (context, 1, GL_INVALID_OPERATION);
			break;
	}
}

void cgl_GLColor4f(struct GLContextIFace *Self, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
{
	GLcontext context = GET_INSTANCE(Self);

	context->current.CurrentColor.r = CLAMPF(red);
	context->current.CurrentColor.g = CLAMPF(green);
	context->current.CurrentColor.b = CLAMPF(blue);
	context->current.CurrentColor.a = CLAMPF(alpha);

#ifdef MGL_USE_LIGHTING
	if (context->enable.ColorMaterial)
		light_UpdateColorMaterial(context);
#endif	

//	context->FrameCode ++;
}


void cgl_GLVertex4f(struct GLContextIFace *Self, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
{
	GLcontext context = GET_INSTANCE(Self);

	int i;
	
	#define thisvertex context->VertexBuffer[context->VertexBufferPointer]
	thisvertex.object.x = x;
	thisvertex.object.y = y;
	thisvertex.object.z = z;
	thisvertex.object.w = w;

	/*
	** 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->texture.MaxTextureUnit > -1)
	{
		for (i = 0; i <= context->texture.MaxTextureUnit; i++)
		{
			if (context->enable.Texture2D[i] == GL_TRUE)
			{
				if (context->current.CurTexQValid[i] == GL_TRUE)
				{
					thisvertex.uvw[i].u = context->current.CurTexS[i]
										/ context->current.CurTexQ[i];
					thisvertex.uvw[i].v = context->current.CurTexT[i]
										/ context->current.CurTexQ[i];
					thisvertex.uvw[i].w = context->current.CurTexQ[i];
					thisvertex.rhw_valid[i] = GL_TRUE;
				}
				else
				{
					thisvertex.uvw[i].u = context->current.CurTexS[i];
					thisvertex.uvw[i].v = context->current.CurTexT[i];
					thisvertex.uvw[i].w = 1.0;
					thisvertex.rhw_valid[i] = GL_FALSE;
				}
			}
		}
	}

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

	thisvertex.Normal.x  = context->current.CurrentNormal.x;
	thisvertex.Normal.y  = context->current.CurrentNormal.y;
	thisvertex.Normal.z  = context->current.CurrentNormal.z;

	thisvertex.outcode = 0;
	thisvertex.inview  = GL_FALSE;
	
	if (context->fog.CurrentFogSource == GL_FOG_COORD)
		thisvertex.bf = context->current.CurrentFogDepth;
	else
		thisvertex.bf = z;
	
	context->VertexBufferPointer ++;
	#undef thisvertex
}

void cgl_GLNormal3f(struct GLContextIFace *Self, GLfloat x, GLfloat y, GLfloat z)
{
	GLcontext context = GET_INSTANCE(Self);

	context->current.CurrentNormal.x = x;
	context->current.CurrentNormal.y = y;
	context->current.CurrentNormal.z = z;

//	context->FrameCode ++;
}

void cgl_GLTexCoord2f(struct GLContextIFace *Self, GLfloat s, GLfloat t)
{
	GLcontext context = GET_INSTANCE(Self);

	context->current.CurTexS[context->texture.ActiveTexture] 		= s;
	context->current.CurTexT[context->texture.ActiveTexture] 		= t;
	context->current.CurTexQ[context->texture.ActiveTexture] 		= 1.0;
	context->current.CurTexQValid[context->texture.ActiveTexture]	= GL_FALSE;

//	context->TexFrameCode ++;
}


void cgl_GLTexCoord4f(struct GLContextIFace *Self, GLfloat s, GLfloat t, GLfloat r, GLfloat q)
{
	GLcontext context = GET_INSTANCE(Self);

	context->current.CurTexS[context->texture.ActiveTexture]		= s;
	context->current.CurTexT[context->texture.ActiveTexture]		= t;
	context->current.CurTexQ[context->texture.ActiveTexture]		= q;
	context->current.CurTexQValid[context->texture.ActiveTexture] 	= GL_TRUE;

//	context->TexFrameCode ++;
}


void cgl_GLMultiTexCoord2f(struct GLContextIFace *Self, GLenum stage, GLfloat s, GLfloat t)
{
	GLcontext context = GET_INSTANCE(Self);

	stage -= GL_TEXTURE0;
	if (stage < context->NumTextureUnits)
	{
		context->current.CurTexS[stage] 		= s;
		context->current.CurTexT[stage] 		= t;
		context->current.CurTexQ[stage] 		= 1.0;
		context->current.CurTexQValid[stage]	= GL_FALSE;

//		context->TexFrameCode ++;
	}
}

void cgl_GLMultiTexCoord4f(struct GLContextIFace *Self, GLenum stage, GLfloat s, GLfloat t, GLfloat r, GLfloat q)
{
	GLcontext context = GET_INSTANCE(Self);

	stage -= GL_TEXTURE0;
	if (stage < context->NumTextureUnits)
	{
		context->current.CurTexS[stage]		= s;
		context->current.CurTexT[stage]		= t;
		context->current.CurTexQ[stage]		= q;
		context->current.CurTexQValid[stage] 	= GL_TRUE;

//		context->TexFrameCode ++;
	}
}


void cgl_GLDepthRange(struct GLContextIFace *Self, GLclampd n, GLclampd f)
{
	GLcontext context = GET_INSTANCE(Self);

	context->viewport.near = n;
	context->viewport.far  = f;
	context->sz = (f-n)*0.5;
	context->az = (n+f)*0.5;
}

void cgl_GLViewport(struct GLContextIFace *Self, GLint x, GLint y, GLsizei w, GLsizei h)
{
	GLcontext context = GET_INSTANCE(Self);

	context->viewport.x = x;
	context->viewport.y = y;
	context->viewport.w = w;
	context->viewport.h = h;

	context->ax = (double)x + (double)w*0.5;
	if (context->textureRenderTarget)
	{
		// We have a temporary texture render target
		context->ay = context->w3dTextures[context->textureRenderTarget].texObj
			->texheight - (double)y - (double)h*0.5;
	}
	else if (context->w3dWindow)
	{
		context->ay =  (double)(context->w3dWindow->Height
							  - context->w3dWindow->BorderTop
							  - context->w3dWindow->BorderBottom)
			                  - (double)y 
		    	              - (double)h*0.5;
	}
	else
	{
		context->ay = (double)(IP96->p96GetBitMapAttr(context->w3dBitMap, P96BMA_HEIGHT)
							- (double)y - (double)h*0.5);
	}
							
	context->sx = (double)w * 0.5;
	context->sy = (double)h * 0.5;
}

void cgl_GLEnd(struct GLContextIFace *Self)
{
	GLcontext context = GET_INSTANCE(Self);

	if (context->FogDirty && context->enable.Fog)
	{
		fog_Set(context);
		context->FogDirty = GL_FALSE;
	}

	if (context->lighting.ShadeModel == GL_FLAT)
	{
		static W3D_Color color;
		color.r = CLAMPF(context->current.CurrentColor.r);
		color.g = CLAMPF(context->current.CurrentColor.g);
		color.b = CLAMPF(context->current.CurrentColor.b);
		color.a = CLAMPF(context->current.CurrentColor.a);
		IWarp3D->W3D_SetCurrentColor(context->w3dContext, &color);
	}

	// Check for blending inconsistancy
//	if (context->AlphaFellBack && (context->SrcAlpha == GL_ONE || context->DstAlpha == GL_ONE)
//		&& context->Blend_State == GL_TRUE)
//	{
//		tex_ConvertTexture(context);
//	}

	if (context->LockMode == MGL_LOCK_AUTOMATIC) // Automatic: Lock per primitive
	{
		if (W3D_SUCCESS == IWarp3D->W3D_LockHardware(context->w3dContext))
		{
			context->w3dLocked = GL_TRUE;
			IWarp3D->W3D_InterleavedArray(context->w3dContext, 
				context->VertexBuffer, sizeof (MGLVertex), context->VertexFormat,
				W3D_TEXCOORD_NORMALIZED);
			context->CurrentDraw(context);
			IWarp3D->W3D_UnLockHardware(context->w3dContext);
			context->w3dLocked = GL_FALSE;
		}
		else
		{
			dprintf("Error during LockHardware\n");
		}
	}
	else if (context->LockMode == MGL_LOCK_MANUAL) // Manual: Lock manually
	{
		IWarp3D->W3D_InterleavedArray(context->w3dContext, 
				context->VertexBuffer, sizeof (MGLVertex), context->VertexFormat,
				W3D_TEXCOORD_NORMALIZED);
		context->CurrentDraw(context);
	}
	else // Smart: Lock timer based
	{
		if (context->w3dLocked == GL_FALSE)
		{
			if (W3D_SUCCESS != IWarp3D->W3D_LockHardware(context->w3dContext))
			{
				dprintf("[glEnd] Error during W3D_LockHardware()\n");
				return; // give up
			}
			context->w3dLocked = GL_TRUE;
			TMA_Start(&(context->LockTime));
		}

		IWarp3D->W3D_InterleavedArray(context->w3dContext, 
				context->VertexBuffer, sizeof (MGLVertex), context->VertexFormat,
				W3D_TEXCOORD_NORMALIZED);
		context->CurrentDraw(context);  // Draw!
		if (TMA_Check(&(context->LockTime)) == GL_TRUE)
		{
			// Time to unlock
			IWarp3D->W3D_UnLockHardware(context->w3dContext);
			context->w3dLocked = GL_FALSE;
		}
	}

	context->CurrentPrimitive = GL_BASE;
}

void cgl_GLFinish(struct GLContextIFace *Self)
{
	GLcontext context = GET_INSTANCE(Self);

	Self->GLFlush();
	IWarp3D->W3D_WaitIdle(context->w3dContext);
}

void cgl_GLFlush(struct GLContextIFace *Self)
{
	//LOG(2, glFlush, "");
}
