#include <windows.h>            // Window defines
#include <gl\gl.h>              // OpenGL
#include <gl\glu.h>             // GLU library
#include <time.h>
#include <stdio.h>

const GLfloat rad = 1.0;
const float XMIN = -36.0f;
const float XMAX = 36.0f;
const float YMIN = -72.0f;
const float YMAX = 72.0f;
const float DT = 0.1f;

const int ballres = 20;

class Ball {
public:
	Ball(float px, float py, float vx, float vy, int col) :
	  px(px), py(py), vx(vx), vy(vy), col(col)
	  { }
    void move();
	float x() { return px; }
	float y() { return py; }
	int c() { return col; }
private:
	float px;
	float py;
	float vx;
	float vy;
	int col;
};

void Ball::move()
{
	if (px < XMIN || px > XMAX) 
		vx = - vx;
	if (py < YMIN || py > YMAX)
		vy = - vy;
	px += vx * DT;
	py += vy * DT;
}

GLUquadricObj *q;
GLfloat ball_ambient[] = { 0.5, 0.5, 0.5, 1.0 };

const int NUMBALLS = 22;
Ball *balls[NUMBALLS];
GLfloat colours[][4] = 
{ 
	{ 1.0f, 1.0f, 1.0f, 1.0f },	// 0 white
	{ 1.0f, 0.0f, 0.0f, 1.0f },	// 1 red
	{ 0.9f, 0.9f, 0.0f, 1.0  },	// 2 yellow
	{ 0.0f, 1.0f, 0.0f, 1.0f }, // 3 green
	{ 0.4f, 0.4f, 0.0f, 1.0f },	// 4 brown
	{ 0.0f, 0.0f, 1.0f, 1.0f },	// 5 blue
	{ 1.0f, 0.8f, 0.5f, 1.0f },	// 6 pink
	{ 0.0f, 0.0f, 0.0f, 1.0f }	// 7 black
};

HPALETTE hPalette = NULL;

// Application name and instance storeage
static LPCTSTR lpszAppName = "Balls";
static HINSTANCE hInstance;

// Rotation amounts
static GLfloat xRot = -50.0f;
static GLfloat yRot = 0.0f;
static float fRot = 0.0f;

// Mouse coordinates
int xm = 0;
int ym = 0;

// Declaration for Window procedure
LRESULT CALLBACK WndProc(   HWND    hWnd,
							UINT    message,
							WPARAM  wParam,
							LPARAM  lParam);

// Dialog procedure for about box
BOOL APIENTRY AboutDlgProc (HWND hDlg, UINT message, UINT wParam, LONG lParam);

// Set Pixel Format function - forward declaration
void SetDCPixelFormat(HDC hDC);

// Viewing distance
GLfloat dist = -100.0f;

int sw, sh;

// Change viewing volume and viewport.  Called when window is resized
void ChangeSize(GLsizei w, GLsizei h)
	{
	sw = w;
	sh = h;

	GLfloat nRange = 100.0f;
	GLfloat fAspect;

	if(h == 0)
		h = 1;

	fAspect = (GLfloat)w/(GLfloat)h;
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f, fAspect, 20, 600);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	}


