#define _CRT_SECURE_NO_WARNINGS

#include "stdio.h"
#include "dxwnd.h"
#include "dxwcore.hpp"
#include "syslibs.h"
#include "dxhook.h"
#include "gl\gl.h"
#include "gl\wglext.h"
#include "gl\glext.h"

#define DXWDECLARATIONS TRUE
#include "glhook.h"
#undef DXWDECLARATIONS

#ifndef COMPRESSED_RGB_S3TC_DXT1_EXT
#define COMPRESSED_RGB_S3TC_DXT1_EXT                   0x83F0
#define COMPRESSED_RGBA_S3TC_DXT1_EXT                  0x83F1
#define COMPRESSED_RGBA_S3TC_DXT3_EXT                  0x83F2
#define COMPRESSED_RGBA_S3TC_DXT5_EXT                  0x83F3
#endif

static HookEntry_Type Hooks[]={
	{HOOK_IAT_CANDIDATE, "glViewport", NULL, (FARPROC *)&pglViewport, (FARPROC)extglViewport},
	{HOOK_IAT_CANDIDATE, "glScissor", NULL, (FARPROC *)&pglScissor, (FARPROC)extglScissor},
	{HOOK_IAT_CANDIDATE, "glGetIntegerv", NULL, (FARPROC *)&pglGetIntegerv, (FARPROC)&extglGetIntegerv},
	{HOOK_IAT_CANDIDATE, "glDrawBuffer", NULL, (FARPROC *)&pglDrawBuffer, (FARPROC)extglDrawBuffer},
	{HOOK_IAT_CANDIDATE, "glPolygonMode", NULL, (FARPROC *)&pglPolygonMode, (FARPROC)extglPolygonMode},
	{HOOK_IAT_CANDIDATE, "glGetFloatv", NULL, (FARPROC *)&pglGetFloatv, (FARPROC)extglGetFloatv},
	{HOOK_IAT_CANDIDATE, "glClear", NULL, (FARPROC *)&pglClear, (FARPROC)extglClear},
	{HOOK_IAT_CANDIDATE, "wglCreateContext", NULL, (FARPROC *)&pwglCreateContext, (FARPROC)extwglCreateContext},
	{HOOK_IAT_CANDIDATE, "wglMakeCurrent", NULL, (FARPROC *)&pwglMakeCurrent, (FARPROC)extwglMakeCurrent},
	{HOOK_IAT_CANDIDATE, "wglGetProcAddress", NULL, (FARPROC *)&pwglGetProcAddress, (FARPROC)extwglGetProcAddress},
	{HOOK_IAT_CANDIDATE, "glTexImage2D", NULL, (FARPROC *)&pglTexImage2D, (FARPROC)extglTexImage2D},
	{HOOK_IAT_CANDIDATE, 0, NULL, 0, 0} // terminator
};

FARPROC Remap_gl_ProcAddress(LPCSTR proc, HMODULE hModule)
{
	FARPROC addr;
	if(!(dxw.dwFlags2 & HOOKOPENGL)) return NULL; 
	if (addr=RemapLibrary(proc, hModule, Hooks)) return addr;
	// NULL -> keep the original call address
	return NULL;
}

PROC Remap_wgl_ProcAddress(LPCSTR proc)
{
	int i;
	HookEntry_Type *Hook;
	for(i=0; Hooks[i].APIName; i++){
		Hook=&Hooks[i];
		if (!strcmp(proc,Hook->APIName)){
			if (Hook->StoreAddress) *(Hook->StoreAddress)=(*pwglGetProcAddress)(proc);
			OutTraceDW("GetProcAddress: hooking proc=%s at addr=%x\n", ProcToString(proc), (Hook->StoreAddress) ? *(Hook->StoreAddress) : 0);
			return Hook->HookerAddress;
		}
	}
	// NULL -> keep the original call address
	return NULL;
}

void ForceHookOpenGL(HMODULE base) // to do .....
{
	HMODULE hGlLib;
	static int DoOnce=FALSE;

	if(DoOnce) return;
	DoOnce = TRUE;

	hGlLib=(*pLoadLibraryA)("OpenGL32.dll");
	OutTrace("hGlLib=%x\n",hGlLib);
	if(!hGlLib){
		OutTraceE("LoadLibrary(\"OpenGL32.dll\") ERROR: err=%d at %d\n", GetLastError(), __LINE__);
		return;
	}

	int i;
	HookEntry_Type *Hook;
	for(i=0; Hooks[i].APIName; i++){
		Hook=&Hooks[i];
		Hook->OriginalAddress = GetProcAddress(hGlLib, Hook->APIName);
		if(Hook->OriginalAddress) {
			HookAPI(base, "opengl32", Hook->StoreAddress, Hook->APIName, Hook->HookerAddress);
		}
	}
	return;
}

