/*
Copyright (C) 1997-2001 Id Software, Inc.

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/*
** GLW_IMP.C
**
** This file contains ALL Win32 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
**
*/

#include "win_local.h"
#include "win_glimp.h"
#include "win_wgl.h"

glwstate_t glw_state;

static cvar_t	*glw_driver;
static cvar_t	*glw_allow_software;
static cvar_t	*glw_drawbuffer;
static cvar_t	*glw_bitdepth;
static cvar_t	*glw_stereo;
static cvar_t	*vid_flip_on_switch;
static cvar_t	*vid_displayfrequency;

void GLimp_FatalShutdown( void ) {
	if( glw_state.flags & QVF_GAMMARAMP_ENABLED ) {
		if( glw_state.hDC ) {
			SetDeviceGammaRamp( glw_state.hDC, glw_state.originalGammaRamp );
		}
		glw_state.flags &= ~QVF_GAMMARAMP_ENABLED;
	}

	if( glw_state.hWnd ) {
		DestroyWindow( glw_state.hWnd );
		glw_state.hWnd = NULL;
	}

	if( glw_state.flags & QVF_FULLSCREEN_ENABLED ) {
		ChangeDisplaySettings( 0, 0 );
		glw_state.flags &= ~QVF_FULLSCREEN_ENABLED;
	}
}

/*
** 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.
**
*/
static void GLimp_Shutdown( void ) {
	Com_Printf( "Shutting down OpenGL subsystem...\n" );

	if( glw_state.flags & QVF_GAMMARAMP_ENABLED ) {
		if( glw_state.hDC ) {
			Com_Printf( "...restoring gamma settings: " );
			if( !SetDeviceGammaRamp( glw_state.hDC, glw_state.originalGammaRamp ) ) {
				Com_Printf( "failed\n" );
			} else {
				Com_Printf( "ok\n" );
			}
		}
		glw_state.flags &= ~QVF_GAMMARAMP_ENABLED;
	}

	if( qwglMakeCurrent ) {
		Com_Printf( "...making context not current: " );
		if( !qwglMakeCurrent( NULL, NULL ) ) {
			Com_Printf( "failed\n" );
		} else {
			Com_Printf( "ok\n" );
		}
	}

	if( glw_state.hGLRC ) {
		Com_Printf( "...deleting OpenGL context: " );
		if( !qwglDeleteContext( glw_state.hGLRC ) ) {
			Com_Printf( "failed\n" );
		} else {
			Com_Printf( "ok\n" );
		}
		glw_state.hGLRC = NULL;
	}

	if( glw_state.hDC ) {
		Com_Printf( "...releasing DC: " );
		if( !ReleaseDC( glw_state.hWnd, glw_state.hDC ) ) {
			Com_Printf( "failed\n" );
		} else {
			Com_Printf( "ok\n" );
		}
		glw_state.hDC = NULL;
	}

	if( glw_state.hWnd ) {
		Com_Printf( "...destroying window\n" );
		Win_DestroyWindow( glw_state.hWnd );
		glw_state.hWnd = NULL;
	}

	if( glw_state.flags & QVF_FULLSCREEN_ENABLED ) {
		ChangeDisplaySettings( 0, 0 );
		glw_state.flags &= ~QVF_FULLSCREEN_ENABLED;
	}

	WGL_Shutdown();
}