// Initialize the Rendering Context
void SetupRC(void)
	{
	glEnable(GL_LIGHT0);
//	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHTING);

	q = gluNewQuadric();
	gluQuadricDrawStyle(q, GLU_FILL);
	gluQuadricNormals(q, GLU_SMOOTH);
 
	balls[ 0] = new Ball(0.0f, 0.0f, 1.0f, 2.1f, 0);
	balls[ 1] = new Ball(0.1f, 1.0f, 2.1f, 2.2f, 2);
	balls[ 2] = new Ball(0.2f, 2.0f, 3.2f, 3.3f, 3);
	balls[ 3] = new Ball(0.3f, 3.0f, 2.3f, 2.4f, 4);
	balls[ 4] = new Ball(0.4f, 4.0f, 5.4f, 3.5f, 5);
	balls[ 5] = new Ball(0.5f, 5.0f, 3.5f, 4.6f, 6);
	balls[ 6] = new Ball(1.6f, 6.0f, 2.6f, 2.7f, 7);
	balls[ 7] = new Ball(2.7f, 7.0f, 9.7f, 1.8f, 1);
	balls[ 8] = new Ball(2.8f, 8.0f, 2.8f, 2.9f, 1);
	balls[ 9] = new Ball(2.9f, 9.0f, 1.9f, 1.3f, 1);
	balls[10] = new Ball(0.0f, 1.5f, 1.0f, 2.4f, 1);
	balls[11] = new Ball(0.1f, 2.5f, 2.1f, 2.5f, 1);
	balls[12] = new Ball(0.2f, 3.5f, 7.2f, 9.6f, 1);
	balls[13] = new Ball(0.3f, 4.5f, 2.3f, 2.7f, 1);
	balls[14] = new Ball(0.4f, 5.5f, 5.4f, 3.8f, 1);
	balls[15] = new Ball(0.5f, 6.5f, 3.5f, 4.9f, 1);
	balls[16] = new Ball(1.6f, 7.5f, 2.6f, 2.0f, 1);
	balls[17] = new Ball(2.7f, 8.5f, 4.7f, 1.3f, 1);
	balls[18] = new Ball(2.8f, 9.5f, 2.8f, 2.4f, 1);
	balls[19] = new Ball(2.9f, 5.1f, 1.9f, 1.5f, 1);
	balls[20] = new Ball(2.8f, 6.2f, 2.8f, 2.6f, 1);
	balls[21] = new Ball(2.9f, 7.3f, 21.9f, 9.7f, 1);

	glEnable(GL_DEPTH_TEST);
	}


GLfloat xl = 0.0f, yl = 0.0f, zl = 0.0f;
GLfloat lo_amb = 0.2f;
GLfloat hi_amb = 0.8f;

// Called to draw scene
void RenderScene(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPushMatrix();
	glTranslatef(0.0f, 0.0f, dist);
	glRotatef(xRot, 1.0f, 0.0f, 0.0f);
	glRotatef(yRot, 0.0f, 0.0f, 1.0f);

	GLfloat light0pos[] = { 1.0, 1.0, 4.0, 0.0 };
	glLightfv(GL_LIGHT0, GL_POSITION, light0pos);

//	GLfloat light1pos[] = { 0.0, -2.0, 1.0, 0.0 };
//	glLightfv(GL_LIGHT1, GL_POSITION, light1pos);

	GLfloat ambient[] = { hi_amb, hi_amb, hi_amb, 1.0f };
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);

	GLfloat cloth_ambient[] = { 0.0f, 1.0f, 0.0f, 1.0f };
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, cloth_ambient);

	GLfloat cloth_specular[] = { 0.0f, 1.0f, 0.0f, 1.0f };
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, cloth_specular);
	
	GLfloat cloth_shininess[] = { 10.0 };
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, cloth_shininess);

	glNormal3f(0.0f, 0.0f, 1.0f);
	glBegin(GL_POLYGON);
		glVertex3f(XMIN-rad, YMIN-rad, -rad);
		glVertex3f(XMIN-rad, YMAX+rad, -rad);
		glVertex3f(XMAX+rad, YMAX+rad, -rad);
		glVertex3f(XMAX+rad, YMIN-rad, -rad);
	glEnd();

	GLfloat height = 0.4f * rad;

	GLfloat cush_ambient[] = { 0.0f, 0.8f, 0.2f, 1.0f };
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, cush_ambient);

	glNormal3f(-1.0f, 0.0f, 0.0f);
	glBegin(GL_POLYGON);
		glVertex3f(XMAX+rad, YMAX+rad, -rad);
		glVertex3f(XMAX+rad, YMIN-rad, -rad);
		glVertex3f(XMAX+rad, YMIN-rad, height);
		glVertex3f(XMAX+rad, YMAX+rad, height);
	glEnd();
	
	glNormal3f(1.0f, 0.0f, 0.0f);
	glBegin(GL_POLYGON);
		glVertex3f(XMIN-rad, YMIN-rad, -rad);
		glVertex3f(XMIN-rad, YMAX+rad, -rad);
		glVertex3f(XMIN-rad, YMAX+rad, height);
		glVertex3f(XMIN-rad, YMIN-rad, height);
	glEnd();
	
	glNormal3f(0.0f, -1.0f, 0.0f);
	glBegin(GL_POLYGON);
		glVertex3f(XMIN-rad, YMAX+rad, -rad);
		glVertex3f(XMAX+rad, YMAX+rad, -rad);
		glVertex3f(XMAX+rad, YMAX+rad, height);
		glVertex3f(XMIN-rad, YMAX+rad, height);
	glEnd();
	
	glNormal3f(0.0f, 1.0f, 0.0f);
	glBegin(GL_POLYGON);
		glVertex3f(XMAX+rad, YMIN-rad, -rad);
		glVertex3f(XMIN-rad, YMIN-rad, -rad);
		glVertex3f(XMIN-rad, YMIN-rad, height);
		glVertex3f(XMAX+rad, YMIN-rad, height);
	glEnd();

	GLfloat ball_ambient[] = { lo_amb, lo_amb, lo_amb, 1.0f };
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ball_ambient);

	GLfloat ball_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	glMaterialfv(GL_FRONT, GL_SPECULAR, ball_specular);

	GLfloat ball_shininess[] = { 120.0 };
	glMaterialfv(GL_FRONT, GL_SHININESS, ball_shininess);

	for (int b = 0; b < NUMBALLS; b++)
	{
		glPushMatrix();
		glTranslatef(balls[b]->x(), balls[b]->y(), 0.0f);
		glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, colours[balls[b]->c()]);
		gluSphere(q, rad, ballres, ballres);
		glPopMatrix();
	}

	glPopMatrix();
}