void HookOpenGL(HMODULE module, char *customlib) 
{
	HookLibrary(module, Hooks, customlib);
}

void HookOpenGLLibs(HMODULE module, char *customlib)
{
	char *DefOpenGLModule="OpenGL32.dll";

	if (!customlib) customlib=DefOpenGLModule;

	OutTraceDW("HookOpenGLLibs module=%x lib=\"%s\" forced=%x\n", module, customlib, (dxw.dwFlags3 & FORCEHOOKOPENGL)?1:0);
	if (dxw.dwFlags3 & FORCEHOOKOPENGL) 
		ForceHookOpenGL(module);
	else
		HookOpenGL(module, customlib);

	return;
}

void WINAPI extglViewport(GLint  x,  GLint  y,  GLsizei  width,  GLsizei  height)
{
	RECT client;
	POINT p={0,0};
	HWND hwnd;
	//if (dxw.dwFlags2 & HANDLEFPS) if(dxw.HandleFPS()) return;
	hwnd=dxw.GethWnd();
	(*pGetClientRect)(hwnd, &client);
	OutTraceDW("glViewport: declared pos=(%d,%d) size=(%d,%d)\n", x, y, width, height);
	if(IsDebug) OutTrace("glViewport: DEBUG hwnd=%x win=(%d,%d) screen=(%d,%d)\n",
		hwnd, client.right, client.bottom, dxw.GetScreenWidth(), dxw.GetScreenHeight());
	if(x==CW_USEDEFAULT) x=0;
	if(y==CW_USEDEFAULT) y=0;
	x = (x * (GLint)client.right) / (GLint)dxw.GetScreenWidth();
	y = (y * (GLint)client.bottom) / (GLint)dxw.GetScreenHeight();
	width = (width * (GLint)client.right) / (GLint)dxw.GetScreenWidth();
	height = (height * (GLint)client.bottom) / (GLint)dxw.GetScreenHeight();
	OutTraceDW("glViewport: remapped pos=(%d,%d) size=(%d,%d)\n", x, y, width, height);
	(*pglViewport)(x, y, width, height);
}

void WINAPI extglScissor(GLint  x,  GLint  y,  GLsizei  width,  GLsizei  height)
{
	RECT client;
	POINT p={0,0};
	//if (dxw.dwFlags2 & HANDLEFPS) if(dxw.HandleFPS()) return;
	(*pGetClientRect)(dxw.GethWnd(), &client);
	OutTraceDW("glScissor: declared pos=(%d,%d) size=(%d,%d)\n", x, y, width, height);
	x = (x * (GLint)client.right) / (GLint)dxw.GetScreenWidth();
	y = (y * (GLint)client.bottom) / (GLint)dxw.GetScreenHeight();
	width = (width * (GLint)client.right) / (GLint)dxw.GetScreenWidth();
	height = (height * (GLint)client.bottom) / (GLint)dxw.GetScreenHeight();
	OutTraceDW("glScissor: remapped pos=(%d,%d) size=(%d,%d)\n", x, y, width, height);
	(*pglScissor)(x, y, width, height);
}

void WINAPI extglGetIntegerv(GLenum pname, GLint *params)
{
	(*pglGetIntegerv)(pname, params);
	OutTraceDW("glGetIntegerv: pname=%d\n", pname);
}

void WINAPI extglDrawBuffer(GLenum mode)
{
	if (IsDebug) OutTrace("glDrawBuffer: mode=%x\n", mode);
	if(dxw.dwFlags2 & WIREFRAME) (*pglClear)(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT) ; // clear screen for wireframe mode....
	// handle FPS only to backbuffer updates (if stereo, on left backbuffer...)
	// using the frontbuffer seems less reliable: Return to Castle Wolfenstein doesn't use it at all!
	if (dxw.dwFlags2 & HANDLEFPS){
		switch (mode){
			//case GL_FRONT_LEFT:
			case GL_BACK_LEFT:
			//case GL_FRONT:
			case GL_BACK:
			case GL_LEFT:
			case GL_FRONT_AND_BACK:
				if(dxw.HandleFPS()) return;
		}
	}
	(*pglDrawBuffer)(mode);
	dxw.ShowOverlay();
}

