//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "platformWin32/platformGL.h"
#include "platformWin32/platformWin32.h"
#include "platform/platformAudio.h"
#include "platformWin32/winV2Video.h"
#include "platform/3Dfx.h"
#include "console/console.h"
#include "math/mPoint.h"
#include "platform/event.h"
#include "platform/gameInterface.h"
#include "console/consoleInternal.h"
#include "console/ast.h"
#include "core/fileStream.h"


static U8 sResCode;  // Used for initializing the resolution list only

static void execScript(const char *scriptFile)
{
	// execute the script
   FileStream str;

   if (!str.open(scriptFile, FileStream::Read))
      return;

   U32 size = str.getStreamSize();
   char *script = new char[size + 1];

   str.read(size, script);
   str.close();

   script[size] = 0;
   Con::executef(2, "eval", script);
   delete[] script;
}

//------------------------------------------------------------------------------
Voodoo2Device::Voodoo2Device()
{
   initDevice();
}


//------------------------------------------------------------------------------
void Voodoo2Device::initDevice()
{
   // Set the device name:
   mDeviceName = "Voodoo2";

   // Set some initial conditions:
   mResolutionList.clear();
	mFullScreenOnly = true;

   // Enumerate all available resolutions:
   Resolution newRes;
   newRes = Resolution( 640, 480, 16 );
   mResolutionList.push_back( newRes );

   if ( sResCode & 1 )
   {
      newRes = Resolution( 800, 600, 16 );
      mResolutionList.push_back( newRes );
   }

   if ( sResCode & 2 )
   {
      Con::printf( "SLI detected." );

      newRes = Resolution( 960, 720, 16 );
      mResolutionList.push_back( newRes );

      newRes = Resolution( 1024, 768, 16 );
      mResolutionList.push_back( newRes );
   }
}