/*
** GLimp_Init
**
** This routine is responsible for initializing the OS specific portions
** of OpenGL.  Under Win32 this means dealing with the pixelformats and
** doing the wgl interface stuff.
*/
static qboolean GLimp_Init( void ) {
	OSVERSIONINFO	vinfo;

	// Com_Printf( "Initializing OpenGL subsystem\n" );

	Win_Init();

	vinfo.dwOSVersionInfoSize = sizeof( vinfo );

	if( !GetVersionEx( &vinfo ) ) {
		Com_Error( ERR_FATAL, "GLimp_Init: GetVersionEx failed" );
	}
	
	glw_state.allowdisplaydepthchange = qfalse;
	if( vinfo.dwMajorVersion > 4 ) {
		glw_state.allowdisplaydepthchange = qtrue;
	} else if ( vinfo.dwMajorVersion == 4 ) {
		if( vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) {
			glw_state.allowdisplaydepthchange = qtrue;
		} else if( vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) {
			if( LOWORD( vinfo.dwBuildNumber ) >= 1111 ) {
				glw_state.allowdisplaydepthchange = qtrue;
			}
		}
	}

	Cvar_Subsystem( CVAR_SYSTEM_VIDEO );

	glw_driver = Cvar_Get( "gl_driver", "opengl32", CVAR_ARCHIVE|CVAR_LATCHED );
	glw_drawbuffer = Cvar_Get( "gl_drawbuffer", "GL_BACK", 0 );
	glw_bitdepth = Cvar_Get( "gl_bitdepth", "0", CVAR_LATCHED );
	glw_allow_software = Cvar_Get( "gl_allow_software", "0", CVAR_LATCHED );
	glw_stereo = Cvar_Get( "cl_stereo", "0", CVAR_LATCHED );
	vid_flip_on_switch = Cvar_Get( "vid_flip_on_switch", "0", 0 );
	vid_displayfrequency = Cvar_Get( "vid_displayfrequency", "0", CVAR_LATCHED );

	Cvar_Subsystem( CVAR_SYSTEM_GENERIC );

	return qtrue;
}

static qboolean GLimp_VerifyDriver( void ) {
	char buffer[MAX_STRING_CHARS];

	Q_strncpyz( buffer, qglGetString( GL_RENDERER ), sizeof( buffer ) );
	Q_strlwr( buffer );
	if( strcmp( buffer, "gdi generic" ) == 0 )
		if( !( glw_state.flags & QVF_ACCELERATED ) )
			return qfalse;
	return qtrue;
}

