/*
 * sdl_glimp.c - SDL window creation, input handling
 *
 * Copyright (C) 1999-2005  Id Software, Inc.
 * Copyright (C) 2005       Damien Thebault <damien.thebault@laposte.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
** GLW_IMP.C
**
** This file contains ALL Linux specific stuff having to do with the
** OpenGL refresh.  When a port is being made the following functions
** must be implemented by the port:
**
** GLimp_EndFrame
** GLimp_Init
** GLimp_Shutdown
** GLimp_SwitchFullscreen
** GLimp_SetGamma
**
*/

#include "../renderer/tr_local.h"
#include "../client/client.h"
#include "sdl_local.h"

#include "sdl_glw.h"

#include "SDL.h"
#include "SDL_loadso.h" /* SDL_LoadFunction */

typedef enum
{
  RSERR_OK,

  RSERR_INVALID_FULLSCREEN,
  RSERR_INVALID_MODE,

  RSERR_UNKNOWN
} rserr_t;

glwstate_t glw_state;

cvar_t *in_nograb; // this is strictly for developers

cvar_t *in_joythreshold    = NULL;

cvar_t  *r_allowSoftwareGL;   // don't abort out if the pixelformat claims software
cvar_t  *r_previousglDriver;

qboolean vidmode_ext = qfalse;

/*
* Find the first occurrence of find in s.
*/
// bk001130 - from cvs1.17 (mkv), const
// bk001130 - made first argument const
static const char *Q_stristr( const char *s, const char *find)
{
	register char c, sc;
	register size_t len;
	
	if ((c = *find++) != 0)
	{
		if (c >= 'a' && c <= 'z')
		{
			c -= ('a' - 'A');
		}
		len = strlen(find);
		do
		{
			do
			{
				if ((sc = *s++) == 0)
					return NULL;
				if (sc >= 'a' && sc <= 'z')
				{
					sc -= ('a' - 'A');
				}
			} while (sc != c);
		} while (Q_stricmpn(s, find, len) != 0);
		s--;
	}
	return s;
}

/*****************************************************************************
** KEYBOARD
** Some special keys are mapped directly from SDL KeySym to Quake ones
** The others are translated from the keysym and the mods by SDL.
** you can turn on some debugging and verbose of the keyboard code with #define KBD_DBG
******************************************************************************/

//#define KBD_DBG

char * SDLateKey( int *qkey, SDL_KeyboardEvent sdlke )
{
	int key;
	static char buf[2];
	
	switch( sdlke.keysym.sym )
	{
		case SDLK_PAGEUP:		key = K_PGUP;		break;
		case SDLK_PAGEDOWN:		key = K_PGDN;		break;
		case SDLK_HOME: 		key = K_HOME;		break;
		case SDLK_END:	 		key = K_END;		break;
		
		case SDLK_UP:			key = K_UPARROW;	break;
		case SDLK_DOWN:			key = K_DOWNARROW;	break;
		case SDLK_LEFT:			key = K_LEFTARROW;	break;
		case SDLK_RIGHT:		key = K_RIGHTARROW;	break;
		
		case SDLK_ESCAPE:		key = K_ESCAPE;		break;
		case SDLK_RETURN:		key = K_ENTER;		break;
		case SDLK_KP_ENTER:		key = K_KP_ENTER;	break;
		case SDLK_TAB:			key = K_TAB;		break;
		
		case SDLK_F1:			key = K_F1;			break;
		case SDLK_F2:			key = K_F2;			break;
		case SDLK_F3:			key = K_F3;			break;
		case SDLK_F4:			key = K_F4;			break;
		case SDLK_F5:			key = K_F5;			break;
		case SDLK_F6:			key = K_F6;			break;
		case SDLK_F7:			key = K_F7;			break;
		case SDLK_F8:			key = K_F8;			break;
		case SDLK_F9:			key = K_F9;			break;
		case SDLK_F10:			key = K_F10;		break;
		case SDLK_F11:			key = K_F11;		break;
		case SDLK_F12:			key = K_F12;		break;
		
		case SDLK_BACKSPACE:	key = 8;			break;
		case SDLK_DELETE:		key = K_DEL;		break;
		case SDLK_PAUSE:		key = K_PAUSE;		break;
		
		case SDLK_RSHIFT:
		case SDLK_LSHIFT:		key = K_SHIFT;		break;
		
		case SDLK_RCTRL:
		case SDLK_LCTRL:		key = K_CTRL;		break;
		
		case SDLK_RALT:
		case SDLK_LALT:
		case SDLK_RMETA:
		case SDLK_LMETA:
		case SDLK_LSUPER:
		case SDLK_RSUPER:		key = K_ALT;		break;
		
		case SDLK_INSERT:		key = K_INS;		break;
		
		case SDLK_SPACE:		key = K_SPACE;		break;
		
		//French keyboard '²'
		case 178:			key = '~';				break;
		
		default:
			if( sdlke.keysym.unicode < 256 )
			{
				/*
					If the translated key is between 0 and 256, it's ok
				*/
				key = sdlke.keysym.unicode;
			}
			else
			{
				/*
					The key don't fit in the Quake3 keyboard array
				*/
				key = 0;
			}
		break;
	}
	
#ifdef KBD_DBG
	printf( "Key '%c' (%d) -> '%c' (%d)\n", sdlke.keysym.sym, sdlke.keysym.sym, key, key );
#endif
	
	*qkey = key;
	
	if( sdlke.type == SDL_KEYDOWN )
	{
		buf[0] = key;
		buf[1] = '\0';
	}
	else
	{
		buf[0] = '\0';
	}
	
	return buf;
}

