/* GGI-Driver for MESA
 * 
 * Copyright (C) 1997-1998  Uwe Maurer  -  uwe_maurer@t-online.de 
 *                    2002  Filip Spacek
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * ---------------------------------------------------------------------
 * This code was derived from the following source of information:
 *
 * svgamesa.c and ddsample.c by Brian Paul
 * 
 */

#ifdef HAVE_CONFIG_H
#include "conf.h"
#endif

#include <ggi/mesa/ggimesa_int.h>
#include <ggi/mesa/debug.h>
#include "extensions.h"
#include "buffers.h"
#include "colormac.h"
#include "imports.h"
#include "matrix.h"
#include "swrast/swrast.h"
#include "swrast_setup/swrast_setup.h"
#include "tnl/tnl.h"
#include "tnl/t_context.h"
#include "tnl/t_pipeline.h"
#include "vbo/vbo.h"
#include "teximage.h"
#include "texformat.h"
#include "texstore.h"

/* We use LibGG to manage config files */
#include <ggi/gg.h>


/* XXX: Those #defines should be provided via 
 * config.h
 */
#define GGIMESAPATHTAG	"pAtHTAg"
#define GGIMESACONFDIR	"pAtHTAg/usr/local/etc/ggi"
#define GGIMESATAGLEN	7
#define GGIMESACONFFILE	"ggimesa.conf"


/* Static variables
 */
static int _ggimesaLibIsUp = 0;
static void *_ggimesaConfigHandle;
static char _ggimesaconfstub[512] = GGIMESACONFDIR;
static char *_ggimesaconfdir = _ggimesaconfstub+GGIMESATAGLEN;

int _ggimesaDebugSync = 0;
uint32 _ggimesaDebugState = 0;



/* Extension ID. Defaulting to -1 should make segfault on abuse more likely...
 */
ggi_extid _ggiMesaID = -1;


#define SUBLIB_PREFIX	"MesaGGIdl_"


/*
 * Returns the directory where global config files are kept
 */
 
const char *ggiMesaGetConfDir(void)
{
#ifdef __WIN32__
	/* On Win32 we allow overriding of the compiled in path. */
	const char *envdir = getenv("GGI_CONFDIR");
	if (envdir) return envdir;
#endif
	return _ggimesaconfdir;
}


/* Dummy function which returns -1
   We use this to reset the function pointers */
static int _ggi_error(void)
{
	GGIMESADPRINT_CORE("_ggi_error() called\n");
	
	return -1;
}


static int changed(ggi_visual_t vis, int whatchanged)
{
	GLcontext *ctx;
	ctx = _mesa_get_current_context();

	GGIMESADPRINT_CORE("changed() called\n");
		
	switch (whatchanged) {
	case GGI_CHG_APILIST:
	{
		char api[GGI_MAX_APILEN];
		char args[GGI_MAX_APILEN];
		int i;
		const char *fname;
		ggi_dlhandle *lib;

		GLvisual *gl_vis = &(LIBGGI_MESAEXT(vis)->mesa_visual.gl_visual);
		GLframebuffer *gl_fb = &(LIBGGI_MESAEXT(vis)->mesa_buffer);
		
		/* Initialize the framebuffer to provide all necessary
		   buffers in software. The target libraries that are loaded
		   next are free to modify this according to their
		   capabilities. 
		 */
		 /* FIXME: if the target changes capabilities we'll leak 
		    swrast's memory !!! Need to deallocate first */
		_mesa_initialize_framebuffer(gl_fb, gl_vis,
					 gl_vis->depthBits > 0,
					 gl_vis->stencilBits > 0,
					 gl_vis->accumRedBits > 0,
					 gl_vis->alphaBits > 0);

		for (i = 0; ggiGetAPI(vis, i, api, args) == 0; i++) {
			strcat(api, "-mesa");
			GGIMESADPRINT_CORE("GGIMesa: looking for"
					"a sublib named %s\n", api);
			fname = ggMatchConfig(_ggimesaConfigHandle, api, NULL);
			if (fname == NULL) {
				/* No special implementation for this sublib */
				continue;
			}
			lib = ggiExtensionLoadDL(vis, fname, args, NULL,
						 SUBLIB_PREFIX);
		}

		/* The targets have cleared everything they can do from 
		   the framebuffer structure so we provide the rest in sw
		 */
		/*_swrast_alloc_buffers(gl_fb);*/
		
		break;
	} 
	}
	return 0;
}


