#include "renderer.h"
#include "windowing.h"
#include "camera.h"
#include "quadtree.h"
#include "math.h"
#include "skybox.h"
#include "text.h"

#pragma comment(lib, "winmm.lib")	//for timeGetTime()

int rWindowing::width,
		rWindowing::height,
		rWindowing::depth;

HWND rWindowing::hWnd;

extern HANDLE g_EndFrameSignal;
extern rMap *g_Map;
extern CCamera g_Camera;
extern rMath *m;
extern rText *t;

long curFrame = 0;
float runtime = 0;

rRenderer::rRenderer()
{
	bRender = false;
	bStopped = false;
	bMotionBlur = false;
	bRenderText = false;
	timeToEnd = 5;
	numTextures = 0;
	hSuspended = CreateEvent(0,1,1,"resumeRendr");
}

rRenderer::~rRenderer()
{
	Kill();
}

bool rRenderer::Create()
{
	return rCreateThread(false);
}


void rRenderer::Update()
{
//	if(bRender && !bStopped)
//		rResumeThread();
}

const unsigned int frameInterval = 17; // 17ms frame time
void FrameDelay()
{
	static unsigned int lastTime = 0;
    unsigned int currentTime = (unsigned int)timeGetTime();
	unsigned int expectedTime = frameInterval + lastTime;

	if(currentTime<expectedTime)
		Sleep((unsigned int)(expectedTime - currentTime));

	lastTime = currentTime;
	return;
}


void rRenderer::rThreadLoop()
{
	g_Logger.Log("Render thread starting...");
	initSubSystem();
	rInitFromGLThread();
	glClearAccum(0,0,0,0);
	glClear(GL_ACCUM_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

	LARGE_INTEGER timerFreq, lastTime, startupTime;
	QueryPerformanceFrequency(&timerFreq);
	float timerPeriod = 1.0f/(float)timerFreq.QuadPart;
	QueryPerformanceCounter(&startupTime);
	lastTime = startupTime;

	while(active)
	{
		glClear(GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		if(bRenderText)
		{
			bRenderText = t->Draw();
		}

		WaitForSingleObject(g_RenderStateMutex, INFINITE);

		g_Camera.Look();
		CalculateFrustum();
		g_Map->Draw();

		ReleaseMutex(g_RenderStateMutex);
		

		if(bMotionBlur)
		{
			glAccum(GL_MULT, mulFactor);
			glAccum(GL_ACCUM, accumFactor);
			glAccum(GL_RETURN, 1);
		}


		// Done Rendering
		LARGE_INTEGER curTime;
		QueryPerformanceCounter(&curTime);

		runtime = (float)(curTime.QuadPart - startupTime.QuadPart)*timerPeriod;
		curFrame = (long)(60.0f*runtime);

		SetEvent(g_EndFrameSignal); //Notify the main thread we are done rendering

#if 1
		FrameDelay();
#else
		// Cap FPS to 61, doesn't work very well due to the resolution of Sleep() I think
		float frameTime = (float)(curTime.QuadPart - lastTime.QuadPart)*timerPeriod;
		float sleepTime = 1.0f/61.0f - frameTime;
		if(sleepTime > 0)
		{
			Sleep((long)(1000.0f*sleepTime));
		}
		lastTime = curTime;
#endif

		SwapBuffers(hDC);
		if(!bRender)
		{
			SetEvent(hSuspended);	//Tell other threads we are not rendering anymore
			rSuspendThread();
			rInitFromGLThread();
			SetEvent(hSuspended);
			bRender = true;
		}
	}
	rDeInitFromGLThread();
	g_Logger.Log("Render thread shutdown...");
}


bool rRenderer::Suspend()
{
	bRender = false;
	WaitForSingleObject(hSuspended, INFINITE);//Wait till we enter the if-block above
	ResetEvent(hSuspended);
	return true;
}

void rRenderer::Resume()
{
	unsigned int n = 0;
	while(n++, rResumeThread() == 0)
	{
		Sleep(5);
		if(n>10000)
		{
			g_Logger.Log("Abnormal thread resume.");
			return;	//Most likely the thread was not suspended
		}
	}
	WaitForSingleObject(hSuspended, INFINITE);
	ResetEvent(hSuspended);
	bRender = true;
}

void rRenderer::Kill()
{
	if(!active)return;
	rResumeThread();
	active = false;
	WaitForSingleObject(threadHandle, INFINITE);
}

bool rOpenGL::initSubSystem()
{
	hDC = GetDC(rWindowing::hWnd);
			
	PIXELFORMATDESCRIPTOR pfd; 
    int pixelformat; 
 
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;		
									
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 
    pfd.dwLayerMask = PFD_MAIN_PLANE;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = rWindowing::depth;
    pfd.cDepthBits = rWindowing::depth;
    pfd.cAccumBits = 0;
    pfd.cStencilBits = 0;
 
    if ((pixelformat = ChoosePixelFormat(hDC, &pfd)) == false) 
        return false; 
 
    if (SetPixelFormat(hDC, pixelformat, &pfd) == false) 
        return false; 					

    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);
	glViewport(0, 0, rWindowing::width, rWindowing::height);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	setFov(45);

	return !glewInit();
}

rOpenGL::~rOpenGL()
{
	releaseSubSytem();
}

void rOpenGL::releaseSubSytem()
{
	wglMakeCurrent(NULL, NULL);
	wglDeleteContext(hRC);
	if(hDC) 
		ReleaseDC(rWindowing::hWnd, hDC);
}

void rOpenGL::enableMotionBlur(float accum, float mul)
{
	accumFactor = accum;
	mulFactor = mul;
	bMotionBlur = true;
}

void rOpenGL::disableMotionBlur()
{
	bMotionBlur = false;
}

void rOpenGL::setFov(float fov)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(
		fov, 
		(double)rWindowing::width / (double)rWindowing::height,
		0.15f,	// Near plane should be further away
		250.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}


void rOpenGL::OrthoMode()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0,(double)rWindowing::width, 0, (double)rWindowing::height, -100.0, 100.0);
}