static void HandleEvents(void)
{
	int key;
	char *p;
	SDL_Event sdlevent;
	
	while( SDL_PollEvent( &sdlevent ) )
	{
		switch( sdlevent.type )
		{
			case SDL_KEYDOWN:
			case SDL_KEYUP:
				p = SDLateKey( &key, sdlevent.key );
				Sys_QueEvent( 0, SE_KEY, key, sdlevent.key.state, 0, NULL );
				while (*p)
					Sys_QueEvent( 0, SE_CHAR, *p++, 0, 0, NULL );
			break;
				
			case SDL_MOUSEMOTION:
				Sys_QueEvent( 0, SE_MOUSE, sdlevent.motion.xrel, sdlevent.motion.yrel, 0, NULL );
			break;
			
			case SDL_MOUSEBUTTONDOWN:
			case SDL_MOUSEBUTTONUP:
				Sys_QueEvent( 0, SE_KEY, K_MOUSE1 + sdlevent.button.button - 1, sdlevent.button.state, 0, NULL );
			break;
			
			case SDL_JOYAXISMOTION:
			case SDL_JOYBUTTONDOWN:
			case SDL_JOYBUTTONUP:
				IN_JoyEvent( sdlevent );
			break;
		}
	}
}

/*****************************************************************************/

/*
** GLimp_SetGamma
**
** This routine should only be called if glConfig.deviceSupportsGamma is TRUE
*/
void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] )
{
	float g = Cvar_Get("r_gamma", "1.0", 0)->value;
	SDL_SetGamma( g,g,g );
}

/*
** GLimp_Shutdown
**
** This routine does all OS specific shutdown procedures for the OpenGL
** subsystem.  Under OpenGL this means NULLing out the current DC and
** HGLRC, deleting the rendering context, and releasing the DC acquired
** for the window.  The state structure is also nulled out.
**
*/
void GLimp_Shutdown( void )
{
	
	if (!in_nograb->value)
	{
		SDL_WM_GrabInput( SDL_GRAB_OFF );
		SDL_ShowCursor( SDL_ENABLE );
	}
	
	memset( &glConfig, 0, sizeof( glConfig ) );
	memset( &glState, 0, sizeof( glState ) );
	
	SDL_Quit();
	QGL_Shutdown();
}

/*
** GLimp_LogComment
*/
void GLimp_LogComment( char *comment ) 
{
	if ( glw_state.log_fp )
	{
		fprintf( glw_state.log_fp, "%s", comment );
	}
}