static qboolean GLimp_InitGL( int width, int height, qboolean fullscreen ) {
    PIXELFORMATDESCRIPTOR pfd = {
		sizeof( PIXELFORMATDESCRIPTOR ),	// size of this pfd
		1,								// version number
		PFD_DRAW_TO_WINDOW |			// support window
		PFD_SUPPORT_OPENGL |			// support OpenGL
		PFD_DOUBLEBUFFER,				// double buffered
		PFD_TYPE_RGBA,					// RGBA type
		24,								// 24-bit color depth
		0, 0, 0, 0, 0, 0,				// color bits ignored
		0,								// no alpha buffer
		0,								// shift bit ignored
		0,								// no accumulation buffer
		0, 0, 0, 0, 					// accum bits ignored
		32,								// 32-bit z-buffer	
		0,								// no stencil buffer
		0,								// no auxiliary buffer
		PFD_MAIN_PLANE,					// main layer
		0,								// reserved
		0, 0, 0							// layer masks ignored
    };
    int  pixelformat;
	DWORD dwLastError = 0;

	glw_state.hWnd = Win_CreateWindow( width, height, fullscreen );
    glw_state.flags = 0;

	/*
	** set PFD_STEREO if necessary
	*/
	if( glw_stereo->integer ) {
		Com_Printf( "...attempting to use stereo\n" );
		pfd.dwFlags |= PFD_STEREO;
		glw_state.flags |= QVF_STEREO_ENABLED;
	}

	/*
	** figure out if we're running on a minidriver or not
	*/
	if( !strstr( glw_driver->string, "opengl32" ) ) {
		Com_Printf( "...running a minidriver: %s\n", glw_driver->string );
		glw_state.flags |= QVF_MINIDRIVER;
	}

	/*
	** Load OpenGL library
	*/
	Com_Printf( "...initializing WGL: " );
	if( !WGL_Init( glw_driver->string ) ) {
		dwLastError = GetLastError();
		Com_Printf( "failed\n" );
		goto fail;
	}
	Com_Printf( "ok\n" );

	/*
	** Get a DC for the specified window
	*/
	Com_Printf( "...getting DC: " );
    if( ( glw_state.hDC = GetDC( glw_state.hWnd ) ) == NULL ) {
		dwLastError = GetLastError();
		Com_Printf( "failed\n" );
		goto fail;
	}
	Com_Printf( "ok\n" );

	Com_Printf( "...setting pixel format: " );
	if( glw_state.flags & QVF_MINIDRIVER ) {
		if ( !( pixelformat = qwglChoosePixelFormat( glw_state.hDC, &pfd ) ) ) {
			dwLastError = GetLastError();
			Com_Printf( "failed\n" );
			goto fail;
		}

		if( qwglSetPixelFormat( glw_state.hDC, pixelformat, &pfd ) == FALSE ) {
			dwLastError = GetLastError();
			Com_Printf( "failed\n" );
			goto fail;
		}

		qwglDescribePixelFormat( glw_state.hDC, pixelformat,
                sizeof( pfd ), &pfd );
	} else {
		if( ( pixelformat = ChoosePixelFormat( glw_state.hDC, &pfd ) ) == 0 ) {
			dwLastError = GetLastError();
			Com_Printf( "failed\n" );
			goto fail;
		}

		if( SetPixelFormat( glw_state.hDC, pixelformat, &pfd ) == FALSE ) {
			dwLastError = GetLastError();
			Com_Printf( "failed\n" );
			goto fail;
		}

		DescribePixelFormat( glw_state.hDC, pixelformat, sizeof( pfd ), &pfd );

		if( ( pfd.dwFlags & PFD_GENERIC_ACCELERATED ) ||
                glw_allow_software->integer )
        {
			glw_state.flags |= QVF_ACCELERATED;
		}
	}
	Com_Printf( "ok\n" );

	/*
	** report if stereo is desired but unavailable
	*/
	if( !( pfd.dwFlags & PFD_STEREO ) &&
            ( glw_state.flags & QVF_STEREO_ENABLED ) )
    {
		Com_Printf( "...failed to select stereo pixel format\n" );
		Cvar_SetInteger( "cl_stereo", 0 );
		glw_state.flags &= ~QVF_STEREO_ENABLED;
	}

	/*
	** startup the OpenGL subsystem by creating a context and making
	** it current
	*/
	Com_Printf( "...creating OpenGL context: " );
	if( ( glw_state.hGLRC = qwglCreateContext( glw_state.hDC ) ) == NULL ) {
		dwLastError = GetLastError();
		Com_Printf( "failed\n" );
		goto fail;
	}
	Com_Printf( "ok\n" );

	Com_Printf( "...making context current: " );
    if( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) {
		dwLastError = GetLastError();
		Com_Printf( "failed\n" );
		goto fail;
	}
	Com_Printf( "ok\n" );

	if( !GLimp_VerifyDriver() ) {
		Com_Printf( "No hardware acceleration detected\n" );
		goto fail;
	}

	/*
	** print out PFD specifics 
	*/
	Com_Printf( "GL_VENDOR: %s\n", qglGetString( GL_VENDOR ) );
	Com_Printf( "GL_RENDERER: %s\n", qglGetString( GL_RENDERER ) );
	Com_Printf( "GL_PFD: color(%d-bits: %d,%d,%d,%d) Z(%d-bit) stencil(%d-bit)\n",
		pfd.cColorBits, pfd.cRedBits, pfd.cGreenBits, pfd.cBlueBits,
		pfd.cAlphaBits, pfd.cDepthBits, pfd.cStencilBits );


	if( GetDeviceGammaRamp( glw_state.hDC, glw_state.originalGammaRamp ) ) {
		glw_state.flags |= QVF_GAMMARAMP_ENABLED;
		memcpy( glw_state.customGammaRamp, glw_state.originalGammaRamp,
			sizeof( glw_state.customGammaRamp ) );
	}

	return qtrue;

fail:
    if( dwLastError ) {
    	Com_Printf( "GetLastError() = 0x%lx\n", dwLastError );
    }

	if( glw_state.hWnd ) {
		Win_DestroyWindow( glw_state.hWnd );
		glw_state.hWnd = NULL;
	}

	if( glw_state.hGLRC && qwglDeleteContext ) {
		qwglDeleteContext( glw_state.hGLRC );
		glw_state.hGLRC = NULL;
	}

	if( glw_state.hDC ) {
		ReleaseDC( glw_state.hWnd, glw_state.hDC );
		glw_state.hDC = NULL;
	}

	WGL_Shutdown();

	return qfalse;
}

