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

#include "state.h"
#include "cr_mem.h"
#include "cr_error.h"
#include "cr_spu.h"

#ifdef CHROMIUM_THREADSAFE
CRtsd __contextTSD;
#else
CRContext *__currentContext = NULL;
#endif

CRStateBits *__currentBits = NULL;
GLboolean g_availableContexts[CR_MAX_CONTEXTS];

static CRContext *defaultContext = NULL;



/**
 * Allocate a new shared state object.
 * Contains texture objects, display lists, etc.
 */
static CRSharedState *
crStateAllocShared(void)
{
	CRSharedState *s = (CRSharedState *) crCalloc(sizeof(CRSharedState));
	if (s) {
		s->textureTable = crAllocHashtable();
		s->dlistTable = crAllocHashtable();
		s->refCount = 1; /* refcount is number of contexts using this state */
	}
	return s;
}



/**
 * Callback used for crFreeHashtable().
 */
static void
DeleteTextureCallback(void *texObj)
{
	crStateDeleteTextureObject((CRTextureObj *) texObj);
}


/**
 * Decrement shared state's refcount and delete when it hits zero.
 */
static void
crStateFreeShared(CRSharedState *s)
{
	s->refCount--;
	if (s->refCount <= 0) {
		crFreeHashtable(s->textureTable, DeleteTextureCallback);
		crFreeHashtable(s->dlistTable, crFree); /* call crFree for each entry */
		crFree(s);
	}
}


/*
 * Helper for crStateCreateContext, below.
 */
static CRContext *
crStateCreateContextId(int i, const CRLimitsState *limits,
											 GLint visBits, CRContext *shareCtx)
{
	CRContext *ctx = (CRContext *) crCalloc( sizeof( *ctx ) );
	int j;
	int node32 = i >> 5;
	int node = i & 0x1f;

	ctx->id = i;
	ctx->flush_func = NULL;
	for (j=0;j<CR_MAX_BITARRAY;j++){
		if (j == node32) {
			ctx->bitid[j] = (1 << node);
		} else {
			ctx->bitid[j] = 0;
		}
		ctx->neg_bitid[j] = ~(ctx->bitid[j]);
	}

	if (shareCtx) {
		CRASSERT(shareCtx->shared);
		ctx->shared = shareCtx->shared;
		ctx->shared->refCount ++;
	}
	else {
		ctx->shared = crStateAllocShared();
	}

	/* use Chromium's OpenGL defaults */
	crStateLimitsInit( &(ctx->limits) );
	crStateExtensionsInit( &(ctx->limits), &(ctx->extensions) );

	crStateBufferObjectInit( ctx ); /* must precede client state init! */
	crStateClientInit( &(ctx->client) );

	crStateBufferInit( ctx );
	crStateCurrentInit( ctx );
	crStateEvaluatorInit( ctx );
	crStateFogInit( ctx );
	crStateHintInit( ctx );
	crStateLightingInit( ctx );
	crStateLineInit( ctx );
	crStateListsInit( ctx );
	crStateMultisampleInit( ctx );
	crStateOcclusionInit( ctx );
	crStatePixelInit( ctx );
	crStatePolygonInit( ctx );
	crStatePointInit( ctx );
	crStateProgramInit( ctx );
	crStateRegCombinerInit( ctx );
	crStateStencilInit( ctx );
	crStateTextureInit( ctx );
	crStateTransformInit( ctx );
	crStateViewportInit ( ctx );
	
	/* This has to come last. */
	crStateAttribInit( &(ctx->attrib) );

	ctx->renderMode = GL_RENDER;

	/* Initialize values that depend on the visual mode */
	if (visBits & CR_DOUBLE_BIT) {
		ctx->limits.doubleBuffer = GL_TRUE;
	}
	if (visBits & CR_RGB_BIT) {
		ctx->limits.redBits = 8;
		ctx->limits.greenBits = 8;
		ctx->limits.blueBits = 8;
		if (visBits & CR_ALPHA_BIT) {
			ctx->limits.alphaBits = 8;
		}
	}
	else {
		ctx->limits.indexBits = 8;
	}
	if (visBits & CR_DEPTH_BIT) {
		ctx->limits.depthBits = 24;
	}
	if (visBits & CR_STENCIL_BIT) {
		ctx->limits.stencilBits = 8;
	}
	if (visBits & CR_ACCUM_BIT) {
		ctx->limits.accumRedBits = 16;
		ctx->limits.accumGreenBits = 16;
		ctx->limits.accumBlueBits = 16;
		if (visBits & CR_ALPHA_BIT) {
			ctx->limits.accumAlphaBits = 16;
		}
	}
	if (visBits & CR_STEREO_BIT) {
		ctx->limits.stereo = GL_TRUE;
	}
	if (visBits & CR_MULTISAMPLE_BIT) {
		ctx->limits.sampleBuffers = 1;
		ctx->limits.samples = 4;
		ctx->multisample.enabled = GL_TRUE;
	}

	if (visBits & CR_OVERLAY_BIT) {
		ctx->limits.level = 1;
	}

	return ctx;
}