/*
** GLW_StartDriverAndSetMode
*/
// bk001204 - prototype needed
int GLW_SetMode( const char *drivername, int mode, qboolean fullscreen );
static qboolean GLW_StartDriverAndSetMode( const char *drivername, 
                                           int mode, 
                                           qboolean fullscreen )
{
	rserr_t err;
	
	// don't ever bother going into fullscreen with a voodoo card
	#if 1	// JDC: I reenabled this
	if ( Q_stristr( drivername, "Voodoo" ) )
	{
		ri.Cvar_Set( "r_fullscreen", "0" );
		r_fullscreen->modified = qfalse;
		fullscreen = qfalse;
	}
	#endif
	
		if (fullscreen && in_nograb->value)
		{
			ri.Printf( PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n");
		ri.Cvar_Set( "r_fullscreen", "0" );
		r_fullscreen->modified = qfalse;
		fullscreen = qfalse;		
		}
	
	err = GLW_SetMode( drivername, mode, fullscreen );
	
	switch ( err )
	{
	case RSERR_INVALID_FULLSCREEN:
		ri.Printf( PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n" );
		return qfalse;
	case RSERR_INVALID_MODE:
		ri.Printf( PRINT_ALL, "...WARNING: could not set the given mode (%d)\n", mode );
		return qfalse;
	default:
		break;
	}
	return qtrue;
}

/*
** GLW_SetMode
*/
int GLW_SetMode( const char *drivername, int mode, qboolean fullscreen )
{
	const char*   glstring;
	int colorbits,depthbits,stencilbits;
	int r=0,g=0,b=0,depth=0,stencil=0,doublebuffer=-1; // status
	
	ri.Printf( PRINT_ALL, "Initializing OpenGL display\n");
	
	ri.Printf( PRINT_ALL, "SDL : initialization... ");
	
	if( SDL_Init( SDL_INIT_VIDEO ) != 0 )
	{
		fprintf( stderr, "Error : %s\n", SDL_GetError() );
		return RSERR_INVALID_MODE;
	}
	else
	{
		ri.Printf( PRINT_ALL, "OK\n" );
	}
	
	ri.Printf( PRINT_ALL, "SDL : get video info... ");
	if( SDL_GetVideoInfo() == NULL )
	{
		fprintf( stderr, "Error : %s\n", SDL_GetError() );
		return RSERR_INVALID_MODE;
	}
	else
	{
		ri.Printf( PRINT_ALL, "OK\n" );
	}
	
	if ( !r_depthbits->value )
		depthbits = 24;
	else
		depthbits = r_depthbits->value;
	
	stencilbits = r_stencilbits->value;
	
	if (!r_colorbits->value)
		colorbits = 24;
	else
		colorbits = r_colorbits->value;
	
	if ( !Q_stricmp( r_glDriver->string, _3DFX_DRIVER_NAME ) )
		colorbits = 16;
	
	if( colorbits == 24 || colorbits == 32 )
	{
		SDL_GL_SetAttribute( SDL_GL_RED_SIZE,	8 );
		SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE,	8 );
		SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE,	8 );
		r=g=b=8;
	}
	else if( colorbits == 16 )
	{
		SDL_GL_SetAttribute( SDL_GL_RED_SIZE,	4 );
		SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE,	4 );
		SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE,	4 );
		r=g=b=4;
	}
	
	SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 	depthbits ); // depth buffer
	SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE,	stencilbits ); // stencil buffer
	
	ri.Printf( PRINT_ALL, "getting info about mode %d... ", mode );
	if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, mode ) )
	{
		ri.Printf( PRINT_ALL, "invalid mode\n" );
		return RSERR_INVALID_MODE;
	}
	else
	{
		ri.Printf( PRINT_ALL, "%dx%d\n", glConfig.vidWidth, glConfig.vidHeight);
	}
	
	ri.Printf( PRINT_ALL, "SDL : setting mode %d (%dx%d) %s... ", mode, glConfig.vidWidth, glConfig.vidHeight, fullscreen?"fullscreen":"windowed" );
	if( SDL_SetVideoMode( glConfig.vidWidth, glConfig.vidHeight, 32, SDL_OPENGL | (fullscreen?SDL_FULLSCREEN:0) ) == 0 )
	{
		ri.Printf( PRINT_ALL, "Failed\n" );
		ri.Printf( PRINT_ALL, "Trying with minimal settings : 640x480 windowed... " );
		
		SDL_GL_SetAttribute( SDL_GL_RED_SIZE,		2 );
		SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE,		2 );
		SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE,		2 );
		r=g=b=2;
		
		SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 	8 );
		depthbits = 8;
		SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE,	0 );
		stencilbits = 0;
		
		R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, 3 );
		
		if( SDL_SetVideoMode( glConfig.vidWidth, glConfig.vidHeight, 32, SDL_OPENGL ) == 0 )
		{
			fprintf( stderr, "Error : \n%s\n", SDL_GetError() );
			SDL_Quit();
			return RSERR_INVALID_MODE;
		}
		else
		{
			ri.Cvar_Set( "r_colorbits", "8" );
			ri.Cvar_Set( "r_depthbits", "8" );
			ri.Cvar_Set( "r_stencilbits", "0" );
			ri.Cvar_Get( "r_mode", "", CVAR_ROM ); // disable latched value
			ri.Cvar_Set( "r_mode", "3" );
			ri.Cvar_Set( "r_fullscreen", "0" );
			ri.Printf( PRINT_ALL, "OK\n" );
		}
	}
	else
	{
		ri.Printf( PRINT_ALL, "OK\n" );
	}
	
	ri.Printf( PRINT_ALL, "Configuration : Using %d/%d/%d Color bits, %d depth, %d stencil display.\n", r, g, b, depthbits, stencilbits);
	ri.Printf( PRINT_ALL, "SDL/GL status :\n" );
	
	if( SDL_GL_GetAttribute( SDL_GL_RED_SIZE, &r ) != 0 )
		ri.Printf( PRINT_ALL, "Error getting red size : %s\n", SDL_GetError() );
	if( SDL_GL_GetAttribute( SDL_GL_GREEN_SIZE, &g ) != 0 )
		ri.Printf( PRINT_ALL, "Error getting green size : %s\n", SDL_GetError() );
	if( SDL_GL_GetAttribute( SDL_GL_BLUE_SIZE, &b ) != 0 )
		ri.Printf( PRINT_ALL, "Error getting blue size : %s\n", SDL_GetError() );
	
	if( r==g && r==b && r!=0)
		ri.Printf( PRINT_ALL, "- color depth : %d (%d/%d/%d)\n", r+g+b, r, g, b );
	else
		ri.Printf( PRINT_ALL, "- color depth : unknown (r=%d, g=%d, b=%d)\n", r, g, b );
	
	if( SDL_GL_GetAttribute( SDL_GL_DEPTH_SIZE, &depth ) == 0)
		ri.Printf( PRINT_ALL, "- depth buffer size : %d\n", depth );
	else
		ri.Printf( PRINT_ALL, "- depth buffer size : Error : %s\n", SDL_GetError() );
	
	if( SDL_GL_GetAttribute( SDL_GL_STENCIL_SIZE, &stencil ) == 0)
		ri.Printf( PRINT_ALL, "- stencil buffer size : %d\n", stencil );
	else
		ri.Printf( PRINT_ALL, "- stencil buffer size : Error : %s\n", SDL_GetError() );
	
	if( SDL_GL_GetAttribute( SDL_GL_DOUBLEBUFFER, &doublebuffer ) == 0)
		ri.Printf( PRINT_ALL, "- double buffering : %s\n", doublebuffer==1? "enabled" : "disabled" );
	else
		ri.Printf( PRINT_ALL, "- double buffering : Error : %s\n", SDL_GetError() );
	
	glConfig.colorBits = r+g+b;
	glConfig.depthBits = depth;
	glConfig.stencilBits = stencil;
	
	glstring = qglGetString (GL_RENDERER);
	if ( !Q_stricmp( glstring, "Mesa X11") || !Q_stricmp( glstring, "Mesa GLX Indirect") )
	{
		if ( !r_allowSoftwareGL->integer )
		{
			ri.Printf( PRINT_ALL, "\nGL_RENDERER: %s\n", glstring );
			ri.Printf( PRINT_ALL, "***********************************************************\n" );
			ri.Printf( PRINT_ALL, " You are using software Mesa (no hardware acceleration)!   \n" );
			ri.Printf( PRINT_ALL, " Driver DLL used: %s\n", drivername ); 
			ri.Printf( PRINT_ALL, " If this is intentional, add\n" );
			ri.Printf( PRINT_ALL, "       \"+set r_allowSoftwareGL 1\"\n" );
			ri.Printf( PRINT_ALL, " to the command line when starting the game.\n" );
			ri.Printf( PRINT_ALL, "***********************************************************\n");
			GLimp_Shutdown( );
			return RSERR_INVALID_MODE;
		}
		else
		{
			ri.Printf( PRINT_ALL, "...using software Mesa (r_allowSoftwareGL==1).\n" );
		}
	}
	
	if (!in_nograb->value)
	{
		SDL_ShowCursor( SDL_DISABLE );
		SDL_WM_GrabInput( SDL_GRAB_ON );
	}
	
	/*
		KeyRepeat enabled for console and textboxes
		This don't change anytihng for gameplay
	*/
	SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL );
	/*
		Unicode enabled for console and textboxes
		This is good because any keyboard is supported
	*/
	SDL_EnableUNICODE( 1 );
	/*
		Window title
	*/
	SDL_WM_SetCaption( "Quake III arena", "quake3" );
	
	return RSERR_OK;
}

