// =============================================================================
// -----------------------------------------------------------------------------
// Monitor (OpenGL)
// -----------------------------------------------------------------------------

#include <stdio.h>
#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <math.h>
#include <direct.h>
#include "bitmap.h"

#define rangeof(ENTRY) (0x01<<(0x08*sizeof(ENTRY)))

#define StringSize 0x1000
char String [StringSize];
int StringPos;

#define L 0x1000
char T [L];
int P;

const char ProgName [] = "Monitor";
const char ProgWinClass [] = "Window Class";
RECT Rect = { 0, 0, 320, 224 };
int WindowStyles = WS_THICKFRAME | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;

int ProgramTimer = 0x01;

#define HELD 0
#define PRESSED rangeof(u_char)
bool Keys [rangeof (u_char)*2];

HDC hdc;
HGLRC hrc;

// =============================================================================
// -----------------------------------------------------------------------------
// Subroutine to enable Open GL
// -----------------------------------------------------------------------------

void EnableOpenGL (HWND hwnd)

{
	PIXELFORMATDESCRIPTOR pfd;
	int iFormat;

	/* get the device context (DC) */

	hdc = GetDC (hwnd);

	/* set the pixel format for the DC */

	ZeroMemory (&pfd, sizeof (pfd));

	pfd.nSize	= sizeof (pfd);
	pfd.nVersion	= 1;
	pfd.dwFlags	= PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType	= PFD_TYPE_RGBA;
	pfd.cColorBits	= 32;
	pfd.cDepthBits	= 16;
	pfd.iLayerType	= PFD_MAIN_PLANE;

	iFormat = ChoosePixelFormat (hdc, &pfd);
	SetPixelFormat (hdc, iFormat, &pfd);

	/* create and enable the render context (RC) */

	hrc = wglCreateContext (hdc);
	wglMakeCurrent (hdc, hrc);

	/* Use depth buffering for hidden surface elimination. */

	glEnable (GL_DEPTH_TEST);
	glEnable (GL_TEXTURE_2D); 						// set to enable 2D Textures
	glEnable (GL_BLEND);							// set to enable blending
}

// =============================================================================
// -----------------------------------------------------------------------------
// Subroutine to disable Open GL
// -----------------------------------------------------------------------------

void DisableOpenGL (HWND hwnd)

{
	wglMakeCurrent (NULL, NULL);
	wglDeleteContext (hrc);
	ReleaseDC (hwnd, hdc);
}

// =============================================================================
// -----------------------------------------------------------------------------
// Variables/constants for OpenGL...
// -----------------------------------------------------------------------------

float CameraX;
float CameraY;
float CameraZ;
float DestX;
float DestY;
float DestZ;
float ScreenAspect;
int ScreenWidth, ScreenHeight, ScreenX, ScreenY;
float CameraPerspect = 45.0;
float CameraAngle = 0.0;
float Thickness = 4.0;
float Length = 1.0;

bool SetupView = FALSE;
bool SetupTextures = FALSE;
char FilmMode = 0;
int PrevZ = 0;
int FramePos = 0;

const char MonitorFiles [] [29] = {	"Textures/Monitor Film.bmp\0  ",
					"Textures/Monitor Front.bmp\0 ",
					"Textures/Monitor Sides.bmp\0 ",
					"Textures/Monitor Back.bmp\0  ",
					"Textures/Monitor top.bmp\0   ",
					"Textures/Monitor bottom.bmp\0",
					"Textures/Monitor Stand.bmp\0 ",

					"\0                           " };

#define MonitorSides ((sizeof (MonitorFiles) / sizeof (*MonitorFiles)) - 1)

GLuint TXR_Monitor [MonitorSides];

	// monitor face file/texture IDs

#define FACE_FILM 0
#define FACE_FRONT 1
#define FACE_SIDES 2
#define FACE_BACK 3
#define FACE_TOP 4
#define FACE_BOTTOM 5
#define FACE_STAND 6

	// width/height/length of textures for monitor faces and stand

#define MONX 54.0
#define MONY 44.0
#define MONZ 44.0

#define STAX (24.0+2.0)	// The +2.0 is simply because it's further back than the front of the screen, and that needs accounting for.
#define STAY  6.0

#define MOVEZ 0.0	// 4.0 for preview, 0.0 for actual rendering (to keep it still)

	// Camera stuff~

#define LAST_Z (400.0 + ((MONZ-4.0)/5)) // donno why /5 instead of /2, probably logarithmic, but whatever I'm tired...

// =============================================================================
// -----------------------------------------------------------------------------
// Subroutine to draw graphics using OpenGL
// -----------------------------------------------------------------------------