//------------------------------------------------------------------------------
bool Voodoo2Device::activate( U32 width, U32 height, U32 /*bpp*/, bool /*fullScreen*/ )
{
   bool needResurrect = false;

   // If the rendering context exists, delete it:
   if ( winState.hGLRC )
   {
      Con::printf( "Killing the texture manager..." );
      Game->textureKill();
      needResurrect = true;

      Con::printf( "Making the rendering context not current..." );
      if ( !dwglMakeCurrent( NULL, NULL ) )
      {
         AssertFatal( false, "Voodoo2Device::activate\ndwglMakeCurrent( NULL, NULL ) failed!" );
         return false;
      }

      Con::printf( "Deleting the rendering context..." );
      if ( !dwglDeleteContext( winState.hGLRC ) )
      {
         AssertFatal( false, "Voodoo2Device::activate\ndwglDeleteContext failed!" );
         return false;
      }
      winState.hGLRC = NULL;
   }

   // If window already exists, kill it so we can start fresh:
   if ( winState.appWindow )
   {
      Con::printf( "Releasing the device context..." );
      ReleaseDC( winState.appWindow, winState.appDC );
      winState.appDC = NULL;

      Con::printf( "Destroying the window..." );
      DestroyWindow( winState.appWindow );
      winState.appWindow = NULL;
   }

   // If OpenGL library already loaded, shut it down and reload the 3Dfx standalone driver:
   if ( winState.hinstOpenGL )
      GL_Shutdown();

   GL_Init( "3dfxvgl", "glu32" );

   // This device only supports full-screen, so force it:
   smIsFullScreen = true;
   Con::setVariable( "$pref::Video::fullScreen", "true" );

   // Create the new window:
   Con::printf( "Creating a new full-screen window..." );
   winState.appWindow = CreateOpenGLWindow( width, height, true );
   if ( !winState.appWindow )
   {
      AssertFatal( false, "Voodoo2Device::activate\nFailed to create a window!" );
      return false;
   }

   // Get a device context from the new window:
   HDC tempDC = GetDC( winState.appWindow );
   if ( !tempDC )
   {
      AssertFatal( false, "Voodoo2Device::activate\nFailed to get a device context!" );
      return false;
   }

   // Set the pixel format of the new window:
   PIXELFORMATDESCRIPTOR pfd;
   CreatePixelFormat( &pfd, 16, 16, 8, false );
   S32 chosenFormat = ChooseBestPixelFormat( tempDC, &pfd );
   dwglDescribePixelFormat( tempDC, chosenFormat, sizeof( pfd ), &pfd );
   bool test = dwglSetPixelFormat( tempDC, chosenFormat, &pfd );
   ReleaseDC( winState.appWindow, tempDC );
   if ( !test )
   {
      AssertFatal( false, "Voodoo2Device::activate\nFailed to set the pixel format of the device context!" );
      return false;
   }
   Con::printf( "Pixel format set:" );
   Con::printf( "  %d color bits, %d depth bits, %d stencil bits", pfd.cColorBits, pfd.cDepthBits, pfd.cStencilBits );

   // Set the resolution:
   smCurrentRes.bpp = 16; // Force 16-bit color.
   if ( !setScreenMode( width, height, 16, true, true, false ) )
      return false;

   // Output some driver info to the console:
   const char* vendorString   = (const char*) glGetString( GL_VENDOR );
   const char* rendererString = (const char*) glGetString( GL_RENDERER );
   const char* versionString  = (const char*) glGetString( GL_VERSION );
   Con::printf( "OpenGL driver information:" );
   if ( vendorString )
      Con::printf( "  Vendor: %s", vendorString );
   if ( rendererString )
      Con::printf( "  Renderer: %s", rendererString );
   if ( versionString )
      Con::printf( "  Version: %s", versionString );

   // Set a few variables:
   if ( Con::getIntVariable( "$pref::OpenGL::mipReduction" ) < 1 )
   	  Con::setIntVariable( "$pref::OpenGL::mipReduction", 1 );
	if ( Con::getIntVariable( "$pref::OpenGL::interiorMipReduction" ) < 1 )
   	  Con::setIntVariable( "$pref::OpenGL::interiorMipReduction", 1 );
	if ( Con::getIntVariable( "$pref::OpenGL::skyMipReduction" ) < 1 )
   	  Con::setIntVariable( "$pref::OpenGL::skyMipReduction", 1 );

   if (dStrcmp(vendorString, Con::getVariable("$pref::Video::profiledVendor")) ||
       dStrcmp(rendererString, Con::getVariable("$pref::Video::profiledRenderer")))
   {
      // Voodoo2 defaults
      Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", false);
      Con::setBoolVariable("$pref::OpenGL::disableSubImage", false);
      Con::setBoolVariable("$pref::OpenGL::noEnvColor", true);
      Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", false);
		Con::setBoolVariable("$pref::Interior::lockArrays", true);
      Con::setBoolVariable("$pref::TS::skipFirstFog", false);
      Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", true);
   }

   if ( needResurrect )
   {
      // Reload the textures:
      Con::printf( "Resurrecting the texture manager..." );
      Game->textureResurrect();
   }

   // Set the new window to the foreground:
   ShowWindow( winState.appWindow, SW_SHOW );
   SetForegroundWindow( winState.appWindow );
   SetFocus( winState.appWindow );

   GL_EXT_Init();

   Con::setBoolVariable( "$SwapIntervalSupported", false );
   Con::setVariable( "$pref::Video::displayDevice", mDeviceName );
	Con::setBoolVariable("$pref::OpenGL::allowTexGen", true);

   return true;
}


//------------------------------------------------------------------------------
void Voodoo2Device::shutdown()
{
   if ( winState.hGLRC )
   {
      dwglMakeCurrent( NULL, NULL );
      dwglDeleteContext( winState.hGLRC );
      winState.hGLRC = NULL;
   }

   if ( winState.appDC )
   {
      ReleaseDC( winState.appWindow, winState.appDC );
      winState.appDC = NULL;
   }

   ChangeDisplaySettings( NULL, 0 );
}