void WINAPI extglPolygonMode(GLenum face, GLenum mode)
{
	OutTraceDW("glPolygonMode: face=%x mode=%x\n", face, mode);
	//OutTraceDW("glPolygonMode: extglPolygonMode=%x pglPolygonMode=%x\n", extglPolygonMode, pglPolygonMode);
	if(dxw.dwFlags2 & WIREFRAME) mode = GL_LINE; // trick to set wireframe mode....
	(*pglPolygonMode)(face, mode);
	return;
}

void WINAPI extglGetFloatv(GLenum pname, GLboolean *params)
{
	OutTraceDW("glGetFloatv: pname=%x\n", pname);
	(*pglGetFloatv)(pname, params);
	return;
}

void WINAPI extglClear(GLbitfield mask)
{
	(*pglClear)(mask);
	return;
}

//BEWARE: SetPixelFormat must be issued on the same hdc used by OpenGL wglCreateContext, otherwise 
// a failure err=2000 ERROR INVALID PIXEL FORMAT occurs!!

HGLRC WINAPI extwglCreateContext(HDC hdc)
{
	HGLRC ret;
	OutTraceDW("wglCreateContext: hdc=%x\n", hdc);
	// v2.02.31: don't let it use desktop hdc
	if(dxw.IsDesktop(WindowFromDC(hdc))){
		HDC oldhdc = hdc;
		hdc=(*pGDIGetDC)(dxw.GethWnd());
		OutTraceDW("wglCreateContext: remapped desktop hdc=%x->%x hWnd=%x\n", oldhdc, hdc, dxw.GethWnd());
	}
	ret=(*pwglCreateContext)(hdc);
	if(ret){
		HWND hwnd;
		hwnd=WindowFromDC(hdc);
		dxw.SethWnd(hwnd);
		OutTraceDW("wglCreateContext: SET hwnd=%x\n", hwnd);
	}
	else {
		OutTraceDW("wglCreateContext: ERROR err=%d\n", GetLastError());
	}
	return ret;
}

PROC WINAPI extwglGetProcAddress(LPCSTR proc)
{
	PROC procaddr;

	OutTraceDW("wglGetProcAddress: proc=%s\n", proc);
	procaddr=Remap_wgl_ProcAddress(proc);
	if (!procaddr) procaddr=(*pwglGetProcAddress)(proc);
	return procaddr;
}

BOOL WINAPI extwglMakeCurrent(HDC hdc, HGLRC hglrc)
{
	BOOL ret;

	OutTraceDW("wglMakeCurrent: hdc=%x hglrc=%x\n", hdc, hglrc);
	// v2.02.31: don't let it use desktop hdc
	if(dxw.IsDesktop(WindowFromDC(hdc))){
		HDC oldhdc = hdc;
		hdc=(*pGDIGetDC)(dxw.GethWnd());
		OutTraceDW("wglMakeCurrent: remapped desktop hdc=%x->%x\n", oldhdc, hdc);
	}
	ret=(*pwglMakeCurrent)(hdc, hglrc);
	if(ret){
		HWND hWnd;
		hWnd = WindowFromDC(hdc);
		OutTraceDW("wglMakeCurrent: setting hwnd=%x\n", hWnd);
		dxw.SethWnd(hWnd);
	}
	return ret;
}

// to do:
//	glutSetWindow - save current window handle
//	glutInitWindowPosition, glutInitWindowSize
//	glutInitDisplayMode
//  glutCreateWindow,  glutCreateSubWindow
//	glutPositionWindow,  glutReshapeWindow
//	glGetFloatv ( GL_SCISSOR_BOX - GL_VIEWPORT )

static unsigned int Hash(BYTE *buf, int len)
{
   unsigned int b    = 378551;
   unsigned int a    = 63689;
   DWORD hash = 0;
   for(int i = 0; i < len; i++){
      hash = hash * a + buf[i];
      a    = a * b;
   }
   return hash;
}

