/*
* opengl programming framework
*
* based on original from Jeff Molofee's tutorial #4
*/
#include "stdafx.h"
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <complex>
#include <vector>
using Complex = std::complex<float>;

// key input, true if key is down. Use windows' VK_* indexes or 'C'
static bool keys[256];
struct particle {
	Complex position, spd;
	float life;

	particle(Complex p, Complex s) {
		position = p;
		spd = s;
		life = 0;
	}

	bool isDead()
	{
		return life > 1;
	}

	void update(float deltaT)
	{
		position += spd * deltaT;
		life += deltaT;
	}

	void draw()
	{
		glPushMatrix();
		glTranslatef(position.real(), position.imag(), 0);
		glScalef(0.1f, 0.1f, 0.1f);
		glBegin(GL_LINES);
		glColor3f(1.0f, 1.0f, 1.0f);
		glVertex3f(0.0f, 0.0f, 0.0f);
		glColor3f(0.0f, 0.0f, 0.0f);
		glVertex3f(-spd.real(), -spd.imag(), 0.0f);
		glEnd();
		glPopMatrix();
	}
};

// global state
static float px = 0;
static float py = 0;
static float vx = 0;
static float vy = 0;
static const float acc = 10;
static std::vector<particle> particles;

void initialize()
{
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClearDepth(1.0f);
	//glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

bool update(float deltaT)
{
	if (keys[VK_RIGHT]) vx += deltaT * acc;
	if (keys[VK_LEFT]) vx -= deltaT * acc;

	if (keys[VK_UP]) vy += deltaT * acc;
	if (keys[VK_DOWN]) vy -= deltaT * acc;

	px += vx * deltaT;
	py += vy * deltaT;

	if (px < -0.9f) {
		px = -0.9f;
		vx *= -1;
	}
	if (px > 0.9f) {
		px = 0.9f;
		vx *= -1;
	}
	if (py < -0.9f) {
		py = -0.9f;
		vy *= -1;
	}
	if (py > 0.9f) {
		py = 0.9f;
		vy *= -1;
	}

	vx *= pow(0.1f, deltaT);
	vy *= pow(0.1f, deltaT);

	for (int i = 0; i < particles.size(); i++)
	{
		particles[i].update(deltaT);
		if (particles[i].isDead())
			particles.erase(particles.begin() + (i--));
	}
	particles.push_back(particle(Complex(px, py), -Complex(vx, vy)));

	return true;
}

void render()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();
	glTranslatef(px, py, -3);
	glScalef(0.1f, 0.1f, 0.1f);
	glColor3f(0.1f, 0.2f, 1.0f);
	glBegin(GL_QUADS);
	glVertex3f(-1.0f, -1.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, 0.0f);
	glVertex3f(1.0f, 1.0f, 0.0f);
	glVertex3f(-1.0f, 1.0f, 0.0f);
	glEnd();
	glLoadIdentity();
	glTranslatef(0, 0, -3);
	glColor3f(1.0f,0.5f,0.0f);
	glBegin(GL_LINE_LOOP);
	glVertex3f(-1.0f, -1.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, 0.0f);
	glVertex3f(1.0f, 1.0f, 0.0f);
	glVertex3f(-1.0f, 1.0f, 0.0f);
	glEnd();
	
	for (int i = 0; i < particles.size(); i++)
	{
		particles[i].draw();
	}
}

// everytime the window gets resized, this function is called with a new size
void handle_resize(int width, int height)
{
	if (height == 0) {
		height = 1;
	}

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	//glFrustrum(-0.4,0.4,-0.3,0.3,1,100);
	gluPerspective(45.0f, (float)width / (float)height, 0.1f, 100.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}


/*
* WINDOWS-SPECIFIC PART
*/

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "gdi32.lib")

static HDC hDC = NULL;
static HGLRC hRC = NULL;
static HWND hWnd = NULL;
static HINSTANCE hInstance;
static bool active = true;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