//------------------------------------------------------------------------------
bool Voodoo2Device::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt, bool repaint )
{
   HWND curtain = NULL;
   bool needResurrect = false;
	Resolution newRes( width, height, bpp );

	if ( !fullScreen )
	{
	   // The 3Dfx Voodoo2 OpenGL driver only runs in full-screen, so ignore:
	   Con::warnf( ConsoleLogEntry::General, "Sorry, the Voodoo 2 display device only supports full-screen!" );
	}

   // Force the res to be one of the ones in the supported list:
   U32 resIndex = 0;
   U32 bestScore = 0, thisScore = 0;
   for ( U32 i = 0; i < mResolutionList.size(); i++ )
   {
      if ( newRes == mResolutionList[i] )
      {
         resIndex = i;
         break;
      }
      else
      {
         thisScore = abs( S32( newRes.w ) - S32( mResolutionList[i].w ) ) + abs( S32( newRes.h ) - S32( mResolutionList[i].h ) );
         if ( !bestScore || thisScore < bestScore )
         {
            bestScore = thisScore;
            resIndex = i;
         }
      }
   }
   newRes = mResolutionList[resIndex];

   // Return if we aren't forcing it and we are already in the desired resolution:
   if ( !forceIt && newRes == smCurrentRes )
      return true;

   Con::printf( "Setting screen mode to %dx%dx16 (fs)...", newRes.w, newRes.h );

   // Delete the rendering context:
   if ( winState.hGLRC )
   {
      Con::printf( "Killing the texture manager..." );
      Game->textureKill();
      needResurrect = true;

      Con::printf( "Making the rendering context not current..." );
      if ( !dwglMakeCurrent( NULL, NULL ) )
      {
         AssertFatal( false, "Voodoo2Device::setScreenMode\ndwglMakeCurrent( NULL, NULL ) failed!" );
         return false;
      }

      Con::printf( "Deleting the rendering context..." );
      if ( !dwglDeleteContext( winState.hGLRC ) )
      {
         AssertFatal( false, "Voodoo2Device::setScreenMode\ndwglDeleteContext failed!" );
         return false;
      }
      winState.hGLRC = NULL;
   }

   // Release the device context:
   if ( winState.appDC )
   {
      Con::printf( "Releasing the device context..." );
      ReleaseDC( winState.appWindow, winState.appDC );
      winState.appDC = NULL;
   }

   // Change the display settings (shouldn't really be necessary, but is):
   U32 test;
   DEVMODE devMode;
   dMemset( &devMode, 0, sizeof( devMode ) );
   devMode.dmSize       = sizeof( devMode );
   devMode.dmPelsWidth  = newRes.w;
   devMode.dmPelsHeight = newRes.h;
   devMode.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT;

   Con::printf( "Changing the display settings to %dx%dx16...", newRes.w, newRes.h );
   curtain = CreateCurtain( newRes.w, newRes.h );
   test = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN );
   if ( test != DISP_CHANGE_SUCCESSFUL )
   {
      ChangeDisplaySettings( NULL, 0 );
      switch( test )
      {
         case DISP_CHANGE_RESTART:
            Platform::AlertOK( "Display Change Failed", "You must restart your machine to get the specified mode." );
            break;

         case DISP_CHANGE_BADMODE:
            Platform::AlertOK( "Display Change Failed", "The specified mode is not supported by this device." );
            break;

         default:
            Platform::AlertOK( "Display Change Failed", "Hardware failed to switch to the specified mode." );
            break;
      };
      return false;
   }

   // Resize the window:
   if ( !SetWindowPos( winState.appWindow, NULL, 0, 0, newRes.w, newRes.h, SWP_NOZORDER | SWP_NOMOVE ) )
   {
      Con::printf( "Voodoo2Device::setScreenMode - SetWindowPos sizing to %dx%d failed.", newRes.w, newRes.h );
      return false;
   }

   // Get a new device context:
   Con::printf( "Acquiring a new device context..." );
   winState.appDC = GetDC( winState.appWindow );
   if ( !winState.appDC )
   {
      AssertFatal( false, "Voodoo2Device::setScreenMode\nFailed to get a valid device context!" );
      return false;
   }

   // Create a new rendering context:
   Con::printf( "Creating a new rendering context..." );
   winState.hGLRC = dwglCreateContext( winState.appDC );
   if ( !winState.hGLRC )
   {
      AssertFatal( false, "Voodoo2Device::setScreenMode\nFailed to create a GL rendering context!" );
      return false;
   }

   // Make the new rendering context current:
   Con::printf( "Making the new rendering context current..." );
   if ( !dwglMakeCurrent( winState.appDC, winState.hGLRC ) )
   {
      AssertFatal( false, "Voodoo2Device::setScreenMode\nFailed to make the rendering context current!" );
      return false;
   }

   if ( needResurrect )
   {
      // Reload the textures:
      Con::printf( "Resurrecting the texture manager..." );
      Game->textureResurrect();
   }

   smCurrentRes = newRes;
   Platform::setWindowSize( newRes.w, newRes.h );
   char tempBuf[15];
   dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp );
   Con::setVariable( "$pref::Video::resolution", tempBuf );

   if ( curtain )
      DestroyWindow( curtain );

   if ( repaint )
      Con::evaluate( "resetCanvas();" );

   return true;
}