int ggiMesaInit()
{
	int err;
	char *str;
	char *conffile;
	
	GGIMESADPRINT_CORE("ggiMesaInit() called\n");
	
	_ggimesaLibIsUp++;
	if (_ggimesaLibIsUp > 1) return 0; /* Initialize only at first call */
	
	str = getenv("GGIMESA_DEBUGSYNC");
	if (str != NULL) {
		_ggimesaDebugSync = 1;
	}
	
	str = getenv("GGIMESA_DEBUG");
	if (str != NULL) {
		_ggimesaDebugState = atoi(str);
		GGIMESADPRINT_CORE("%s Debugging=%d\n",
			_ggimesaDebugSync ? "sync" : "async",
			_ggimesaDebugState);
	}
	

	conffile = malloc(strlen(ggiMesaGetConfDir()) + 1
			  + strlen(GGIMESACONFFILE) +1);
	if (conffile == NULL) {
		fprintf(stderr, "GGIMesa: unable to allocate memory for config filename.\n");
		return GGI_ENOMEM;
	}
	sprintf(conffile, "%s%c%s",
		ggiMesaGetConfDir(), '/', GGIMESACONFFILE);
	err = ggLoadConfig(conffile, &_ggimesaConfigHandle);
	if (err != GGI_OK) {
		fprintf(stderr, "GGIMesa: Couldn't open %s\n",
			conffile);
		free(conffile);
		_ggimesaLibIsUp--;
		return err;
	}
	free(conffile);
	
	_ggiMesaID = ggiExtensionRegister("GGIMesa",
					 sizeof(struct ggi_mesa_ext), changed);
	if (_ggiMesaID < 0) {
		fprintf(stderr, "GGIMesa: failed to register as extension\n");
		_ggimesaLibIsUp--;
		ggFreeConfig(_ggimesaConfigHandle);
		return _ggiMesaID;
	}
	
	return 0;
}

int ggiMesaExit(void)
{
	int rc;

	GGIMESADPRINT_CORE("ggiMesaExit() called\n");

	if (!_ggimesaLibIsUp) return -1;

	if (_ggimesaLibIsUp > 1) {
		/* Exit only at last call */
		_ggimesaLibIsUp--;
		return 0;
	}
	
	rc = ggiExtensionUnregister(_ggiMesaID);
	ggFreeConfig(_ggimesaConfigHandle);

	_ggimesaLibIsUp = 0;

	return rc;
}




static void gl_ggiUpdateState(GLcontext *ctx, GLuint new_state);


static void gl_ggiGetSize(GLframebuffer *fb, GLuint *width, GLuint *height)
{
	/* FIXME: this is a hack to work around the new interface */
	GLcontext *ctx;
	ggi_mesa_context_t ggi_ctx;
	ctx = _mesa_get_current_context();
	ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;
	
	GGIMESADPRINT_CORE("gl_ggiGetSize() called\n");
	
	*width = LIBGGI_VIRTX(ggi_ctx->ggi_visual);
	*height = LIBGGI_VIRTY(ggi_ctx->ggi_visual);
	printf("returning %d, %d\n", *width, *height);
}

/**
 * We only implement this function as a mechanism to check if the
 * framebuffer size has changed (and update corresponding state).
 */