void KillGLWindow()
{
	if (hRC) {
		if (!wglMakeCurrent(NULL, NULL)) {
			MessageBox(NULL, (LPCWSTR)"Release Of DC And RC Failed.", (LPCWSTR)"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		}

		if (!wglDeleteContext(hRC)) {
			MessageBox(NULL, (LPCWSTR)"Release Rendering Context Failed.", (LPCWSTR)"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		}
		hRC = NULL;
	}

	if (hDC && !ReleaseDC(hWnd, hDC)) {
		MessageBox(NULL, (LPCWSTR)"Release Device Context Failed.", (LPCWSTR)"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		hDC = NULL;
	}

	if (hWnd && !DestroyWindow(hWnd)) {
		MessageBox(NULL, (LPCWSTR)"Could Not Release hWnd.", (LPCWSTR)"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		hWnd = NULL;
	}

	if (!UnregisterClass((LPCWSTR)"OpenGL", hInstance)) {
		MessageBox(NULL, (LPCWSTR)"Could Not Unregister Class.", (LPCWSTR)"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		hInstance = NULL;
	}
}

bool CreateGLWindow(const char* title, int width, int height, unsigned char bits, unsigned char depth)
{
	GLuint PixelFormat;
	WNDCLASS wc;
	DWORD dwExStyle;
	DWORD dwStyle;
	RECT WindowRect;
	WindowRect.left = (long)0;
	WindowRect.right = (long)width;
	WindowRect.top = (long)0;
	WindowRect.bottom = (long)height;

	hInstance = GetModuleHandle(NULL);
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc = (WNDPROC)WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = NULL;
	wc.lpszMenuName = NULL;
	wc.lpszClassName = (LPCWSTR)"OpenGL";

	if (!RegisterClass(&wc)) {
		MessageBox(NULL, (LPCWSTR)"Failed To Register The Window Class.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
	dwStyle = WS_OVERLAPPEDWINDOW;

	AdjustWindowRectEx(&WindowRect, dwStyle, false, dwExStyle);


	if (!(hWnd = CreateWindowEx(dwExStyle,
		(LPCWSTR)"OpenGL",
		(LPCWSTR)title,
		dwStyle |
		WS_CLIPSIBLINGS |
		WS_CLIPCHILDREN,
		0, 0,
		WindowRect.right - WindowRect.left,
		WindowRect.bottom - WindowRect.top,
		NULL,
		NULL,
		hInstance,
		NULL))) {
		KillGLWindow();
		MessageBox(NULL, (LPCWSTR)"Window Creation Error.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	static PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW |
		PFD_SUPPORT_OPENGL |
		PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA,
		bits,
		0, 0, 0, 0, 0, 0,
		0,
		0,
		0,
		0, 0, 0, 0,
		depth,
		0,
		0,
		PFD_MAIN_PLANE,
		0,
		0, 0, 0
	};

	if (!(hDC = GetDC(hWnd))) {
		KillGLWindow();
		MessageBox(NULL, (LPCWSTR)"Can't Create A GL Device Context.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) {
		KillGLWindow();
		MessageBox(NULL, (LPCWSTR)"Can't Find A Suitable PixelFormat.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	if (!SetPixelFormat(hDC, PixelFormat, &pfd)) {
		KillGLWindow();
		MessageBox(NULL, (LPCWSTR)"Can't Set The PixelFormat.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	if (!(hRC = wglCreateContext(hDC))) {
		KillGLWindow();
		MessageBox(NULL, (LPCWSTR)"Can't Create A GL Rendering Context.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	if (!wglMakeCurrent(hDC, hRC)) {
		KillGLWindow();
		MessageBox(NULL, (LPCWSTR)"Can't Activate The GL Rendering Context.", (LPCWSTR)"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	ShowWindow(hWnd, SW_SHOW);
	SetForegroundWindow(hWnd);
	SetFocus(hWnd);
	handle_resize(width, height);

	return true;
}

LRESULT CALLBACK WndProc(HWND hWnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam)
{
	switch (uMsg) {
	case WM_ACTIVATE: {
		if (!HIWORD(wParam)) {
			active = true;
		}
		else {
			active = false;
		}

		return 0;
	}

	case WM_SYSCOMMAND: {
		switch (wParam) {
		case SC_SCREENSAVE:
		case SC_MONITORPOWER:
			return 0;
		}
		break;
	}

	case WM_CLOSE: {
		PostQuitMessage(0);
		return 0;
	}

	case WM_KEYDOWN: {
		keys[wParam] = true;
		return 0;
	}

	case WM_KEYUP: {
		keys[wParam] = false;
		return 0;
	}

	case WM_SIZE: {
		handle_resize(LOWORD(lParam), HIWORD(lParam));
		return 0;
	}
	}


	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nCmdShow)
{
	bool done = false;

	LARGE_INTEGER timer_freq, timer_last;
	QueryPerformanceFrequency(&timer_freq);

	if (!CreateGLWindow("OpenGL", 800, 600, 32, 16)) {
		return 0;
	}

	initialize();

	QueryPerformanceCounter(&timer_last);
	while (!done) {
		MSG msg;
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			if (msg.message == WM_QUIT) {
				done = true;
			}
			else {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			continue;
		}

		LARGE_INTEGER timer_now;
		QueryPerformanceCounter(&timer_now);
		float timeDiff = (timer_now.QuadPart - timer_last.QuadPart) / (float)timer_freq.QuadPart;
		timer_last = timer_now;
		update(timeDiff);

		if (active) render();
		if (keys[VK_ESCAPE]) done = true;
		SwapBuffers(hDC);
	}


	KillGLWindow();
	return 0;
}