//------------------------------------------------------------------------------
void Voodoo2Device::swapBuffers()
{
   dwglSwapBuffers( winState.appDC );
}


//------------------------------------------------------------------------------
const char* Voodoo2Device::getDriverInfo()
{
   // Output some driver info to the console:
   const char* vendorString   = (const char*) glGetString( GL_VENDOR );
   const char* rendererString = (const char*) glGetString( GL_RENDERER );
   const char* versionString  = (const char*) glGetString( GL_VERSION );
	const char* extensionsString = (const char*) glGetString( GL_EXTENSIONS );
	
	// TODO - move strings out of code and into script...
	U32 bufferLen = ( vendorString ? dStrlen( vendorString ) : 0 )
	              + ( rendererString ? dStrlen( rendererString ) : 0 )
                 + ( versionString  ? dStrlen( versionString ) : 0 )
                 + ( extensionsString ? dStrlen( extensionsString ) : 0 )
                 + 4;

	char* returnString = Con::getReturnBuffer( bufferLen );
	dSprintf( returnString, bufferLen, "%s\t%s\t%s\t%s",
			( vendorString ? vendorString : "" ),
			( rendererString ? rendererString : "" ),
			( versionString ? versionString : "" ),
			( extensionsString ? extensionsString : "" ) );
	
   return( returnString );
}


//------------------------------------------------------------------------------
bool Voodoo2Device::getGammaCorrection(F32 &g)
{
   // apparently the qwlGetDeviceGammaRamp3DFX doesn't actually work.  So, this
	// is a good default.  We don't need to worry about restoring the original
	// gamma.  Anything we do is wiped out once we destroy the Voodoo2 window.
   g = 0.8;

	return true;

   U16 ramp[256*3];

   if (!dwglGetDeviceGammaRamp3DFX(winState.appDC, ramp))
      return false;

   F32 csum = 0.0;
   U32 ccount = 0;

   for (U16 i = 0; i < 256; ++i)
   {
      if (i != 0 && ramp[i] != 0 && ramp[i] != 65535)
      {
         F64 b = (F64) i/256.0;
         F64 a = (F64) ramp[i]/65535.0;
         F32 c = (F32) (mLog(a)/mLog(b));

         csum += c;
         ++ccount;
      }
   }
   g = csum/ccount;

   return true;
}


//------------------------------------------------------------------------------
bool Voodoo2Device::setGammaCorrection(F32 g)
{
   U16 ramp[256*3];

   for (U16 i = 0; i < 256; ++i)
      ramp[i] = mPow((F32) i/256.0f, g) * 65535.0f;
   dMemcpy(&ramp[256],ramp,256*sizeof(U16));
   dMemcpy(&ramp[512],ramp,256*sizeof(U16));

   return dwglSetDeviceGammaRamp3DFX(winState.appDC, ramp);
}

//------------------------------------------------------------------------------
bool Voodoo2Device::setVerticalSync( bool on )
{
   on;
   return( false );
}

//------------------------------------------------------------------------------
// Stuff for Voodoo 2 detection:
#ifndef GR_HARDWARE
#define GR_HARDWARE  0xa1
#endif

#define GLIDE_NUM_TMU         2
#define MAX_NUM_SST           4

typedef S32 GrSstType;
#define GR_SSTTYPE_VOODOO     0
#define GR_SSTTYPE_SST96      1
#define GR_SSTTYPE_AT3D       2

typedef struct GrTMUConfig_St
{
   S32    tmuRev;                /* Rev of Texelfx chip */
   S32    tmuRam;                /* 1, 2, or 4 MB */
} GrTMUConfig_t;

typedef struct GrVoodooConfig_St
{
   S32    fbRam;                             /* 1, 2, or 4 MB */
   S32    fbiRev;                            /* Rev of Pixelfx chip */
   S32    nTexelfx;                          /* How many texelFX chips are there? */
   FxBool sliDetect;                         /* Is it a scan-line interleaved board? */
   GrTMUConfig_t tmuConfig[GLIDE_NUM_TMU];   /* Configuration of the Texelfx chips */
} GrVoodooConfig_t;

typedef struct GrSst96Config_St
{
   S32   fbRam;                  /* How much? */
   S32   nTexelfx;
   GrTMUConfig_t tmuConfig;
} GrSst96Config_t;