/*
** GLW_InitExtensions
*/
static void GLW_InitExtensions( void )
{
  if ( !r_allowExtensions->integer )
  {
    ri.Printf( PRINT_ALL, "*** IGNORING OPENGL EXTENSIONS ***\n" );
    return;
  }

  ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" );

  // GL_S3_s3tc
  if ( Q_stristr( glConfig.extensions_string, "GL_S3_s3tc" ) )
  {
    if ( r_ext_compressed_textures->value )
    {
      glConfig.textureCompression = TC_S3TC;
      ri.Printf( PRINT_ALL, "...using GL_S3_s3tc\n" );
    } else
    {
      glConfig.textureCompression = TC_NONE;
      ri.Printf( PRINT_ALL, "...ignoring GL_S3_s3tc\n" );
    }
  } else
  {
    glConfig.textureCompression = TC_NONE;
    ri.Printf( PRINT_ALL, "...GL_S3_s3tc not found\n" );
  }

  // GL_EXT_texture_env_add
  glConfig.textureEnvAddAvailable = qfalse;
  if ( Q_stristr( glConfig.extensions_string, "EXT_texture_env_add" ) )
  {
    if ( r_ext_texture_env_add->integer )
    {
      glConfig.textureEnvAddAvailable = qtrue;
      ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" );
    } else
    {
      glConfig.textureEnvAddAvailable = qfalse;
      ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" );
    }
  } else
  {
    ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" );
  }

  // GL_ARB_multitexture
  qglMultiTexCoord2fARB = NULL;
  qglActiveTextureARB = NULL;
  qglClientActiveTextureARB = NULL;
  if ( Q_stristr( glConfig.extensions_string, "GL_ARB_multitexture" ) )
  {
    if ( r_ext_multitexture->value )
    {
      qglMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) SDL_LoadFunction (glw_state.OpenGLLib, "glMultiTexCoord2fARB");
      qglActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) SDL_LoadFunction (glw_state.OpenGLLib, "glActiveTextureARB");
      qglClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) SDL_LoadFunction (glw_state.OpenGLLib, "glClientActiveTextureARB");

      if ( qglActiveTextureARB )
      {
        qglGetIntegerv( GL_MAX_ACTIVE_TEXTURES_ARB, &glConfig.maxActiveTextures );

        if ( glConfig.maxActiveTextures > 1 )
        {
          ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" );
        } else
        {
          qglMultiTexCoord2fARB = NULL;
          qglActiveTextureARB = NULL;
          qglClientActiveTextureARB = NULL;
          ri.Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n" );
        }
      }
    } else
    {
      ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" );
    }
  } else
  {
    ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" );
  }

  // GL_EXT_compiled_vertex_array
  if ( Q_stristr( glConfig.extensions_string, "GL_EXT_compiled_vertex_array" ) )
  {
    if ( r_ext_compiled_vertex_array->value )
    {
      ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" );
      qglLockArraysEXT = (void (APIENTRY *)(int, int)) SDL_LoadFunction (glw_state.OpenGLLib, "glLockArraysEXT");
      qglUnlockArraysEXT = (void (APIENTRY *)(void )) SDL_LoadFunction (glw_state.OpenGLLib, "glUnlockArraysEXT");
      if (!qglLockArraysEXT || !qglUnlockArraysEXT)
      {
        ri.Error (ERR_FATAL, "bad getprocaddress");
      }
    } else
    {
      ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" );
    }
  } else
  {
    ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" );
  }

}