// Select the pixel format for a given device context
void SetDCPixelFormat(HDC hDC)
	{
	int nPixelFormat;

	static PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),  // Size of this structure
		1,                              // Version of this structure    
		PFD_DRAW_TO_WINDOW |            // Draw to Window (not to bitmap)
		PFD_SUPPORT_OPENGL |			// Support OpenGL calls in window
		PFD_DOUBLEBUFFER,               // Double buffered
		PFD_TYPE_RGBA,                  // RGBA Color mode
		24,                             // Want 24bit color if available
		0,0,0,0,0,0,                    // Not used to select mode
		0,0,                            // Not used to select mode
		0,0,0,0,0,                      // Not used to select mode
		16,                             // Size of depth buffer (16 is sufficient)
		0,                              // Not used to select mode
		0,                              // Not used to select mode
		PFD_MAIN_PLANE,                 // Draw in main plane
		0,                              // Not used to select mode
		0,0,0 };                        // Not used to select mode

	// Choose a pixel format that best matches that described in pfd
	nPixelFormat = ChoosePixelFormat(hDC, &pfd);

	// Set the pixel format for the device context
	SetPixelFormat(hDC, nPixelFormat, &pfd);
	}



// If necessary, creates a 3-3-2 palette for the device context listed.
// (This is for support of 256 color rendering modes). It's not an exact
// match, but it is pretty close and easy to produce
HPALETTE GetOpenGLPalette(HDC hDC)
	{
	HPALETTE hRetPal = NULL;	// Handle to palette to be created
	PIXELFORMATDESCRIPTOR pfd;	// Pixel Format Descriptor
	LOGPALETTE *pPal;			// Pointer to memory for logical palette
	int nPixelFormat;			// Pixel format index
	int nColors;				// Number of entries in palette
	int i;						// Counting variable
	BYTE RedRange,GreenRange,BlueRange;
								// Range for each color entry (7,7,and 3)


	// Get the pixel format index and retrieve the pixel format description
	nPixelFormat = GetPixelFormat(hDC);
	DescribePixelFormat(hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

	// Does this pixel format require a palette?  If not, do not create a
	// palette and just return NULL
	if(!(pfd.dwFlags & PFD_NEED_PALETTE))
		return NULL;

	// Number of entries in palette.  8 bits yeilds 256 entries
	nColors = 1 << pfd.cColorBits;	

	// Allocate space for a logical palette structure plus all the palette entries
	pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +nColors*sizeof(PALETTEENTRY));

	// Fill in palette header 
	pPal->palVersion = 0x300;		// Windows 3.0
	pPal->palNumEntries = nColors; // table size

	// Build mask of all 1's.  This creates a number represented by having
	// the low order x bits set, where x = pfd.cRedBits, pfd.cGreenBits, and
	// pfd.cBlueBits.  
	RedRange = (1 << pfd.cRedBits) -1;
	GreenRange = (1 << pfd.cGreenBits) - 1;
	BlueRange = (1 << pfd.cBlueBits) -1;

	// Loop through all the palette entries
	for(i = 0; i < nColors; i++)
		{
		// Fill in the 8-bit equivalents for each component
		pPal->palPalEntry[i].peRed = (i >> pfd.cRedShift) & RedRange;
		pPal->palPalEntry[i].peRed = (unsigned char)(
			(double) pPal->palPalEntry[i].peRed * 255.0 / RedRange);

		pPal->palPalEntry[i].peGreen = (i >> pfd.cGreenShift) & GreenRange;
		pPal->palPalEntry[i].peGreen = (unsigned char)(
			(double)pPal->palPalEntry[i].peGreen * 255.0 / GreenRange);

		pPal->palPalEntry[i].peBlue = (i >> pfd.cBlueShift) & BlueRange;
		pPal->palPalEntry[i].peBlue = (unsigned char)(
			(double)pPal->palPalEntry[i].peBlue * 255.0 / BlueRange);

		pPal->palPalEntry[i].peFlags = (unsigned char) NULL;
		}
		

	// Create the palette
	hRetPal = CreatePalette(pPal);

	// Go ahead and select and realize the palette for this device context
	SelectPalette(hDC,hRetPal,FALSE);
	RealizePalette(hDC);

	// Free the memory used for the logical palette structure
	free(pPal);

	// Return the handle to the new palette
	return hRetPal;
	}