void
crStateFreeContext(CRContext *ctx)
{
	crStateClientDestroy( &(ctx->client) );
	crStateLimitsDestroy( &(ctx->limits) );
	crStateBufferObjectDestroy( ctx );
	crStateEvaluatorDestroy( ctx );
	crStateListsDestroy( ctx );
	crStateLightingDestroy( ctx );
	crStateOcclusionDestroy( ctx );
	crStateProgramDestroy( ctx );
	crStateTextureDestroy( ctx );
	crStateTransformDestroy( ctx );
	crStateFreeShared(ctx->shared);
	crFree( ctx );
}


/*
 * Allocate the state (dirty) bits data structures.
 * This should be called before we create any contexts.
 * We'll also create the default/NULL context at this time and make
 * it the current context by default.  This means that if someone
 * tries to set GL state before calling MakeCurrent() they'll be
 * modifying the default state object, and not segfaulting on a NULL
 * pointer somewhere.
 */
void crStateInit(void)
{
	unsigned int i;

	/* Purely initialize the context bits */
	if (!__currentBits) {
		__currentBits = (CRStateBits *) crCalloc( sizeof(CRStateBits) );
		crStateClientInitBits( &(__currentBits->client) );
		crStateLightingInitBits( &(__currentBits->lighting) );
	} else
		crWarning("State tracker is being re-initialized..\n");

	for (i=0;i<CR_MAX_CONTEXTS;i++)
		g_availableContexts[i] = 0;

	if (defaultContext) {
		/* Free the default/NULL context.
		 * Ensures context bits are reset */
		crStateFreeContext(defaultContext);
	}

	/* Reset diff_api */
	crMemZero(&diff_api, sizeof(SPUDispatchTable));

	/* Allocate the default/NULL context */
	defaultContext = crStateCreateContextId(0, NULL, CR_RGB_BIT, NULL);
	CRASSERT(g_availableContexts[0] == 0);
	g_availableContexts[0] = 1; /* in use forever */

#ifdef CHROMIUM_THREADSAFE
	crInitTSD(&__contextTSD);
	crSetTSD(&__contextTSD, defaultContext);
#else
	__currentContext = defaultContext;
#endif
}



/*
 * Notes on context switching and the "default context".
 *
 * See the paper "Tracking Graphics State for Networked Rendering"
 * by Ian Buck, Greg Humphries and Pat Hanrahan for background
 * information about how the state tracker and context switching
 * works.
 *
 * When we make a new context current, we call crStateSwitchContext()
 * in order to transform the 'from' context into the 'to' context
 * (i.e. the old context to the new context).  The transformation
 * is accomplished by calling GL functions through the 'diff_api'
 * so that the downstream GL machine (represented by the __currentContext
 * structure) is updated to reflect the new context state.  Finally, 
 * we point __currentContext to the new context.
 *
 * A subtle problem we have to deal with is context destruction.
 * This issue arose while testing with Glean.  We found that when
 * the currently bound context was getting destroyed that state
 * tracking was incorrect when a subsequent new context was activated.
 * In DestroyContext, the __hwcontext was being set to NULL and effectively
 * going away.  Later in MakeCurrent we had no idea what the state of the
 * downstream GL machine was (since __hwcontext was gone).  This meant
 * we had nothing to 'diff' against and the downstream GL machine was
 * in an unknown state.
 *
 * The solution to this problem is the "default/NULL" context.  The
 * default context is created the first time CreateContext is called
 * and is never freed.  Whenever we get a crStateMakeCurrent(NULL) call
 * or destroy the currently bound context in crStateDestroyContext()
 * we call crStateSwitchContext() to switch to the default context and
 * then set the __currentContext pointer to point to the default context.
 * This ensures that the dirty bits are updated and the diff_api functions
 * are called to keep the downstream GL machine in a known state.
 * Finally, the __hwcontext variable is no longer needed now.
 *
 * Yeah, this is kind of a mind-bender, but it really solves the problem
 * pretty cleanly.
 *
 * -Brian
 */