static void glTextureDump(GLint internalFormat, GLenum Format, GLsizei w, GLsizei h, GLenum type, const GLvoid * data)
{
	int iSurfaceSize, iScanLineSize;
	static int MinTexX, MinTexY, MaxTexX, MaxTexY;
	static BOOL DoOnce = TRUE;
	char pszFile[MAX_PATH];
	WORD dwRGBBitCount;
	DWORD dwRBitMask, dwGBitMask, dwBBitMask, dwABitMask;
	char *sType;

	if(DoOnce){
		char sProfilePath[MAX_PATH];
		sprintf_s(sProfilePath, MAX_PATH, "%s\\dxwnd.ini", GetDxWndPath());
		MinTexX=GetPrivateProfileInt("Texture", "MinTexX", 0, sProfilePath);
		MaxTexX=GetPrivateProfileInt("Texture", "MaxTexX", 0, sProfilePath);
		MinTexY=GetPrivateProfileInt("Texture", "MinTexY", 0, sProfilePath);
		MaxTexY=GetPrivateProfileInt("Texture", "MaxTexY", 0, sProfilePath);
		OutTrace("TextureDump: size min=(%dx%d) max=(%dx%d)\n", MinTexX, MinTexY, MaxTexX, MaxTexY);
		sprintf_s(pszFile, MAX_PATH, "%s\\texture.out", GetDxWndPath());
		CreateDirectory(pszFile, NULL);
		DoOnce = FALSE;
	}

	if((w < MinTexX) || (h < MinTexY)) return;

	// temporary ....
					dwRBitMask = 0x000000FF;
					dwGBitMask = 0x0000FF00;
					dwBBitMask = 0x00FF0000;
					dwABitMask = 0xFF000000;
					dwRGBBitCount = 32;

	// format: GL_RED?, GL_GREEN?, GL_BLUE?, GL_RG?, GL_RGB?, GL_BGR?, GL_RGBA?, GL_BGRA
	// type: GL_UNSIGNED_BYTE?, GL_BYTE?, GL_UNSIGNED_SHORT?, GL_SHORT?, GL_UNSIGNED_INT?, GL_INT?, 
	// GL_FLOAT?, GL_UNSIGNED_BYTE_3_3_2?, GL_UNSIGNED_BYTE_2_3_3_REV?, GL_UNSIGNED_SHORT_5_6_5?, GL_UNSIGNED_SHORT_5_6_5_REV?,
	// GL_UNSIGNED_SHORT_4_4_4_4?, GL_UNSIGNED_SHORT_4_4_4_4_REV?, GL_UNSIGNED_SHORT_5_5_5_1?, GL_UNSIGNED_SHORT_1_5_5_5_REV?, 
	// GL_UNSIGNED_INT_8_8_8_8?, GL_UNSIGNED_INT_8_8_8_8_REV?, GL_UNSIGNED_INT_10_10_10_2?, GL_UNSIGNED_INT_2_10_10_10_REV
	sType = "unknown";
	dwRGBBitCount = 0;
	switch(Format){
		case GL_RGB:
		case GL_RGBA:
			switch(type){
				case GL_BYTE:
				case GL_UNSIGNED_BYTE: // Daikatana, Doom 3
					dwRBitMask = 0x000000FF;
					dwGBitMask = 0x0000FF00;
					dwBBitMask = 0x00FF0000;
					dwABitMask = (Format == GL_RGBA) ? 0xFF000000 : 0x00000000;
					dwRGBBitCount = 32;
					sType = (Format == GL_RGBA) ? "RGBA888" : "RGB888";
					break;
			}
			break;
		case GL_BGR:
		case GL_BGRA:
			switch(type){
				case GL_BYTE:
				case GL_UNSIGNED_BYTE: // ???
					dwRBitMask = 0x00FF0000;
					dwGBitMask = 0x0000FF00;
					dwBBitMask = 0x000000FF;
					dwABitMask = (Format == GL_RGBA) ? 0xFF000000 : 0x00000000;
					dwRGBBitCount = 32;
					sType = (Format == GL_RGBA) ? "RGBA888" : "RGB888";
					break;
			}
			break;
	}

	if(!dwRGBBitCount) return;
	iSurfaceSize = w * h;

	while (TRUE) {
		FILE *hf;
		BITMAPFILEHEADER hdr;       // bitmap file-header 
		BITMAPV4HEADER pbi;			// bitmap info-header  

		memset((void *)&pbi, 0, sizeof(BITMAPV4HEADER));
		pbi.bV4Size = sizeof(BITMAPV4HEADER); 
		pbi.bV4Width = w;
		pbi.bV4Height = h;
		pbi.bV4BitCount = dwRGBBitCount;
		pbi.bV4SizeImage = ((w * dwRGBBitCount + 0x1F) & ~0x1F)/8 * h; 
		pbi.bV4Height = - h;
		pbi.bV4Planes = 1;
		pbi.bV4V4Compression = BI_BITFIELDS;
		if(pbi.bV4BitCount == 8) pbi.bV4V4Compression = BI_RGB;
		pbi.bV4XPelsPerMeter = 1;
		pbi.bV4YPelsPerMeter = 1;
		pbi.bV4ClrUsed = 0;
		if(pbi.bV4BitCount == 8) pbi.bV4ClrUsed = 256;
		pbi.bV4ClrImportant = 0;
		pbi.bV4RedMask = dwRBitMask;
		pbi.bV4GreenMask = dwGBitMask;
		pbi.bV4BlueMask = dwBBitMask;
		pbi.bV4AlphaMask = dwABitMask;
		pbi.bV4CSType = LCS_CALIBRATED_RGB;
		iScanLineSize = ((w * dwRGBBitCount + 0x1F) & ~0x1F)/8;

		// calculate the bitmap hash
		DWORD hash = Hash((BYTE *)data, iScanLineSize * h);

		// Create the .BMP file. 
		//sprintf_s(pszFile, MAX_PATH, "%s\\texture.out\\texture.%03d.%03d.%s.%08X.bmp", 
		//	GetDxWndPath(), w, h, sType, hash);
		sprintf_s(pszFile, MAX_PATH, "%s\\texture.out\\texture.I%x.F%x.T%x.%03d.%03d.%08X.bmp", 
			GetDxWndPath(), internalFormat, Format, type, w, h, hash);
		OutTrace("writing to %s\n", pszFile);
		hf = fopen(pszFile, "wb");
		if(!hf) break;

		hdr.bfType = 0x4d42;        // 0x42 = "B" 0x4d = "M"  
		// Compute the size of the entire file.  
		hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + pbi.bV4Size + pbi.bV4ClrUsed * sizeof(RGBQUAD) + pbi.bV4SizeImage); 
		hdr.bfReserved1 = 0; 
		hdr.bfReserved2 = 0; 

		// Compute the offset to the array of color indices.  
		hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + pbi.bV4Size + pbi.bV4ClrUsed * sizeof (RGBQUAD); 

		// Copy the BITMAPFILEHEADER into the .BMP file.  
		fwrite((LPVOID)&hdr, sizeof(BITMAPFILEHEADER), 1, hf);

		// Copy the BITMAPINFOHEADER array into the file.  
		fwrite((LPVOID)&pbi, sizeof(BITMAPV4HEADER), 1, hf);

		// Copy the array of color indices into the .BMP file.  
		//for(int y=0; y<(int)ddsd.dwHeight; y++)
		//	fwrite((BYTE *)ddsd.lpSurface + (y*ddsd.lPitch), iScanLineSize, 1, hf);
		fwrite((BYTE *)data, iScanLineSize, h, hf);

		// Close the .BMP file.  
		fclose(hf);
		break;
	}
}


void WINAPI extglTexImage2D(
	GLenum target,
  	GLint level,
  	GLint internalFormat,
  	GLsizei width,
  	GLsizei height,
  	GLint border,
  	GLenum format,
  	GLenum type,
  	const GLvoid * data)
{
	OutTrace("glTexImage2D: TEXTURE target=%x level=%x internalformat=%x format=%x type=%x size=(%dx%d)\n", 
		target, level, internalFormat, format, type, width, height);

	switch(target){
		//case GL_PROXY_TEXTURE_RECTANGLE:
		//case GL_PROXY_TEXTURE_2D:
		case GL_TEXTURE_2D:
		case GL_TEXTURE_RECTANGLE:
			switch(dxw.dwFlags5 & TEXTUREMASK){
				default:
				case TEXTUREHIGHLIGHT: 
					//glTextureHighlight(s);
					break;
				case TEXTUREDUMP: 
					glTextureDump(internalFormat, format, width, height, type, data);
					break;
				case TEXTUREHACK:
					//glTextureHack(...);
					break;
				case TEXTURETRANSP:
					//glTextureTransp(...);
					break;
			}
			break;
	}

	return (*pglTexImage2D)(target, level, internalFormat, width, height, border, format, type, data);
}