static void gl_ggiViewport(GLcontext *ctx, GLint x, GLint y, GLsizei w, GLsizei h)
{
   GLuint newWidth, newHeight;
   GLframebuffer *buffer = ctx->WinSysDrawBuffer;
   gl_ggiGetSize( buffer, &newWidth, &newHeight );
   if (buffer->Width != newWidth || buffer->Height != newHeight) {
      _mesa_resize_framebuffer(ctx, buffer, newWidth, newHeight );
   }
}


static void gl_ggiSetIndex(GLcontext *ctx, GLuint ci)
{
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;
	
	GGIMESADPRINT_CORE("gl_ggiSetIndex() called\n");
	
	ggiSetGCForeground(ggi_ctx->ggi_visual, ci);
	ggi_ctx->color = (ggi_pixel)ci;
}

static void gl_ggiSetClearIndex(GLcontext *ctx, GLuint ci)
{	
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;

	GGIMESADPRINT_CORE("gl_ggiSetClearIndex() called\n");

	ggiSetGCForeground(ggi_ctx->ggi_visual, ci);
	ggi_ctx->clearcolor = (ggi_pixel)ci;
}

static void gl_ggiSetClearColor(GLcontext *ctx, const GLfloat color[4])
{
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;
	ggi_color rgb;
	ggi_pixel col;
	GLubyte byteColor[3];

	GGIMESADPRINT_CORE("gl_ggiSetClearColor() called\n");
	
	CLAMPED_FLOAT_TO_UBYTE(byteColor[0], color[0]);
	CLAMPED_FLOAT_TO_UBYTE(byteColor[1], color[1]);
	CLAMPED_FLOAT_TO_UBYTE(byteColor[2], color[2]);

	rgb.r = (uint16)byteColor[0] << SHIFT;
	rgb.g = (uint16)byteColor[1] << SHIFT;
	rgb.b = (uint16)byteColor[2] << SHIFT;
	col = ggiMapColor(ggi_ctx->ggi_visual, &rgb);
	ggiSetGCForeground(ggi_ctx->ggi_visual, col);
	ggi_ctx->clearcolor = col;
}

static void gl_ggiClear(GLcontext *ctx, GLbitfield mask)
{
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;
        int x = ctx->DrawBuffer->_Xmin;
        int y = ctx->DrawBuffer->_Ymin;
        int w = ctx->DrawBuffer->_Xmax - x;
        int h = ctx->DrawBuffer->_Ymax - y;
        GLboolean all = (w == ctx->DrawBuffer->Width && h == ctx->DrawBuffer->height)
	
	GGIMESADPRINT_CORE("gl_ggiClear() called\n");

	if (mask & (DD_FRONT_LEFT_BIT | DD_BACK_LEFT_BIT)) {
		ggiSetGCForeground(ggi_ctx->ggi_visual, ggi_ctx->clearcolor);

		if (all) {
			int w, h;
			w = LIBGGI_VIRTX(ggi_ctx->ggi_visual);
			h = LIBGGI_VIRTX(ggi_ctx->ggi_visual);
			ggiDrawBox(ggi_ctx->ggi_visual, 0, 0, w, h);
		} else {
			ggiDrawBox(ggi_ctx->ggi_visual, x, y, //FLIP(y),
				   width, height);
		}
		ggiSetGCForeground(ggi_ctx->ggi_visual, ggi_ctx->color);

		mask &= ~(DD_FRONT_LEFT_BIT | DD_BACK_LEFT_BIT);
	}
	_swrast_Clear(ctx, mask);
	
}