void rOpenGL::DeleteTextures(unsigned int n, unsigned int *textures)
{
	glDeleteTextures(n, textures);
}
bool rOpenGL::CreateTexture(unsigned int &texture, char* strFile, bool mipmaps)
{
	char strFileName[128];
	strcpy(strFileName, "textures\\");
	strcat(strFileName, strFile);

	AUX_RGBImageRec *pImage = NULL;
	FILE *pFile = NULL;

	if((pFile = fopen(strFileName, "rb")) == NULL) 
	{
		g_Logger.Log("Unable to load BMP File: %s", strFileName);
		return false;
	}
	

	pImage = auxDIBImageLoad(strFileName);				
	
	fclose(pFile);

	glGenTextures(1, &texture);

	glPixelStorei (GL_UNPACK_ALIGNMENT, 1);

	glBindTexture(GL_TEXTURE_2D, texture);
	
	if(mipmaps)
	{
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pImage->sizeX, 
					  pImage->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pImage->data);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	else
	{
		glTexImage2D(GL_TEXTURE_2D, 0, 3, pImage->sizeX, pImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, pImage->data);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}

	if (pImage->data)	
		free(pImage->data);	

	free(pImage);
	return true;
}

void rOpenGL::CalculateFrustum()
{
	float magnitude;
	float mv[16],p[16];

	glGetFloatv(GL_MODELVIEW_MATRIX, mv);
	glGetFloatv(GL_PROJECTION_MATRIX,p);

	m->MatrixMultiply(clip, mv, p);

	frustum[Left][0] = clip[ 3] + clip[ 0];
	frustum[Left][1] = clip[ 7] + clip[ 4];
	frustum[Left][2] = clip[11] + clip[ 8];
	frustum[Left][3] = clip[15] + clip[12];
	magnitude = m->MagnitudeVA3(frustum[Left]);
	frustum[Left][0] /= magnitude;
	frustum[Left][1] /= magnitude;
	frustum[Left][2] /= magnitude;
	frustum[Left][3] /= magnitude; 

	frustum[Right][0] = clip[ 3] - clip[ 0];
	frustum[Right][1] = clip[ 7] - clip[ 4];
	frustum[Right][2] = clip[11] - clip[ 8];
	frustum[Right][3] = clip[15] - clip[12];
	magnitude = m->MagnitudeVA3(frustum[Right]);
	frustum[Right][0] /= magnitude;
	frustum[Right][1] /= magnitude;
	frustum[Right][2] /= magnitude;
	frustum[Right][3] /= magnitude; 


	frustum[Front][0] = clip[ 3] + clip[ 2];
	frustum[Front][1] = clip[ 7] + clip[ 6];
	frustum[Front][2] = clip[11] + clip[10];
	frustum[Front][3] = clip[15] + clip[14];
	magnitude = m->MagnitudeVA3(frustum[Front]);
	frustum[Front][0] /= magnitude;
	frustum[Front][1] /= magnitude;
	frustum[Front][2] /= magnitude;
	frustum[Front][3] /= magnitude; 


	frustum[Back][0] = clip[ 3] - clip[ 2];
	frustum[Back][1] = clip[ 7] - clip[ 6];
	frustum[Back][2] = clip[11] - clip[10];
	frustum[Back][3] = clip[15] - clip[14];
	magnitude = m->MagnitudeVA3(frustum[Back]);
	frustum[Back][0] /= magnitude;
	frustum[Back][1] /= magnitude;
	frustum[Back][2] /= magnitude;
	frustum[Back][3] /= magnitude; 


	frustum[Top][0] = clip[ 3] - clip[ 1];
	frustum[Top][1] = clip[ 7] - clip[ 5];
	frustum[Top][2] = clip[11] - clip[ 9];
	frustum[Top][3] = clip[15] - clip[13];
	magnitude = m->MagnitudeVA3(frustum[Top]);
	frustum[Top][0] /= magnitude;
	frustum[Top][1] /= magnitude;
	frustum[Top][2] /= magnitude;
	frustum[Top][3] /= magnitude; 


	frustum[Bottom][0] = clip[ 3] + clip[ 1];
	frustum[Bottom][1] = clip[ 7] + clip[ 5];
	frustum[Bottom][2] = clip[11] + clip[ 9];
	frustum[Bottom][3] = clip[15] + clip[13];
	magnitude = m->MagnitudeVA3(frustum[Bottom]);
	frustum[Bottom][0] /= magnitude;
	frustum[Bottom][1] /= magnitude;
	frustum[Bottom][2] /= magnitude;
	frustum[Bottom][3] /= magnitude;
}

bool rRenderer::IsBoxInFrustum(float x1, float y1, float z1, float x2, float y2, float z2)
{
	for(int i = 0; i < 6; i++ )	
	{
		//Check all Box verticies
		if(frustum[i][0] * x1 + frustum[i][1] * y1 + frustum[i][2] * z1 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x2 + frustum[i][1] * y1 + frustum[i][2] * z1 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x1 + frustum[i][1] * y2 + frustum[i][2] * z1 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x2 + frustum[i][1] * y2 + frustum[i][2] * z1 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x1 + frustum[i][1] * y1 + frustum[i][2] * z2 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x2 + frustum[i][1] * y1 + frustum[i][2] * z2 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x1 + frustum[i][1] * y2 + frustum[i][2] * z2 + frustum[i][3] > 0)
		   continue;
		if(frustum[i][0] * x2 + frustum[i][1] * y2 + frustum[i][2] * z2 + frustum[i][3] > 0)
		   continue;

		return false;
	}
	return true;
}