typedef struct GrAT3DConfig_St
{
   S32   rev;
} GrAT3DConfig_t;


typedef struct
{
   S32 num_sst;                  /* # of HW units in the system */
   struct
   {
      GrSstType type;             /* Which hardware is it? */
      union SstBoard_u
      {
         GrVoodooConfig_t  VoodooConfig;
         GrSst96Config_t   SST96Config;
         GrAT3DConfig_t    AT3DConfig;
      } sstBoard;
   } SSTs[MAX_NUM_SST];          /* configuration for each board */
} GrHwConfiguration;

typedef FxBool( FX_CALL *grSstQueryBoards_fpt )( GrHwConfiguration *hwconfig );
typedef FxBool( FX_CALL *grSstQueryHardware_fpt )( GrHwConfiguration *hwconfig );

typedef void ( FX_CALL *grGlideInit_fpt )( void );
typedef const char* ( FX_CALL *grGetString_fpt )( FxU32 pName );
typedef void( FX_CALL *grGlideShutdown_fpt )( void );


//------------------------------------------------------------------------------
DisplayDevice* Voodoo2Device::create()
{
   // Detect the Voodoo2 OpenGL driver:
   GrHwConfiguration hwConfig;
   bool  result = true;

   // Reset the available resolution specifier code:
   sResCode = 0;

   grGlideInit_fpt         grGlideInit = NULL;
   grGlideShutdown_fpt     grGlideShutdown = NULL;
   grSstQueryBoards_fpt    grSstQueryBoards = NULL;
   grSstQueryHardware_fpt  grSstQueryHardware = NULL;
   grGetString_fpt         grGetString = NULL;

   // Let the Glide 2 stuff go first:
   HINSTANCE glide2DLL = LoadLibrary( dT("glide2x") );

   if ( !glide2DLL )
      return NULL;

   grSstQueryBoards     = (grSstQueryBoards_fpt) GetProcAddress( glide2DLL, "_grSstQueryBoards@4" );

   if ( grSstQueryBoards )
   {
      grSstQueryBoards( &hwConfig );
      if ( hwConfig.num_sst == 0 )
         result = false;
      else
      {
         grGlideInit          = (grGlideInit_fpt) GetProcAddress( glide2DLL, "_grGlideInit@0" );
         grSstQueryHardware   = (grSstQueryHardware_fpt) GetProcAddress( glide2DLL, "_grSstQueryHardware@4" );
         grGlideShutdown      = (grGlideShutdown_fpt) GetProcAddress( glide2DLL, "_grGlideShutdown@0" );

         if ( grGlideInit && grSstQueryHardware && grGlideShutdown )
         {
            grGlideInit();
            grSstQueryHardware( &hwConfig );

            // Find out what resolutions are available:
            if ( hwConfig.SSTs[0].sstBoard.VoodooConfig.sliDetect == 0 )   // SLI not detected.
            {
               if ( hwConfig.SSTs[0].sstBoard.VoodooConfig.fbRam >= 4 )
                  sResCode = 1;  // low bit indicates 800x600
            }
            else  // SLI detected.
            {
               sResCode = 1;

               if ( hwConfig.SSTs[0].sstBoard.VoodooConfig.fbRam >= 4 )
                  sResCode += 2;
            }

            grGlideShutdown();
         }

         grGlideInit       = NULL;
         grGlideShutdown   = NULL;
      }
   }
   else
      result = false;

   FreeLibrary( glide2DLL );

   if ( result )
   {
      // Glide 3's turn:
      HINSTANCE glide3DLL = LoadLibrary( dT("glide3x") );

      if ( !glide3DLL )
         return NULL;

      grGlideInit       = (grGlideInit_fpt) GetProcAddress( glide3DLL, "_grGlideInit@0" );
      grGetString       = (grGetString_fpt) GetProcAddress( glide3DLL, "_grGetString@4" );
      grGlideShutdown   = (grGlideShutdown_fpt) GetProcAddress( glide3DLL, "_grGlideShutdown@0" );

      if ( grGlideInit && grGetString && grGlideShutdown )
      {
         grGlideInit();

         const char* hardware = grGetString( GR_HARDWARE );
         if ( dStrcmp( hardware, "Voodoo2" ) != 0 )
            result = false;

         grGlideShutdown();
      }
      else
         result = false;

      FreeLibrary( glide3DLL );
   }

   if ( result )
      return new Voodoo2Device();
   else
      return NULL;
}