/* Set the buffer used for reading */
/* XXX support for separate read/draw buffers hasn't been tested */
static GLboolean gl_ggiSetBuffer(GLcontext *ctx, GLframebuffer *buffer, GLuint bufferBit)
{
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;

	printf("set read %d\n", bufferBit);
	GGIMESADPRINT_CORE("gl_ggiSetBuffer() called\n");

	if (bufferBit == DD_FRONT_LEFT_BIT) 
	{
		ggiSetReadFrame(ggi_ctx->ggi_visual,
				ggiGetDisplayFrame(ggi_ctx->ggi_visual));
		ggiSetWriteFrame(ggi_ctx->ggi_visual,
				 ggiGetDisplayFrame(ggi_ctx->ggi_visual));
		return GL_TRUE;
	}
	else if (bufferBit == DD_BACK_LEFT_BIT)
	{
		ggiSetReadFrame(ggi_ctx->ggi_visual,
				ggiGetDisplayFrame(ggi_ctx->ggi_visual)?0 : 1);
		ggiSetWriteFrame(ggi_ctx->ggi_visual,
				 ggiGetDisplayFrame(ggi_ctx->ggi_visual)?0 : 1);
		return GL_TRUE;
	}
	else
		return GL_FALSE;
}


static const GLubyte * gl_ggiGetString(GLcontext *ctx, GLenum name)
{
	GGIMESADPRINT_CORE("gl_ggiGetString() called\n");

	if (name == GL_RENDERER) {
		return (GLubyte *) "Mesa GGI";
	} else {
		return NULL;
	}
}

static void gl_ggiFlush(GLcontext *ctx)
{
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;

	GGIMESADPRINT_CORE("gl_ggiFlush() called\n");
	
	ggiFlush(ggi_ctx->ggi_visual);
}

static void gl_ggiIndexMask(GLcontext *ctx, GLuint mask)
{
	GGIMESADPRINT_CORE("gl_ggiIndexMask() called\n");
}

static void gl_ggiColorMask(GLcontext *ctx, GLboolean rmask, GLboolean gmask,
			    GLboolean bmask, GLboolean amask)
{
	GGIMESADPRINT_CORE("gl_ggiColorMask() called\n");
}

static void gl_ggiEnable(GLcontext *ctx, GLenum pname, GLboolean state)
{
	GGIMESADPRINT_CORE("gl_ggiEnable() called\n");
}

static void gl_ggiSetupPointers(GLcontext *ctx)
{
	TNLcontext *tnl;

	GGIMESADPRINT_CORE("gl_ggiSetupPointers() called\n");

	/* Plug in default driver functions */
	_mesa_init_driver_functions(&ctx->Driver);

	/* Plug in ggi-specific functions */
	ctx->Driver.GetString = gl_ggiGetString;
	ctx->Driver.GetBufferSize = gl_ggiGetSize;
        ctx->Driver.Viewport = gl_ggiViewport;
	ctx->Driver.Finish = gl_ggiFlush;
	ctx->Driver.Flush = gl_ggiFlush;
	ctx->Driver.Clear = gl_ggiClear;
	ctx->Driver.ClearIndex = gl_ggiSetClearIndex; 
	ctx->Driver.ClearColor = gl_ggiSetClearColor;
	ctx->Driver.IndexMask = gl_ggiIndexMask;
	ctx->Driver.ColorMask = gl_ggiColorMask;
	ctx->Driver.Enable = gl_ggiEnable;
	ctx->Driver.UpdateState = gl_ggiUpdateState;

	/* Initialize TNL driver interface */
	tnl = TNL_CONTEXT(ctx);
	tnl->Driver.RunPipeline = _tnl_run_pipeline;
	
	/* Install setup for tnl */
	_swsetup_Wakeup(ctx);
}

static void get_mode_info(ggi_visual_t vis, int *r, int *g, int *b,
			  GLboolean *rgb, GLboolean *db, int *ci)
{
	unsigned int i;
	
	*r = 0;
	*g = 0;
	*b = 0;

	for(i = 0; i < sizeof(ggi_pixel)*8; ++i) {
		int mask = 1 << i;
		if (LIBGGI_PIXFMT(vis)->red_mask & mask)
			++(*r);
		if (LIBGGI_PIXFMT(vis)->green_mask & mask)
			++(*g);
		if (LIBGGI_PIXFMT(vis)->blue_mask & mask)
			++(*b);
	}

	*rgb = GT_SCHEME(LIBGGI_MODE(vis)->graphtype) == GT_TRUECOLOR;
	*db = LIBGGI_MODE(vis)->frames > 1;
	*ci = GT_SIZE(LIBGGI_MODE(vis)->graphtype);

	printf("rgb (%d, %d, %d) db %d, rgb %d ci %d\n",*r,*g,*b,*db,*rgb,*ci);
}
	