/*
** GLimp_SetMode
*/

// get the list of acceptable video modes
#include "vid_modes.h"

static vidSetModeResult_t GLimp_SetMode( int *pwidth, int *pheight,
        int mode, qboolean fullscreen )
{
	int width, height;
	const char *win_fs[] = { "W", "FS" };
	DEVMODE dm;
    qboolean success;

	Com_Printf( "Initializing OpenGL display\n" );

	if( mode == -1 ) {
		Com_Printf( "...setting custom mode: " );
		width = *pwidth;
		height = *pheight;
	} else {
		Com_Printf( "...setting mode %d: ", mode );
		if( !VID_GetModeInfo( &width, &height, mode ) ) {
			Com_Printf( "invalid mode\n" );
			return SETMODE_ERROR;
		}
		*pwidth = width;
		*pheight = height;
	}

	Com_Printf( "%d %d %s\n", width, height, win_fs[fullscreen] );

	// destroy the existing window
	if( glw_state.hWnd ) {
		GLimp_Shutdown();
	}

	if( !fullscreen ) {
		Com_Printf( "...setting windowed mode\n" );
        goto windowed;
	}

// do a CDS if needed
	Com_Printf( "...attempting fullscreen\n" );

	if( glw_bitdepth->integer && !glw_state.allowdisplaydepthchange ) {
		Com_Printf( "gl_bitdepth requires Win95 OSR2.x or WinNT 4.x\n" );
		Cvar_SetInteger( "gl_bitdepth", 0 );
	}

	memset( &dm, 0, sizeof( dm ) );

	dm.dmSize = sizeof( dm );

	dm.dmPelsWidth  = width;
	dm.dmPelsHeight = height;
	dm.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT;

	if( vid_displayfrequency->integer ) {
		dm.dmDisplayFrequency = vid_displayfrequency->integer;
		dm.dmFields |= DM_DISPLAYFREQUENCY;
		Com_Printf( "...using display frequency of %d\n",
			vid_displayfrequency->integer );
	}

	if( glw_bitdepth->integer ) {
		dm.dmBitsPerPel = glw_bitdepth->integer;
		dm.dmFields |= DM_BITSPERPEL;
		Com_Printf( "...using gl_bitdepth of %d\n",
			glw_bitdepth->integer );
	} else {
		HDC hdc;
		int bitspixel;
		
		hdc = GetDC( NULL );
		bitspixel = GetDeviceCaps( hdc, BITSPIXEL );
		ReleaseDC( NULL, hdc );

		Com_Printf( "...using desktop display depth of %d\n", bitspixel );		
	}

	Com_Printf( "...calling CDS: " );
	if( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) == DISP_CHANGE_SUCCESSFUL ) {
		Com_Printf( "ok\n" );

        success = GLimp_InitGL( width, height, qtrue );
        if( !success && ( glw_state.flags & QVF_MINIDRIVER ) ) {
            Com_Printf( "...attempting to load opengl32\n" );
            Cvar_Set( "gl_driver","opengl32" );
            success = GLimp_InitGL( width, height, qtrue );
        }
		if( !success ) {
			return SETMODE_ERROR;
		}

		glw_state.flags |= QVF_FULLSCREEN_ENABLED;
		glw_state.fullscreenDM = dm;
		return SETMODE_FULLSCREEN;
	}
	Com_Printf( "failed\n" );

	dm.dmPelsWidth = width * 2;
	dm.dmPelsHeight = height;
	dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;

	if( vid_displayfrequency->integer ) {
		dm.dmDisplayFrequency = vid_displayfrequency->integer;
		dm.dmFields |= DM_DISPLAYFREQUENCY;
	}

	if( glw_bitdepth->integer ) {
		dm.dmBitsPerPel = glw_bitdepth->integer;
		dm.dmFields |= DM_BITSPERPEL;
	}

	Com_Printf( "...calling CDS assuming dual monitors: " );
	if( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) == DISP_CHANGE_SUCCESSFUL ) {
		Com_Printf( "ok\n" );

        success = GLimp_InitGL( width, height, qtrue );
        if( !success && ( glw_state.flags & QVF_MINIDRIVER ) ) {
            Com_Printf( "...attempting to load opengl32\n" );
            Cvar_Set( "gl_driver","opengl32" );
            success = GLimp_InitGL( width, height, qtrue );
        }
		if( !success ) {
			return SETMODE_ERROR;
		}

		glw_state.flags |= QVF_FULLSCREEN_ENABLED;
		glw_state.fullscreenDM = dm;
		return SETMODE_FULLSCREEN;
	}
	Com_Printf( "failed\n" );

	Com_Printf( "...falling back to windowed mode\n" );