void DrawGraphics (HWND hwnd)

{
	RECT RectClient;
	GetClientRect (hwnd, &RectClient);

	ScreenWidth = RectClient.right-RectClient.left;
	ScreenHeight = RectClient.bottom-RectClient.top;
	ScreenX = 0;
	ScreenY = -((RectClient.right-RectClient.left) - (RectClient.bottom-RectClient.top)) / 2;	// THIS SHIFTS DOWN BY HALF THE MISSING WIDTH NOW!!
	ScreenAspect = (float) ScreenWidth / (float) ScreenWidth;   // NOTE!!  HEIGHT IS SAME AS WIDTH SO PIXEL FORMAT IS PERFECT SQUARE!!!

	// --- Setting Open GL screen/camera ---

	glViewport (ScreenX, ScreenY, ScreenWidth, ScreenWidth);   // NOTE!!  HEIGHT IS SAME AS WIDTH SO PIXEL FORMAT IS PERFECT SQUARE!!!

	if (SetupView == FALSE)
	{
		SetupView = TRUE;

		CameraX = 0.0;
		CameraY = 0.0;
		CameraZ = LAST_Z;
		DestX = 0.0;
		DestY = 0.0;
		DestZ = 0.0;

		CameraAngle = 0.0;
	}

	if (Keys ['W'+HELD] == TRUE)
	{
		if (CameraZ > 0.0)
		{
			CameraZ -= 10.0;
			CameraX -= 8.0;
			DestZ -= MOVEZ;
		}
	}
	else if (Keys ['S'+HELD] == TRUE)
	{
		CameraZ += 10.0;
		if (CameraZ >= LAST_Z)
		{
			CameraZ = LAST_Z;
			CameraX = 0.0;
			DestZ = 0.0;
		}
		else
		{
			CameraX += 8.0;
			DestZ += MOVEZ;
		}
	}
	if (Keys [VK_TAB+PRESSED] == TRUE)
	{
		FilmMode++;
	}

	// Getting angle the camera is facing...
	CameraAngle = (atan2 (CameraX-DestX, CameraZ-DestZ) * 180 / 3.14159265) + 90.0;

	glMatrixMode (GL_MODELVIEW);						// set mode to GL_MODELVIEW
	glLoadIdentity ( );							// load GL_MODELVIEW's current setting
	glPushMatrix ( );							// store GL_MODELVIEW's current setting (just temporarily editing it)
	gluLookAt (		CameraX,CameraY,CameraZ,			// eye position
				DestX, DestY, DestZ,				// center position
				0.0, 1.0, 0.0);					// up is in positive Y direction

	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);

	// --- Setting up textures ---

	if (SetupTextures == FALSE)
	{
		SetupTextures = TRUE;
		for (int Loc = 0; Loc < MonitorSides; Loc++)
		{
			IMG Image;
			ImageLoad (&Image, (char*) MonitorFiles [Loc]);
			for (int Pos = 0; Pos < Image.Size; Pos++)
			{
				if (	Image.Data [Pos].Red   == 0x00	&&
					Image.Data [Pos].Green == 0x80	&&
					Image.Data [Pos].Blue  == 0x00	)
				{
					Image.Data [Pos].Alpha = 0x00;
				}
			}
			glGenTextures (0x01, &TXR_Monitor [Loc]);
			glBindTexture (GL_TEXTURE_2D, TXR_Monitor [Loc]);
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);	// GL_LINEAR = bluring | GL_NEAREST = Non-bluring (MUST USE "i" not "f")
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);	// GL_LINEAR = bluring | GL_NEAREST = Non-bluring (MUST USE "i" not "f")
			glTexImage2D (GL_TEXTURE_2D, 0x00, 0x04, Image.SizeX, Image.SizeY, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, Image.Data);
			free (Image.Data); Image.Data = NULL;
		}

	}

	// --- The polygon rendering itself ---

	glClearColor (0.0, 0.5, 0.0, 1.0);	// BG colour B, G, R, A
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//	glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);				// set alpha type to a soft blend type
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);			// set alpha type to direct transparent


	glMatrixMode (GL_PROJECTION);				// set mode to GL_PROJECTION
	glLoadIdentity ( );					// load GL_PROJECTION's current setting
	glPushMatrix ( );					// store GL_PROJECTION's current setting (just temporarily editing it)
	gluPerspective (	45.0,				// field of view in degree
				Rect.right/Rect.bottom,	//4/3,	// aspect ratio (set to size of screen, ensures no stretching)
				1.0,				// Z near
				2560.0);			// Z far


	if ((FilmMode & 1) == 0)
	{
		glDisable (GL_CULL_FACE);

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_STAND]);
		glBegin (GL_QUADS);	// Making sure the stand is always facing the camera...
		glTexCoord2i (0, 0); glVertex3f (sin ((CameraAngle*3.14159265)/180) * (-STAX/2), (-MONY/2)-STAY, cos ((CameraAngle*3.14159265)/180) * (-STAX/2));
		glTexCoord2i (1, 0); glVertex3f (sin ((CameraAngle*3.14159265)/180) * ( STAX/2), (-MONY/2)-STAY, cos ((CameraAngle*3.14159265)/180) * ( STAX/2));
		glTexCoord2i (1, 1); glVertex3f (sin ((CameraAngle*3.14159265)/180) * ( STAX/2), (-MONY/2), cos ((CameraAngle*3.14159265)/180) * ( STAX/2));
		glTexCoord2i (0, 1); glVertex3f (sin ((CameraAngle*3.14159265)/180) * (-STAX/2), (-MONY/2), cos ((CameraAngle*3.14159265)/180) * (-STAX/2));
		glEnd ( );

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_FRONT]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f ((-MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (1, 0); glVertex3f (( MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (1, 1); glVertex3f (( MONX/2), ( MONY/2), ( MONZ/2));
		glTexCoord2i (0, 1); glVertex3f ((-MONX/2), ( MONY/2), ( MONZ/2));
		glEnd ( );

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_SIDES]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f (( MONX/2), (-MONY/2), (-MONZ/2));
		glTexCoord2i (1, 0); glVertex3f (( MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (1, 1); glVertex3f (( MONX/2), ( MONY/2), ( MONZ/2));
		glTexCoord2i (0, 1); glVertex3f (( MONX/2), ( MONY/2), (-MONZ/2));
		glEnd ( );

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_SIDES]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f ((-MONX/2), (-MONY/2), (-MONZ/2));
		glTexCoord2i (1, 0); glVertex3f ((-MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (1, 1); glVertex3f ((-MONX/2), ( MONY/2), ( MONZ/2));
		glTexCoord2i (0, 1); glVertex3f ((-MONX/2), ( MONY/2), (-MONZ/2));
		glEnd ( );

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_BACK]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f ((-MONX/2), (-MONY/2), (-MONZ/2));
		glTexCoord2i (1, 0); glVertex3f (( MONX/2), (-MONY/2), (-MONZ/2));
		glTexCoord2i (1, 1); glVertex3f (( MONX/2), ( MONY/2), (-MONZ/2));
		glTexCoord2i (0, 1); glVertex3f ((-MONX/2), ( MONY/2), (-MONZ/2));
		glEnd ( );

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_TOP]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f ((-MONX/2), ( MONY/2), (-MONZ/2));
		glTexCoord2i (1, 0); glVertex3f (( MONX/2), ( MONY/2), (-MONZ/2));
		glTexCoord2i (1, 1); glVertex3f (( MONX/2), ( MONY/2), ( MONZ/2));
		glTexCoord2i (0, 1); glVertex3f ((-MONX/2), ( MONY/2), ( MONZ/2));
		glEnd ( );

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_BOTTOM]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f ((-MONX/2), (-MONY/2), (-MONZ/2));
		glTexCoord2i (1, 0); glVertex3f (( MONX/2), (-MONY/2), (-MONZ/2));
		glTexCoord2i (1, 1); glVertex3f (( MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (0, 1); glVertex3f ((-MONX/2), (-MONY/2), ( MONZ/2));
		glEnd ( );
	}
	else
	{
		glEnable (GL_CULL_FACE);

		glBindTexture (GL_TEXTURE_2D, TXR_Monitor [FACE_FILM]);
		glBegin (GL_QUADS);
		glTexCoord2i (0, 0); glVertex3f ((-MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (1, 0); glVertex3f (( MONX/2), (-MONY/2), ( MONZ/2));
		glTexCoord2i (1, 1); glVertex3f (( MONX/2), ( MONY/2), ( MONZ/2));
		glTexCoord2i (0, 1); glVertex3f ((-MONX/2), ( MONY/2), ( MONZ/2));
		glEnd ( );

	}

	// --- Taking a screenshot ---

	if (Keys [VK_SPACE] == TRUE && CameraZ != PrevZ)
	{
		PrevZ = CameraZ;
		IMG Image;
		Image.SizeX = ScreenWidth;
		Image.SizeY = ScreenHeight;
		Image.Size = Image.SizeX * Image.SizeY;
		Image.Data = (PIX_BGRA*) malloc (Image.Size * sizeof (PIX_BGRA));
		glReadPixels (0, 0, ScreenWidth, ScreenHeight, GL_BGRA_EXT, GL_UNSIGNED_BYTE, Image.Data);
		if (FilmMode == 0)
		{
			StringPos = snprintf (String, StringSize,"Output Monitor/");
		}
		else
		{
			StringPos = snprintf (String, StringSize,"Output Film/");
		}
		mkdir (String);
		snprintf (&String[StringPos],StringSize-StringPos,"%0.3d.bmp", ++FramePos);
		SaveBMP (&Image, String, 24);
		free (Image.Data); Image.Data = NULL;
	}

	SwapBuffers (hdc);
	glPopMatrix ( );							// restore GL_PROJECTION's current setting
	glPopMatrix ( );							// restore GL_MODELVIEW's current setting
}

// =============================================================================
// -----------------------------------------------------------------------------
// Window Procedure
// -----------------------------------------------------------------------------

LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{
	switch (msg)
	{
		case WM_CREATE:
		{
			EnableOpenGL (hwnd);
			if (SetTimer (hwnd, ProgramTimer, ((10*100)/60), NULL) == 0x00)
			{
				MessageBox (hwnd, "Could not set the frame timer", "Error", MB_OK | MB_ICONEXCLAMATION);
			}
		}
		break;
		case WM_TIMER:
		{
			InvalidateRect (hwnd, NULL, TRUE);
		}
		break;
		case WM_KEYDOWN:
		{
			Keys [wParam] = TRUE;
			Keys [wParam+PRESSED] = TRUE;
		}
		break;
		case WM_KEYUP:
		{
			Keys [wParam] = FALSE;
			Keys [wParam+PRESSED] = FALSE;
		}
		break;
		case WM_PAINT:
		{
			DrawGraphics (hwnd);
			ValidateRect (hwnd, NULL);
			for (int Loc = PRESSED; Loc < PRESSED + rangeof (u_char); Loc++)
			{
				Keys [Loc] = FALSE;
			}
		}
		break;
		case WM_CLOSE:
		{
			DisableOpenGL (hwnd);
			DestroyWindow (hwnd);
		}
		break;
		case WM_DESTROY:
		{
			KillTimer (hwnd, ProgramTimer);
			PostQuitMessage (0x00);
		}
		break;
		default:
		{
			return DefWindowProc (hwnd, msg, wParam, lParam);
		}
		break;
	}
	return 0;
}

// =============================================================================
// -----------------------------------------------------------------------------
// Main Routine
// -----------------------------------------------------------------------------

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{
	printf ("Monitor renderer.\n\n"
		"\n  Render controls:\n\n"
		"	Space       = Dump a screenshot\n"
		"\n  Model view controls:\n\n"
		"	Tab         = Model type (Monitor | Screen/film)\n"
		"\n  Player view controls:\n\n"
		"	W/S         = play animation forwards/backwards\n");

	WNDCLASSEX wc;
	HWND hwnd = NULL;
	MSG Msg;
	wc.cbSize		= sizeof (WNDCLASSEX);
	wc.style		= 0;
	wc.lpfnWndProc		= WndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= hInstance;
	wc.hIcon		= NULL;
	wc.hCursor		= LoadCursor (NULL, IDC_ARROW);
	wc.hbrBackground	= (HBRUSH) (COLOR_WINDOW+1);
	wc.lpszMenuName		= NULL;
	wc.lpszClassName	= ProgWinClass;
	wc.hIconSm		= NULL;

	if (!RegisterClassEx (&wc))
	{
		MessageBox (NULL, "Window Registration Failed", "Error", MB_ICONEXCLAMATION | MB_OK);
		return (0x00);
	}

	AdjustWindowRect (&Rect, WindowStyles, FALSE);
	hwnd = CreateWindow (ProgWinClass, ProgName, WindowStyles, CW_USEDEFAULT, CW_USEDEFAULT, Rect.right - Rect.left, Rect.bottom - Rect.top, NULL, NULL, hInstance, NULL);

	if (hwnd == NULL)
	{
		MessageBox (NULL, "Window Creation Failed", "Error", MB_ICONEXCLAMATION | MB_OK);
		return (0x00);
	}

	ShowWindow (hwnd, nCmdShow);
	UpdateWindow (hwnd);

	while (GetMessage (&Msg, NULL, 0, 0) > 0)
	{
		TranslateMessage (&Msg);
		DispatchMessage (&Msg);
	}

	return Msg.wParam;
}

// =============================================================================