int ggiMesaAttach(ggi_visual_t vis)
{
	int rc;

	GGIMESADPRINT_CORE("ggiMesaAttach() called\n");

	rc = ggiExtensionAttach(vis, _ggiMesaID);
	if (rc == 0)
	{
		int r, g, b, ci;
		GLboolean rgb, db;
		GLvisual *gl_visual;
		
		/* We are creating the primary instance */
		memset(LIBGGI_MESAEXT(vis), 0, sizeof(struct ggi_mesa_ext));
		LIBGGI_MESAEXT(vis)->update_state = (void *)_ggi_error;
		LIBGGI_MESAEXT(vis)->setup_driver = (void *)_ggi_error;

		/* Initialize default mesa visual */
		get_mode_info(vis, &r, &g, &b, &rgb, &db, &ci);
		gl_visual = &(LIBGGI_MESAEXT(vis)->mesa_visual.gl_visual);
		_mesa_initialize_visual(gl_visual,
					rgb, db, 0 /* No stereo */,
					r, g, b, 0 /* No alpha */, ci,
					0 /* No depth */, 0 /* No stencil */,
					0, 0, 0, 0 /* No accum */, 0);
		
		/* Now fake an "API change" so the right libs get loaded */
		changed(vis, GGI_CHG_APILIST);
	}
	
	return rc;
}

int ggiMesaDetach(ggi_visual_t vis)
{
	GGIMESADPRINT_CORE("ggiMesaDetach() called\n");
	
	return ggiExtensionDetach(vis, _ggiMesaID);
}
 
int ggiMesaExtendVisual(ggi_visual_t vis, GLboolean alpha_flag,
			GLboolean stereo_flag, GLint depth_size,
			GLint stencil_size, GLint accum_red_size,
			GLint accum_green_size, GLint accum_blue_size,
			GLint accum_alpha_size, GLint num_samples)
{
        GLvisual *gl_vis = &(LIBGGI_MESAEXT(vis)->mesa_visual.gl_visual);
	int r, g, b, ci;
	GLboolean db, rgb;

	get_mode_info(vis, &r, &g, &b, &rgb, &db, &ci);
       
	/* Initialize the visual with the provided information */	
	_mesa_initialize_visual(gl_vis,
				rgb, db, stereo_flag,
				r, g, b, 0 /* FIXME */, ci,
				depth_size, stencil_size,
				accum_red_size, accum_green_size,
				accum_blue_size, accum_alpha_size, 0);

	/* Now fake an "API change" so the right libs get loaded. After all,
	   extending the visual by all these new buffers could be considered
	   a "mode change" which requires an "API change".
	 */
	changed(vis, GGI_CHG_APILIST);
	
	return 0;
}


ggi_mesa_context_t ggiMesaCreateContext(ggi_visual_t vis)
{
	ggi_mesa_context_t ctx;
	int err;

	GGIMESADPRINT_CORE("ggiMesaCreateContext() called\n");
	
	ctx = (ggi_mesa_context_t)malloc(sizeof(struct ggi_mesa_context));
	if (!ctx) 
		return NULL;
	
	ctx->ggi_visual = vis;
	ctx->color = 0;

	ctx->gl_ctx =
	  _mesa_create_context(&(LIBGGI_MESAEXT(vis)->mesa_visual.gl_visual),
			       NULL, (void *) ctx, GL_FALSE);
	if (!ctx->gl_ctx)
		goto free_context;
	
        _mesa_enable_sw_extensions(ctx->gl_ctx);
	
	_swrast_CreateContext(ctx->gl_ctx);
	_vbo_CreateContext(ctx->gl_ctx);
	_tnl_CreateContext(ctx->gl_ctx);
	_swsetup_CreateContext(ctx->gl_ctx);
	
	gl_ggiSetupPointers(ctx->gl_ctx);

	/* Make sure that an appropriate sublib has been loaded */
	if (!LIBGGI_MESAEXT(ctx->ggi_visual)->setup_driver){
		GGIMESADPRINT_CORE("setup_driver==NULL!\n");
		GGIMESADPRINT_CORE("Please check your config files!\n");
		goto free_context;
	}

	/* Set up the sublib driver */
	err = LIBGGI_MESAEXT(ctx->ggi_visual)->setup_driver(ctx);
	if (err){
		GGIMESADPRINT_CORE("setup_driver failed (err = %d)", err);
		goto free_gl_context;
	}

	return ctx;
	
free_gl_context:
	_mesa_destroy_context(ctx->gl_ctx);
free_context:
	free(ctx);
	
	return NULL;
}