///////////////////////////////////////////////////////////////////////////////
// Entry point of all Windows programs
int APIENTRY WinMain(   HINSTANCE       hInst,
						HINSTANCE       hPrevInstance,
						LPSTR           lpCmdLine,
						int                     nCmdShow)
	{
	MSG                     msg;            // Windows message structure
	WNDCLASS        wc;                     // Windows class structure
	HWND            hWnd;           // Storeage for window handle

	hInstance = hInst;

	// Register Window style
	wc.style                = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc          = (WNDPROC) WndProc;
	wc.cbClsExtra           = 0;
	wc.cbWndExtra           = 0;
	wc.hInstance            = hInstance;
	wc.hIcon                = NULL;
	wc.hCursor              = LoadCursor(NULL, IDC_ARROW);
	
	// No need for background brush for OpenGL window
	wc.hbrBackground        = NULL;         
	
	wc.lpszMenuName         = NULL;
	wc.lpszClassName        = lpszAppName;

	// Register the window class
	if(RegisterClass(&wc) == 0)
		return FALSE;


	// Create the main application window
	hWnd = CreateWindow(
				lpszAppName,
				lpszAppName,
				
				// OpenGL requires WS_CLIPCHILDREN and WS_CLIPSIBLINGS
				WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
	
				// Window position and size
				50, 50,
				600, 600,
				NULL,
				NULL,
				hInstance,
				NULL);

	// If window was not created, quit
	if(hWnd == NULL)
		return FALSE;


	// Display the window
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);

	// Process application messages until the application closes
	while( GetMessage(&msg, NULL, 0, 0))
		{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		}

	return msg.wParam;
	}