windowed:
    ChangeDisplaySettings( NULL, 0 );
    
    success = GLimp_InitGL( width, height, qfalse );
    if( !success && ( glw_state.flags & QVF_MINIDRIVER ) ) {
        Com_Printf( "...attempting to load opengl32\n" );
        Cvar_Set( "gl_driver","opengl32" );
        success = GLimp_InitGL( width, height, qfalse );
    }

    return success ? SETMODE_WINDOWED : SETMODE_ERROR;
}

/*
==============
GLimp_UpdateGamma
==============
*/
static void GLimp_UpdateGamma( const byte *table ) {
	WORD v;
	int i;

	if( !( glw_state.flags & QVF_GAMMARAMP_ENABLED ) ) {
		return;
	}
	
	for( i = 0; i < 256; i++ ) {
		v = table[i] << 8;
		glw_state.customGammaRamp[0][i] = v;
		glw_state.customGammaRamp[1][i] = v;
		glw_state.customGammaRamp[2][i] = v;
	}

	SetDeviceGammaRamp( glw_state.hDC, glw_state.customGammaRamp );
	
}

/*
** GLimp_BeginFrame
*/
static void GLimp_BeginFrame( float camera_separation ) {
	//qglDrawBuffer( GL_BACK );
}

/*
** 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.
*/

static void GLimp_EndFrame( void ) {
	/*if( Q_stricmp( glw_drawbuffer->string, "GL_BACK" ) == 0 ) {
		if( !qwglSwapBuffers( glw_state.hDC ) )
			Com_Error( ERR_FATAL, "GLimp_EndFrame: wglSwapBuffers failed" );
	}
	*/
		if( !qwglSwapBuffers( glw_state.hDC ) )
			Com_Error( ERR_FATAL, "GLimp_EndFrame: wglSwapBuffers failed" );
}

/*
============
GLimp_GetFlags
============
*/
static vidFlags_t GLimp_GetFlags( void ) {
	return glw_state.flags;
}

void GLimp_AppActivate( qboolean active ) {
	if( !glw_state.hDC ) {
		return;
	}
	if( glw_state.flags & QVF_GAMMARAMP_ENABLED ) {
		if( active ) {
			SetDeviceGammaRamp( glw_state.hDC, glw_state.customGammaRamp );
		} else {
			SetDeviceGammaRamp( glw_state.hDC, glw_state.originalGammaRamp );
		}
	}

	if( !( glw_state.flags & QVF_FULLSCREEN_ENABLED ) ) {
		return;
	}
	if( !vid_flip_on_switch->integer ) {
		return;
	}
	if( active ) {
		ChangeDisplaySettings( &glw_state.fullscreenDM, CDS_FULLSCREEN );
	} else {
		ChangeDisplaySettings( NULL, 0 );
	}
	
}

/*
@@@@@@@@@@@@
GLimp_FillAPI
@@@@@@@@@@@@
*/
void Video_FillGLAPI( vidGLAPI_t *api ) {
	api->Init = GLimp_Init;
	api->Shutdown = GLimp_Shutdown;
	api->UpdateGamma = GLimp_UpdateGamma;
	api->GetFlags = GLimp_GetFlags;
	api->SetMode = GLimp_SetMode;
	api->GetProcAddr = WGL_GetProcAddress;
	api->BeginFrame = GLimp_BeginFrame;
	api->EndFrame = GLimp_EndFrame;
}