static void GLW_InitGamma()
{
	// We have to try SDL_SetGamma...
	glConfig.deviceSupportsGamma = qtrue;
}

/*
** GLW_LoadOpenGL
**
** GLimp_win.c internal function that that attempts to load and use 
** a specific OpenGL DLL.
*/
static qboolean GLW_LoadOpenGL( const char *name )
{
  ri.Printf( PRINT_ALL, "...loading %s: ", name );

  // disable the 3Dfx splash screen and set gamma
  // we do this all the time, but it shouldn't hurt anything
  // on non-3Dfx stuff
  putenv("FX_GLIDE_NO_SPLASH=0");

  // Mesa VooDoo hacks
  putenv("MESA_GLX_FX=fullscreen\n");

  // load the QGL layer
  if ( QGL_Init( name ) )
  {
    // create the window and set up the context
    if ( !GLW_StartDriverAndSetMode( name, r_mode->integer, r_fullscreen->integer ) )
  {
    ri.Printf( PRINT_ALL, "failed\n" );
  QGL_Shutdown();
  return qfalse;
    }
  }
  return qtrue;
}

/*
** GLimp_Init
**
** This routine is responsible for initializing the OS specific portions
** of OpenGL.  
*/
void GLimp_Init( void )
{
  qboolean attemptedlibGL = qfalse;
  qboolean attempted3Dfx = qfalse;
  qboolean success = qfalse;
  char  buf[1024];
  cvar_t *lastValidRenderer = ri.Cvar_Get( "r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE );

  // guarded, as this is only relevant to SMP renderer thread
#ifdef SMP
	// TODO : what to do with SDL/OpenGL in SMP mode?
#endif

  r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH );

  r_previousglDriver = ri.Cvar_Get( "r_previousglDriver", "", CVAR_ROM );

  InitSig();

  // Hack here so that if the UI 
  if ( *r_previousglDriver->string )
  {
    // The UI changed it on us, hack it back
    // This means the renderer can't be changed on the fly
    ri.Cvar_Set( "r_glDriver", r_previousglDriver->string );
  }
  
  //
  // load and initialize the specific OpenGL driver
  //
  if ( !GLW_LoadOpenGL( r_glDriver->string ) )
  {
    if ( !Q_stricmp( r_glDriver->string, OPENGL_DRIVER_NAME ) )
    {
      attemptedlibGL = qtrue;
    } else if ( !Q_stricmp( r_glDriver->string, _3DFX_DRIVER_NAME ) )
    {
      attempted3Dfx = qtrue;
    }

    #if 0
    // TTimo
    // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=455
    // old legacy load code, was confusing people who had a bad OpenGL setup
    if ( !attempted3Dfx && !success )
    {
      attempted3Dfx = qtrue;
      if ( GLW_LoadOpenGL( _3DFX_DRIVER_NAME ) )
      {
        ri.Cvar_Set( "r_glDriver", _3DFX_DRIVER_NAME );
        r_glDriver->modified = qfalse;
        success = qtrue;
      }
    }
    #endif

    // try ICD before trying 3Dfx standalone driver
    if ( !attemptedlibGL && !success )
    {
      attemptedlibGL = qtrue;
      if ( GLW_LoadOpenGL( OPENGL_DRIVER_NAME ) )
      {
        ri.Cvar_Set( "r_glDriver", OPENGL_DRIVER_NAME );
        r_glDriver->modified = qfalse;
        success = qtrue;
      }
    }

    if (!success)
      ri.Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem\n" );

  }

  // Save it in case the UI stomps it
  ri.Cvar_Set( "r_previousglDriver", r_glDriver->string );

  // This values force the UI to disable driver selection
  glConfig.driverType = GLDRV_ICD;
  glConfig.hardwareType = GLHW_GENERIC;

  // get our config strings
  Q_strncpyz( glConfig.vendor_string, qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) );
  Q_strncpyz( glConfig.renderer_string, qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) );
  if (*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n')
    glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0;
  Q_strncpyz( glConfig.version_string, qglGetString (GL_VERSION), sizeof( glConfig.version_string ) );
  Q_strncpyz( glConfig.extensions_string, qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) );

  //
  // chipset specific configuration
  //
  strcpy( buf, glConfig.renderer_string );
  strlwr( buf );

  //
  // NOTE: if changing cvars, do it within this block.  This allows them
  // to be overridden when testing driver fixes, etc. but only sets
  // them to their default state when the hardware is first installed/run.
  //
  if ( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) )
  {
    glConfig.hardwareType = GLHW_GENERIC;

    ri.Cvar_Set( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST" );

    // VOODOO GRAPHICS w/ 2MB
    if ( Q_stristr( buf, "voodoo graphics/1 tmu/2 mb" ) )
    {
      ri.Cvar_Set( "r_picmip", "2" );
      ri.Cvar_Get( "r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH );
    } else
    {
      ri.Cvar_Set( "r_picmip", "1" );

      if ( Q_stristr( buf, "rage 128" ) || Q_stristr( buf, "rage128" ) )
      {
        ri.Cvar_Set( "r_finish", "0" );
      }
      // Savage3D and Savage4 should always have trilinear enabled
      else if ( Q_stristr( buf, "savage3d" ) || Q_stristr( buf, "s3 savage4" ) )
      {
        ri.Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
      }
    }
  }

  //
  // this is where hardware specific workarounds that should be
  // detected/initialized every startup should go.
  //
  if ( Q_stristr( buf, "banshee" ) || Q_stristr( buf, "Voodoo_Graphics" ) )
  {
    glConfig.hardwareType = GLHW_3DFX_2D3D;
  } else if ( Q_stristr( buf, "rage pro" ) || Q_stristr( buf, "RagePro" ) )
  {
    glConfig.hardwareType = GLHW_RAGEPRO;
  } else if ( Q_stristr( buf, "permedia2" ) )
  {
    glConfig.hardwareType = GLHW_PERMEDIA2;
  } else if ( Q_stristr( buf, "riva 128" ) )
  {
    glConfig.hardwareType = GLHW_RIVA128;
  } else if ( Q_stristr( buf, "riva tnt " ) )
  {
  }

  ri.Cvar_Set( "r_lastValidRenderer", glConfig.renderer_string );

  // initialize extensions
  GLW_InitExtensions();
  GLW_InitGamma();

  InitSig(); // not clear why this is at begin & end of function

  return;
}


/*
** GLimp_EndFrame
** 
** Responsible for doing a swapbuffers and possibly for other stuff
** as yet to be determined.  Probably better not to make this a GLimp
** function and instead do a call to GLimp_SwapBuffers.
*/
void GLimp_EndFrame (void)
{
	// Just call GL_SwapBuffers()
	// This should work with single-buffer too
	SDL_GL_SwapBuffers();
	// check logging
	QGL_EnableLogging( (qboolean)r_logFile->integer ); // bk001205 - was ->value
}

// TODO : handle SMP

void GLimp_RenderThreadWrapper( void *stub ) {}
qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) {
	ri.Printf( PRINT_WARNING, "ERROR: SMP support was disabled at compile time\n");
  return qfalse;
}
void *GLimp_RendererSleep( void ) {
  return NULL;
}
void GLimp_FrontEndSleep( void ) {}
void GLimp_WakeRenderer( void *data ) {}

/*****************************************************************************/
/* Input                                                                     */
/*****************************************************************************/

void IN_Init(void) {
	Com_Printf ("\n------- Input Initialization -------\n");
	
	in_nograb = Cvar_Get ("in_nograb", "0", 0);
	
	in_joythreshold = Cvar_Get ("in_joythreshold", "0.15", CVAR_ARCHIVE);
	
	IN_StartupJoystick( );
	
	Com_Printf ("------------------------------------\n");
}

void IN_Frame (void) {}
void IN_Shutdown(void) { IN_ShutdownJoystick(); }

void Sys_SendKeyEvents (void) { HandleEvents(); }