// Window procedure, handles all messages for this program
LRESULT CALLBACK WndProc(       HWND    hWnd,
							UINT    message,
							WPARAM  wParam,
							LPARAM  lParam)
	{
	static HGLRC hRC;               // Permenant Rendering context
	static HDC hDC;                 // Private GDI Device context


	switch (message)
		{
		// Window creation, setup for OpenGL
		case WM_CREATE:
			// Store the device context
			hDC = GetDC(hWnd);              

			// Select the pixel format
			SetDCPixelFormat(hDC);          

			// Create a palette if needed
			hPalette = GetOpenGLPalette(hDC);

			// Create the rendering context and make it current
			hRC = wglCreateContext(hDC);
			wglMakeCurrent(hDC, hRC);

			// Setup OpenGL State
			SetupRC();

			break;

		// Window is being destroyed, cleanup
		case WM_DESTROY:
			// Deselect the current rendering context and delete it
			wglMakeCurrent(hDC,NULL);
			wglDeleteContext(hRC);
			ReleaseDC(hWnd, hDC);

			// Delete the palette if it was created
			if(hPalette != NULL)
				DeleteObject(hPalette);

			// Tell the application to terminate after the window
			// is gone.
			PostQuitMessage(0);
			break;

		// Window is resized.
		case WM_SIZE:
			// Call our function which modifies the clipping
			// volume and viewport
			ChangeSize(LOWORD(lParam), HIWORD(lParam));
			break;


		// The painting function.  This message sent by Windows 
		// whenever the screen needs updating.
		case WM_PAINT:
			{
			static int nFrameCount = 0;
			static char cOutBuff[64];
			static time_t start = 0L;

			for (int b = 0; b < NUMBALLS; b++)
				balls[b]->move();

			// Call OpenGL drawing code and swap to foreground
			RenderScene();
			SwapBuffers(hDC);

			// Increment the frame count
			nFrameCount++;
	
			// Crude but effective timer to track frame count
			if(start == 0L)
				start = clock();
			else
				{
				time_t diff;
				float fFrameRate;

				diff = clock() - start;

				fFrameRate = float(nFrameCount)/(float(diff)/float(CLOCKS_PER_SEC));

				// Initialize frame rate display
//				sprintf(cOutBuff,"Reflection: %3.1f fps",fFrameRate);
				sprintf(cOutBuff,"Mouse (%d,%d) Xr=%3.0f Yr=%3.0f", xm, ym, xRot, yRot);
//				sprintf(cOutBuff,"%3.0f  %3.0f  %3.0f", xl, yl, zl);
				SetWindowText(hWnd,cOutBuff);
				}

			// Validate the newly painted client area
			// Comment out to continually update the display
			//ValidateRect(hWnd,NULL);
			}
			break;

		// Windows is telling the application that it may modify
		// the system palette.  This message in essence asks the 
		// application for a new palette.
		case WM_QUERYNEWPALETTE:
			// If the palette was created.
			if(hPalette)
				{
				int nRet;

				// Selects the palette into the current device context
				SelectPalette(hDC, hPalette, FALSE);

				// Map entries from the currently selected palette to
				// the system palette.  The return value is the number 
				// of palette entries modified.
				nRet = RealizePalette(hDC);

				// Repaint, forces remap of palette in current window
				InvalidateRect(hWnd,NULL,FALSE);

				return nRet;
				}
			break;

	
		// This window may set the palette, even though it is not the 
		// currently active window.
		case WM_PALETTECHANGED:
			// Don't do anything if the palette does not exist, or if
			// this is the window that changed the palette.
			if((hPalette != NULL) && ((HWND)wParam != hWnd))
				{
				// Select the palette into the device context
				SelectPalette(hDC,hPalette,FALSE);

				// Map entries to system palette
				RealizePalette(hDC);
				
				// Remap the current colors to the newly realized palette
				UpdateColors(hDC);
				return 0;
				}
			break;

		// Key press, check for arrow keys to do cube rotation.
		case WM_KEYDOWN:
			{
				switch (wParam)
				{
				case VK_UP:
					xRot-= 1.0f;
					break;
				case VK_DOWN:
					xRot += 1.0f;
					break;
				case VK_LEFT:
					yRot -= 1.0f;
					break;
				case VK_RIGHT:
					yRot += 1.0f;
					break;
				case VK_F1: xl += 1.0f;
					break;
				case VK_F2: xl -= 1.0f;
					break;
				case VK_F3: yl += 1.0f;
					break;
				case VK_F4: yl -= 1.0f;
					break;
				case VK_F5: zl += 1.0f;
					break;
				case VK_F6: zl -= 1.0f;
					break;
				case VK_ESCAPE:
					SendMessage(hWnd, WM_CLOSE, 0, 0L);
					break;
				}
			

			// Keep rotations within 360 degrees
			xRot = float(int(xRot) % 360);
			yRot = float(int(yRot) % 360);

			InvalidateRect(hWnd,NULL,FALSE);
			}
			break;

		case WM_MOUSEMOVE:
			xm = LOWORD(lParam);
			ym = HIWORD(lParam);
			yRot = GLfloat(xm / 2);
			dist = - ((sh - ym)/2 + 50.0f);
			break;

	default:   // Passes it on if unproccessed
	    return (DefWindowProc(hWnd, message, wParam, lParam));

	}

    return (0L);
	}