void ggiMesaDestroyContext(ggi_mesa_context_t ctx)
{
	GGIMESADPRINT_CORE("ggiMesaDestroyContext() called\n");
	
	if(!ctx)
		return;

	_mesa_destroy_context(ctx->gl_ctx);
	free(ctx);
}

void ggiMesaMakeCurrent(ggi_mesa_context_t ctx, ggi_visual_t vis)
{
	GGIMESADPRINT_CORE("ggiMesaMakeCurrent(ctx = %p) called\n", ctx);

	/* FIXME: clean up where are ggi_vis */
	if (ctx->ggi_visual != vis) {
		GGIMESADPRINT_CORE("Cannot migrate GL contexts\n");
		return;
	}
	
	_mesa_make_current(ctx->gl_ctx, &LIBGGI_MESAEXT(vis)->mesa_buffer);
}


/*
 * Swap front/back buffers for current context if double buffered.
 */
void ggiMesaSwapBuffers(void)
{
	GLcontext *ctx;
	ggi_mesa_context_t ggi_ctx;
	ctx = _mesa_get_current_context();
	ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;
	
	GGIMESADPRINT_CORE("ggiMesaSwapBuffers() called\n");	
	
	_mesa_notifySwapBuffers(ctx);
	gl_ggiFlush(ctx);

	ggiSetDisplayFrame(ggi_ctx->ggi_visual,
			   !ggiGetDisplayFrame(ggi_ctx->ggi_visual));
	ggiSetWriteFrame(ggi_ctx->ggi_visual,
			 !ggiGetWriteFrame(ggi_ctx->ggi_visual));
	ggiSetReadFrame(ggi_ctx->ggi_visual,
			 !ggiGetReadFrame(ggi_ctx->ggi_visual));

	GGIMESADPRINT_CORE("swap disp: %d, write %d\n",
			   ggiGetDisplayFrame(ggi_ctx->ggi_visual),
			   ggiGetWriteFrame(ggi_ctx->ggi_visual));
}

static void gl_ggiUpdateState(GLcontext *ctx, GLuint new_state)
{
	ggi_mesa_context_t ggi_ctx = (ggi_mesa_context_t)ctx->DriverCtx;
	
	GGIMESADPRINT_CORE("gl_ggiUpdateState() called\n");
		
	/* Propogate statechange information to swrast and swrast_setup
	 * modules.  The GGI driver has no internal GL-dependent state.
	 */
	_swrast_InvalidateState(ctx, new_state);
	_swsetup_InvalidateState(ctx, new_state);
	_tnl_InvalidateState(ctx, new_state);
	
	/* XXX: Better use an assertion that bails out here on failure */
	if (!LIBGGI_MESAEXT(ggi_ctx->ggi_visual)->update_state) {
		GGIMESADPRINT_CORE("update_state == NULL!\n");
		GGIMESADPRINT_CORE("Please check your config files!\n");
		ggiPanic("");
	}

	LIBGGI_MESAEXT(ggi_ctx->ggi_visual)->update_state(ggi_ctx);
}