CRContext *
crStateCreateContext(const CRLimitsState *limits, GLint visBits, CRContext *share)
{
	int i;

	/* Must have created the default context via crStateInit() first */
	CRASSERT(defaultContext);

	for (i = 1 ; i < CR_MAX_CONTEXTS ; i++)
	{
		if (!g_availableContexts[i])
		{
			g_availableContexts[i] = 1; /* it's no longer available */
			return crStateCreateContextId( i, limits, visBits, share );
		}
	}
	crError( "Out of available contexts in crStateCreateContexts (max %d)",
					 CR_MAX_CONTEXTS );
	/* never get here */
	return NULL;
}


void crStateDestroyContext( CRContext *ctx )
{
	CRContext *current = GetCurrentContext();

	if (current == ctx) {
		/* destroying the current context - have to be careful here */
		CRASSERT(defaultContext);
		/* Check to see if the differencer exists first,
		   we may not have one, aka the packspu */
		if (diff_api.AlphaFunc)
			crStateSwitchContext(current, defaultContext);
#ifdef CHROMIUM_THREADSAFE
		crSetTSD(&__contextTSD, defaultContext);
#else
		__currentContext = defaultContext;
#endif
		/* ensure matrix state is also current */
		crStateMatrixMode(defaultContext->transform.matrixMode);
	}
	g_availableContexts[ctx->id] = 0;

	crStateFreeContext(ctx);
}


void crStateMakeCurrent( CRContext *ctx )
{
	CRContext *current = GetCurrentContext();

	if (ctx == NULL)
		ctx = defaultContext;

	if (current == ctx)
		return; /* no-op */

	CRASSERT(ctx);

	if (current) {
		/* Check to see if the differencer exists first,
		   we may not have one, aka the packspu */
		if (diff_api.AlphaFunc)
			crStateSwitchContext( current, ctx );
	}

#ifdef CHROMIUM_THREADSAFE
	crSetTSD(&__contextTSD, ctx);
#else
	__currentContext = ctx;
#endif

	/* ensure matrix state is also current */
	crStateMatrixMode(ctx->transform.matrixMode);
}


/*
 * As above, but don't call crStateSwitchContext().
 */
void crStateSetCurrent( CRContext *ctx )
{
	CRContext *current = GetCurrentContext();

	if (ctx == NULL)
		ctx = defaultContext;

	if (current == ctx)
		return; /* no-op */

	CRASSERT(ctx);

#ifdef CHROMIUM_THREADSAFE
	crSetTSD(&__contextTSD, ctx);
#else
	__currentContext = ctx;
#endif

	/* ensure matrix state is also current */
	crStateMatrixMode(ctx->transform.matrixMode);
}


CRContext *crStateGetCurrent(void)
{
	return GetCurrentContext();
}


void crStateUpdateColorBits(void)
{
	/* This is a hack to force updating the 'current' attribs */
	CRStateBits *sb = GetCurrentBits();
	FILLDIRTY(sb->current.dirty);
	FILLDIRTY(sb->current.vertexAttrib[VERT_ATTRIB_COLOR0]);
}


void STATE_APIENTRY
crStateChromiumParameteriCR( GLenum target, GLint value )
{
	/* This no-op function helps smooth code-gen */
}

void STATE_APIENTRY
crStateChromiumParameterfCR( GLenum target, GLfloat value )
{
	/* This no-op function helps smooth code-gen */
}

void STATE_APIENTRY
crStateChromiumParametervCR( GLenum target, GLenum type, GLsizei count, const GLvoid *values )
{
	/* This no-op function helps smooth code-gen */
}

void STATE_APIENTRY
crStateGetChromiumParametervCR( GLenum target, GLuint index, GLenum type, GLsizei count, GLvoid *values )
{
	/* This no-op function helps smooth code-gen */
}

void STATE_APIENTRY
crStateReadPixels( GLint x, GLint y, GLsizei width, GLsizei height,
									 GLenum format, GLenum type, GLvoid *pixels )
{
	/* This no-op function helps smooth code-gen */
}

