
// Rubik's Cube Screensaver
// (C) 2005, Akop Karapetyan
//
// http://www.akop.org
//

#include "CubeApp.h"
#include "Cube.h"

#define MAX_LOADSTRING 100

#define MOUSE_SENSITIVITY_THRESHOLD	10

#define TIMER_IDLE					1
#define DISTANCE_FROM_CENTER		200

#define CUBE_MINTWISTS				10
#define CUBE_MAXTWISTS				75

#define TWIST_SCRAMBLE				0
#define TWIST_SOLVE					1

#define LIST_FRONT					1
#define LIST_RIGHT					2
#define LIST_BACK					3
#define LIST_LEFT					4
#define LIST_TOP					5
#define LIST_BOTTOM					6
#define LIST_BOX					7

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];								// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];								// The title bar text

// Dialog box labels

double 
	dSliceAngle,
	dCubeIncr,
	dCubeAngle,
	dCubeXPos,
	dCubeYPos,
	dCubeXIncr,
	dCubeYIncr,
	dCubeXMax,
	dCubeYMax;
POINT ptLastMousePos = { -1, -1 };
int m_nTwistState;
int m_nMaxTwists;
UINT m_nDelay;

DECL_CUBE(nvCube);

TwistType m_tvTwistHist[CUBE_MAXTWISTS];
int m_nTwists;
UINT nTimerID;

HWND hParentWnd;
GLdouble 
	gldRotateAroundX, 
	gldRotateAroundY;
HPALETTE hPal;
HGLRC hGLrc;

// Preferences

int nScrambleSpeed;
int nSolveSpeed;
double dMovementSpeed;
int nRotationSpeed;

unsigned char threeto8[8] = {
    0, 0111>>1, 0222>>1, 0333>>1, 0444>>1, 0555>>1, 0666>>1, 0377
};
unsigned char twoto8[4] = {
   0, 0x55, 0xaa, 0xff
};
unsigned char oneto8[2] = {
    0, 255
};

int defaultOverride[13] = {
    0, 3, 24, 27, 64, 67, 88, 173, 181, 236, 247, 164, 91
};

PALETTEENTRY defaultPalEntry[20] = {
    { 0,   0,   0,    0 }, //0
    { 0x80,0,   0,    0 }, 
    { 0,   0x80,0,    0 }, 
    { 0x80,0x80,0,    0 }, 
    { 0,   0,   0x80, 0 },
    { 0x80,0,   0x80, 0 },
    { 0,   0x80,0x80, 0 },
    { 0xC0,0xC0,0xC0, 0 }, //7

    { 192, 220, 192,  0 }, //8
    { 166, 202, 240,  0 },
    { 255, 251, 240,  0 },
    { 160, 160, 164,  0 }, //11

    { 0x80,0x80,0x80, 0 }, //12
    { 0xFF,0,   0,    0 },
    { 0,   0xFF,0,    0 },
    { 0xFF,0xFF,0,    0 },
    { 0,   0,   0xFF, 0 },
    { 0xFF,0,   0xFF, 0 },
    { 0,   0xFF,0xFF, 0 },
    { 0xFF,0xFF,0xFF, 0 }  //19
  };

#define MAT_AMBIENT		0
#define MAT_DIFFUSE		1
#define MAT_SPECULAR	2
#define MAT_EMISSION	3
#define MAT_SHININESS	4

GLfloat dvMaterials[][4] = {

	{ 0.33f, 0.22f, 0.03f, 1.0f }, // brass
	{ 0.78f, 0.57f, 0.11f, 1.0f },
	{ 0.99f, 0.94f, 0.81f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 28.0f },

	{ 0.5f, 0.5f, 0.0f, 1.0f }, // yellowish?
	{ 0.9f, 0.9f, 0.0f, 1.0f },
	{ 0.99f, 0.94f, 0.81f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 25.0f },

	{ 0.5f, 0.0f, 0.0f, 1.0f }, // reddish?
	{ 0.9f, 0.0f, 0.0f, 1.0f },
	{ 0.99f, 0.94f, 0.81f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 25.0f },

	{ 0.0f, 0.5f, 0.0f, 1.0f }, // greenish?
	{ 0.0f, 0.9f, 0.0f, 1.0f },
	{ 0.99f, 0.94f, 0.81f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 25.0f },

	{ 0.0f, 0.0f, 0.5f, 1.0f }, // bluish?
	{ 0.0f, 0.0f, 0.9f, 1.0f },
	{ 0.99f, 0.94f, 0.81f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 25.0f },

	{ 0.5f, 0.5f, 0.5f, 1.0f }, // silverish
	{ 0.9f, 0.9f, 0.9f, 1.0f },
	{ 0.99f, 0.94f, 0.81f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 51.0f },

	{ 0.0f, 0.0f, 0.0f, 1.0f },	// black plastic
	{ 0.01f, 0.01f, 0.01f, 1.0f },
	{ 0.4f, 0.4f, 0.4f, 1.0f },
	{ 0.0f, 0.0f, 0.0f, 0.0f },
	{ 10.0f }
};

#define SET_LIGHT_PROPERTY(l, a, b, c, d)	\
	l[0] = a; l[1] = b; l[2] = c; l[3] = d;	

#define SET_LIGHT_AMBIENT(gl, l, a, b, c, d)	\
	SET_LIGHT_PROPERTY(l, a, b, c, d);			\
	glLightfv(gl, GL_AMBIENT, l);

#define SET_LIGHT_DIFFUSE(gl, l, a, b, c, d)	\
	SET_LIGHT_PROPERTY(l, a, b, c, d);			\
	glLightfv(gl, GL_DIFFUSE, l);

#define SET_LIGHT_SPECULAR(gl, l, a, b, c, d)	\
	SET_LIGHT_PROPERTY(l, a, b, c, d);			\
	glLightfv(gl, GL_SPECULAR, l);

#define SET_LIGHT_POSITION(gl, l, a, b, c, d)	\
	SET_LIGHT_PROPERTY(l, a, b, c, d);			\
	glLightfv(gl, GL_POSITION, l);

#define SET_MATERIAL(m, n)	\
		glMaterialfv(GL_FRONT, GL_AMBIENT, m[MAT_AMBIENT + n * 5]); \
		glMaterialfv(GL_FRONT, GL_DIFFUSE, m[MAT_DIFFUSE + n * 5]); \
		glMaterialfv(GL_FRONT, GL_SPECULAR, m[MAT_SPECULAR + n * 5]); \
		glMaterialfv(GL_FRONT, GL_EMISSION, m[MAT_EMISSION + n * 5]); \
		glMaterialfv(GL_FRONT, GL_SHININESS, m[MAT_SHININESS + n * 5]); \

// Foward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK	SettingsHandler(HWND, UINT, WPARAM, LPARAM);

void				OnCreate(HWND hWnd);
void				OnDestroy(HWND hWnd);
void				OnSize(HWND hWnd, UINT nType, int cx, int cy);
void				OnDraw(HWND hWnd);
VOID CALLBACK		OnTimer(HWND hWnd, UINT uMsg, UINT nIDEvent, DWORD dwTime);
BOOL				OnMouseMove(UINT nFlags, UINT x, UINT y);
BOOL CALLBACK		MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData);

DWORD				GetRegInt(char *lpszKey, char *lpszValueName, DWORD dwDefValue);
BOOL				SetRegInt(char *lpszKey, char *lpszValueName, DWORD dwValue);

void				CreateRGBPalette(HDC hDC);
unsigned char		ComponentFromIndex(int i, UINT nbits, UINT shift);

void				InitScene();
void				RenderScene();

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	MSG msg;

	// Initialize global strings

	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_CUBE, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Load preferences

	dMovementSpeed = GetRegInt("Software\\Akop Karapetyan\\RubikKubik", "MovementSpeed", 2) / 10.0f;
	nRotationSpeed = GetRegInt("Software\\Akop Karapetyan\\RubikKubik", "RotationSpeed", 1);

	nScrambleSpeed = GetRegInt("Software\\Akop Karapetyan\\RubikKubik", "ScrambleSpeed", 15);
	nSolveSpeed = GetRegInt("Software\\Akop Karapetyan\\RubikKubik", "SolveSpeed", 5);

	// Continue initializing

	hParentWnd = NULL;

#ifdef SSAVER
	if (!strnicmp(lpCmdLine, "/P ", 2))
	{
		// Preview
		hParentWnd = (HWND)atol(lpCmdLine + 3);
	}
	else if (!strnicmp(lpCmdLine, "/C ", 2))
	{
		// Config
		hParentWnd = (HWND)atol(lpCmdLine + 3);
		DialogBox(hInst, (LPCTSTR)IDD_SETTINGS, hParentWnd, (DLGPROC)SettingsHandler);
		return 1;
	}
#endif

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow)) 
	{
		return FALSE;
	}

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage is only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this function
//    so that the application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX); 

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, (LPCTSTR)IDI_CUBE);
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= NULL; //(LPCSTR)IDC_CUBE;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

	return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HANDLE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HWND hWnd;

	hInst = hInstance; // Store instance handle in our global variable

#ifdef SSAVER
	int nHeight;
	int nWidth;
	DWORD dwStyle;

	if (hParentWnd) // preview mode
	{
		RECT rt;
		GetClientRect(hParentWnd, &rt);
		nWidth = rt.right;
		nHeight = rt.bottom;
		dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
	}
	else
	{
		nWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
		nHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
		dwStyle = WS_POPUP | WS_CLIPSIBLINGS;
	}

	hWnd = CreateWindow(szWindowClass, szTitle, 
		dwStyle, 0, 0, nWidth, nHeight, hParentWnd, NULL, hInstance, NULL);

	if (!hParentWnd)
		SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
#else
	hWnd = CreateWindow(szWindowClass, szTitle, 
		WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
#endif 

	if (!hWnd)
		return FALSE;

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	return TRUE;
}

//
//  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) 
	{
	case WM_CREATE:
		OnCreate(hWnd);
		break;

	case WM_SIZE:
		OnSize(hWnd, wParam, LOWORD(lParam), HIWORD(lParam));
		break;

	case WM_ERASEBKGND:
		return 1;

	case WM_PAINT:
		OnDraw(hWnd);
		break;

#ifdef SSAVER
	case WM_SETCURSOR:
		if (!hParentWnd)
			SetCursor(NULL);
		break;

	case WM_MOUSEMOVE:
		if ( hParentWnd || !OnMouseMove(wParam, LOWORD(lParam), HIWORD(lParam)) )
			break;
#endif

	case WM_KEYDOWN:
	case WM_DESTROY:
		OnDestroy(hWnd);
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

// Mesage handler for about box.
LRESULT CALLBACK SettingsHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND hWnd;
	SCROLLINFO si;

	switch (message)
	{
		case WM_INITDIALOG:

			memset(&si, 0, sizeof(SCROLLINFO));
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
			si.nMin = 1;
			si.nMax = 20;
			si.nPage = 2;

			si.nPos = nScrambleSpeed;
			hWnd = GetDlgItem(hDlg, IDC_SCRAMBLE);
			SetScrollInfo(hWnd, SB_CTL, &si, TRUE);

			si.nPos = nSolveSpeed;
			hWnd = GetDlgItem(hDlg, IDC_SOLVE);
			SetScrollInfo(hWnd, SB_CTL, &si, TRUE);

			si.nMin = 0;
			si.nMax = 10;
			si.nPos = (int)(dMovementSpeed * 10.0);
			hWnd = GetDlgItem(hDlg, IDC_MOVEMENT);
			SetScrollInfo(hWnd, SB_CTL, &si, TRUE);

			si.nMin = 1;
			si.nMax = 10;
			si.nPos = nRotationSpeed;
			hWnd = GetDlgItem(hDlg, IDC_ROTATION);
			SetScrollInfo(hWnd, SB_CTL, &si, TRUE);

			return TRUE;

		case WM_HSCROLL:

			memset(&si, 0, sizeof(SCROLLINFO));
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask = SIF_PAGE | SIF_POS;
			GetScrollInfo((HWND)lParam, SB_CTL, &si);

			switch(LOWORD(wParam))
			{
			case SB_LINELEFT:
				si.nPos -= 1;
				SetScrollInfo((HWND)lParam, SB_CTL, &si, TRUE);
				break;
			case SB_LINERIGHT:
				si.nPos += 1;
				SetScrollInfo((HWND)lParam, SB_CTL, &si, TRUE);
				break;
			case SB_PAGELEFT:
				si.nPos -= si.nPage;
				SetScrollInfo((HWND)lParam, SB_CTL, &si, TRUE);
				break;
			case SB_PAGERIGHT:
				si.nPos += si.nPage;
				SetScrollInfo((HWND)lParam, SB_CTL, &si, TRUE);
				break;
			case SB_THUMBTRACK:
				SetScrollPos((HWND)lParam, SB_CTL, HIWORD(wParam), TRUE);
				break;
			}
			return 0;

		case WM_COMMAND:
			switch(LOWORD(wParam))
			{
			case IDOK:
				hWnd = GetDlgItem(hDlg, IDC_SCRAMBLE);
				SetRegInt("Software\\Akop Karapetyan\\RubikKubik", "ScrambleSpeed", GetScrollPos(hWnd, SB_CTL));

				hWnd = GetDlgItem(hDlg, IDC_SOLVE);
				SetRegInt("Software\\Akop Karapetyan\\RubikKubik", "SolveSpeed", GetScrollPos(hWnd, SB_CTL));

				hWnd = GetDlgItem(hDlg, IDC_MOVEMENT);
				SetRegInt("Software\\Akop Karapetyan\\RubikKubik", "MovementSpeed", GetScrollPos(hWnd, SB_CTL));

				hWnd = GetDlgItem(hDlg, IDC_ROTATION);
				SetRegInt("Software\\Akop Karapetyan\\RubikKubik", "RotationSpeed", GetScrollPos(hWnd, SB_CTL));

			case IDCANCEL:
				EndDialog(hDlg, LOWORD(wParam));
				return TRUE;
			}

			break;
	}
    return FALSE;
}

void OnCreate(HWND hWnd)
{
	hPal = NULL;
	BOOL bTest;

	HDC hDC = GetWindowDC(hWnd);

	//
	// Fill in the pixel format descriptor.
	//
	PIXELFORMATDESCRIPTOR pfd ;
	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)) ;
	pfd.nSize      = sizeof(PIXELFORMATDESCRIPTOR); 
	pfd.nVersion   = 1 ; 
	pfd.dwFlags    = PFD_DOUBLEBUFFER |
					PFD_SUPPORT_OPENGL |
					PFD_DRAW_TO_WINDOW ;
	pfd.iPixelType = PFD_TYPE_RGBA ;
	pfd.cColorBits = 24 ;
	pfd.cDepthBits = 32 ;
	pfd.iLayerType = PFD_MAIN_PLANE ;

	int nPixelFormat = ChoosePixelFormat(hDC, &pfd);

	bTest = SetPixelFormat (hDC, nPixelFormat, &pfd);

	//
	// Create a rendering context.
	//

	hGLrc = wglCreateContext(hDC);

	// Create the palette

	CreateRGBPalette(hDC);

	// Make rendering context current

	bTest = wglMakeCurrent(hDC, hGLrc);

	// Initialize scene

	InitScene();

	// Set up timer

	nTimerID = SetTimer(hWnd, TIMER_IDLE, 25, OnTimer);
}


VOID CALLBACK OnTimer(HWND hWnd, UINT uMsg, UINT nIDEvent, DWORD dwTime) 
{
	int nSwitch = 0;

	switch(nIDEvent)
	{
	case TIMER_IDLE:

		dCubeXPos += dCubeXIncr;
		if (abs((int)dCubeXPos) >= dCubeXMax)
			dCubeXIncr *= -1.0;

		dCubeYPos += dCubeYIncr;
		if (abs((int)dCubeYPos) >= dCubeYMax)
			dCubeYIncr *= -1.0;

		dCubeAngle += dCubeIncr;
		if (dCubeAngle > 360)
			dCubeAngle = 0;

		if (m_nDelay) 
		{
			if (++m_nDelay > 150)
				m_nDelay = 0;

			InvalidateRgn(hWnd, NULL, 0);
			return;
		}

		if (m_nTwistState == TWIST_SCRAMBLE)
		{
			if (m_nTwists == 0)
			{
				cubeSolve(nvCube);
				m_nMaxTwists = (rand() % (CUBE_MAXTWISTS - CUBE_MINTWISTS)) + CUBE_MINTWISTS;
				m_tvTwistHist[0].nTwisty = -1;
				dSliceAngle = 0;
				nSwitch = 1;
			}
			else if (m_tvTwistHist[m_nTwists].nCount > 2)
			{
				if ((dSliceAngle  -= nScrambleSpeed) < -90)
					nSwitch = 1;
			}
			else
			{
				if ((dSliceAngle  += nScrambleSpeed) > m_tvTwistHist[m_nTwists].nCount * 90)
					nSwitch = 1;
			}

			if (nSwitch)
			{
				if (m_nTwists > 0) 
					cubeTwist(nvCube, m_tvTwistHist[m_nTwists].nTwisty, m_tvTwistHist[m_nTwists].nCount);
				
				if (m_nTwists >= m_nMaxTwists)
				{
					m_nDelay = 1;
					dSliceAngle = 0;
					m_nTwistState = TWIST_SOLVE;
					break;
				}

				m_nTwists++;
				while ( (m_tvTwistHist[m_nTwists].nTwisty = (rand() % 9)) == m_tvTwistHist[m_nTwists - 1].nTwisty);
				m_tvTwistHist[m_nTwists].nCount = rand() % 3 + 1;
				dSliceAngle = 0;
			}
		}
		else if (m_nTwistState == TWIST_SOLVE)
		{
			if ((4 - m_tvTwistHist[m_nTwists].nCount) > 2)
			{
				if ((dSliceAngle -= nSolveSpeed) < -90)
					nSwitch = 1;
			}
			else
			{
				if ((dSliceAngle += nSolveSpeed) > (4 - m_tvTwistHist[m_nTwists].nCount) * 90)
					nSwitch = 1;
			}

			if (nSwitch)
			{
				if (m_nTwists > 0) 
					cubeTwist(nvCube, m_tvTwistHist[m_nTwists].nTwisty, 4 - m_tvTwistHist[m_nTwists].nCount);

				if (m_nTwists <= 0)
				{
					m_nDelay = 1;
					dSliceAngle = 0;
					m_nTwistState = TWIST_SCRAMBLE;
					break;
				}

				m_nTwists--;
				dSliceAngle = 0;
			}
		}
		InvalidateRgn(hWnd, NULL, 0);
		break;
	}
}

void OnDestroy(HWND hWnd)
{
	wglMakeCurrent(NULL,NULL) ; 

	KillTimer(hWnd, nTimerID);

	if (hGLrc) 
	{
		wglDeleteContext(hGLrc) ;
		hGLrc = NULL;
	}

	DeleteObject(hPal);
}

void OnSize(HWND hWnd, UINT nType, int cx, int cy) 
{
	if ( (cx <= 0) || (cy <= 0) ) return ;

	double dAspectRatio;

#ifdef SSAVER

	// Multiple display monitors

	if (!hParentWnd && GetSystemMetrics(SM_CMONITORS) > 1)
	{
		MONITORINFO mi;
		HMONITOR hMon;
		RECT rc;
		HDC hdc;

		hMon = MonitorFromRect(&rc, MONITOR_DEFAULTTOPRIMARY);
		mi.cbSize = sizeof(MONITORINFO);

		GetMonitorInfo(hMon, &mi);

		cx = mi.rcMonitor.right;
		cy = mi.rcMonitor.bottom;

		// Blank out all screens

		hdc = GetDC(NULL);
		EnumDisplayMonitors(hdc, NULL, MonitorEnumProc, 0);
		ReleaseDC(NULL, hdc);
	}
#endif

	dAspectRatio = (GLdouble) cx / (GLdouble) cy;

	HDC hDC = GetWindowDC(hWnd);

	//
	// Make the rendering context m_hrc current
	//

	BOOL bTest = wglMakeCurrent(hDC, hGLrc);

    //
    // Set up the mapping of 3-space to screen space
    //

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, dAspectRatio, 10.0, 1400.0);

	glViewport(0, 0, cx, cy);

	dCubeXPos = 0;
	dCubeYPos = 0;
	dCubeXMax = (dAspectRatio * DISTANCE_FROM_CENTER) / 2 - 30;
	dCubeYMax = DISTANCE_FROM_CENTER / 2 - 30;

}

void OnDraw(HWND hWnd)
{
	PAINTSTRUCT ps;

	HDC hdc = BeginPaint(hWnd, &ps);

	// select palette

	HPALETTE hOldPal = SelectPalette(hdc, hPal, 0);
	RealizePalette(hdc);

	// Draw.
	
	RenderScene();

	// Flush the buffers

	glFlush();

	// Swap buffers.
	
	SwapBuffers(hdc) ;

	// Select old palette. 

	if (hOldPal)
		SelectPalette(hdc, hOldPal, 0); 

	EndPaint(hWnd, &ps);
}

//
// CreateRGBPalette
//
void CreateRGBPalette(HDC hDC)
{
	//
	// Check to see if we need a palette
	//

    PIXELFORMATDESCRIPTOR pfd;
    int n = GetPixelFormat(hDC);
    DescribePixelFormat(hDC, n, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
	if (!(pfd.dwFlags & PFD_NEED_PALETTE)) return;

    // allocate a log pal and fill it with the color table info

    LOGPALETTE* pPal = (LOGPALETTE*) malloc(sizeof(LOGPALETTE) 
                     + 256 * sizeof(PALETTEENTRY));
    
    pPal->palVersion = 0x300; // Windows 3.0
    pPal->palNumEntries = 256; // table size

	//
	// Create RGB Palette
	//

    n = 1 << pfd.cColorBits;
    for (int i=0; i<n; i++)
    {
        pPal->palPalEntry[i].peRed =
            ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift);
        pPal->palPalEntry[i].peGreen =
            ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift);
        pPal->palPalEntry[i].peBlue =
            ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift);
        pPal->palPalEntry[i].peFlags = 0;
    }

	//
	// Fix up color table with system colors.
	//
    if ((pfd.cColorBits == 8)                           &&
        (pfd.cRedBits   == 3) && (pfd.cRedShift   == 0) &&
        (pfd.cGreenBits == 3) && (pfd.cGreenShift == 3) &&
        (pfd.cBlueBits  == 2) && (pfd.cBlueShift  == 6)
       )
    {
	    for (int j = 1 ; j <= 12 ; j++)
	        pPal->palPalEntry[defaultOverride[j]] = defaultPalEntry[j];
	}

    // Delete any existing GDI palette
	DeleteObject(hPal);
	hPal = CreatePalette(pPal);

	free(pPal);
}

//
// ComponentFromIndex
//
unsigned char ComponentFromIndex(int i, UINT nbits, UINT shift)
{
    unsigned char val;

    val = (unsigned char) (i >> shift);
    switch (nbits) {

    case 1:
        val &= 0x1;
        return oneto8[val];

    case 2:
        val &= 0x3;
        return twoto8[val];

    case 3:
        val &= 0x7;
        return threeto8[val];

    default:
        return 0;
    }
}

void InitScene()
{
	GLfloat lightSetting[4];
	GLdouble dvBox[][3] = {
		-4.455,  4.455, 5, // 0
		-4.455, -4.455, 5, // 1
		4.455,  -4.455, 5, // 2
		4.455,   4.455, 5, // 3

		4.455, 5, 4.455,   // 4
		-4.455, 5, 4.455,  // 5

		-4.455, -5, 4.455, // 6
		4.455, -5, 4.455,  // 7

		5, -4.455, 4.455,  // 8
		5, 4.455, 4.455,   // 9

		-5, 4.455, 4.455,  // 10
		-5, -4.455, 4.455, // 11
	};

	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glClearColor(0, 0, 0, 1.0);

	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);

	SET_LIGHT_PROPERTY(lightSetting, 0.2f, 0.2f, 0.2f, 1.0f);
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightSetting);

	SET_LIGHT_AMBIENT(GL_LIGHT0, lightSetting, 0.0f, 0.0f, 0.0f, 1.0f);
	SET_LIGHT_DIFFUSE(GL_LIGHT0, lightSetting, 1.0f, 1.0f, 1.0f, 1.0f);
	SET_LIGHT_SPECULAR(GL_LIGHT0, lightSetting, 1.0f, 1.0f, 1.0f, 1.0f);
	SET_LIGHT_POSITION(GL_LIGHT0, lightSetting, 0.0f, 0.0f, 1000.0f, 1.0f);

	SET_LIGHT_PROPERTY(lightSetting, 0.0f, 0.0f, -1.0f, 0.0f);
	glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lightSetting);
	glLighti(GL_LIGHT0, GL_SPOT_EXPONENT, 30);
	glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, 180);

	glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0);
	glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0);
	glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0);

	glNewList(LIST_FRONT, GL_COMPILE);
		glPushMatrix();
			glBegin(GL_POLYGON);
				glNormal3d(0, 0, 1);
				glVertex3d(-4, 3.8, 5.25);
				glVertex3d(-4, -3.8, 5.25);
				glVertex3d(-3.8, -4, 5.25);
				glVertex3d(3.8, -4, 5.25);
				glVertex3d(4, -3.8, 5.25);
				glVertex3d(4, 3.8, 5.25);
				glVertex3d(3.8, 4, 5.25);
				glVertex3d(-3.8, 4, 5.25);
			glEnd();
		glPopMatrix();
	glEndList();

	glNewList(LIST_RIGHT, GL_COMPILE);
		glPushMatrix();
			glRotated(90, 0, 1, 0);
			glCallList(LIST_FRONT);
		glPopMatrix();
	glEndList();

	glNewList(LIST_BACK, GL_COMPILE);
		glPushMatrix();
			glRotated(180, 0, 1, 0);
			glCallList(LIST_FRONT);
		glPopMatrix();
	glEndList();

	glNewList(LIST_LEFT, GL_COMPILE);
		glPushMatrix();
			glRotated(270, 0, 1, 0);
			glCallList(LIST_FRONT);
		glPopMatrix();
	glEndList();

	glNewList(LIST_TOP, GL_COMPILE);
		glPushMatrix();
			glRotated(270, 1, 0, 0);
			glCallList(LIST_FRONT);
		glPopMatrix();
	glEndList();

	glNewList(LIST_BOTTOM, GL_COMPILE);
		glPushMatrix();
			glRotated(90, 1, 0, 0);
			glCallList(LIST_FRONT);
		glPopMatrix();
	glEndList();

	glNewList(LIST_BOX, GL_COMPILE);
		glShadeModel(GL_FLAT);
		glShadeModel(GL_SMOOTH);
		glPushMatrix();
			for (int i = 0; i < 4; i++)
			{
				glBegin(GL_QUADS);
					glNormal3d(0, 0, 1);
					glVertex3dv(dvBox[0]);
					glVertex3dv(dvBox[1]);
					glVertex3dv(dvBox[2]);
					glVertex3dv(dvBox[3]);

					glNormal3d(0.0, 0.7071, 0.7071);
					glVertex3dv(dvBox[3]); // top
					glVertex3dv(dvBox[4]);
					glVertex3dv(dvBox[5]);
					glVertex3dv(dvBox[0]);

					glNormal3d(0.7071, 0, 0.7071);
					glVertex3dv(dvBox[2]); // right
					glVertex3dv(dvBox[8]);
					glVertex3dv(dvBox[9]);
					glVertex3dv(dvBox[3]);

					glNormal3d(-0.7071, 0, 0.7071);
					glVertex3dv(dvBox[0]); // left
					glVertex3dv(dvBox[10]);
					glVertex3dv(dvBox[11]);
					glVertex3dv(dvBox[1]);
				glEnd();

				glBegin(GL_TRIANGLES);
					glNormal3d(0.5774, 0.5774, 0.5774);
					glVertex3dv(dvBox[3]);
					glVertex3dv(dvBox[9]);
					glVertex3dv(dvBox[4]);

					glNormal3d(-0.5774, 0.5774, 0.5774);
					glVertex3dv(dvBox[0]);
					glVertex3dv(dvBox[5]);
					glVertex3dv(dvBox[10]);
				glEnd();

				glRotated(90, 1, 0, 0);
			}
		glPopMatrix();
		glPushMatrix();
			glRotated(90, 0, 1, 0);
			for (i = 0; i < 2; i++)
			{
				glBegin(GL_QUADS);
					glNormal3d(0, 0, 1);
					glVertex3dv(dvBox[0]);
					glVertex3dv(dvBox[1]);
					glVertex3dv(dvBox[2]);
					glVertex3dv(dvBox[3]);
				glEnd();
				glRotated(180, 0, 1, 0);
			}
		glPopMatrix();
	glEndList();

	srand((unsigned)time(NULL));

	cubeSolve(nvCube);
	dCubeIncr = nRotationSpeed;
	dCubeAngle = 0;
	dSliceAngle = 0;
	m_nTwists = 0;
	m_nDelay = 0;
	m_nTwistState = TWIST_SCRAMBLE;
	dCubeXIncr = dMovementSpeed;
	dCubeYIncr = dMovementSpeed;
}

void RenderScene()
{
	int i, j, k;

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glTranslated(dCubeXPos, dCubeYPos, -DISTANCE_FROM_CENTER);
	glRotated(dCubeAngle - 20, 0, 1, 0);
	glRotated(dCubeAngle, 1, 0, 0);

	// Enable depth testing

	glEnable(GL_DEPTH_TEST);

	// Enable lighting calculations

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	// Clear the color and depth buffers

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Draw the cube

	for (i = 0; i < CUBE_NSQUARES; i++)
	{
		for (j = 0; j < CUBE_NSQUARES; j++)
		{
			for (k = 0; k < CUBE_NSQUARES; k++)
			{
				glPushMatrix();

				if (m_nTwistState == TWIST_SCRAMBLE || m_nTwistState == TWIST_SOLVE)
				{
					if (i == m_tvTwistHist[m_nTwists].nTwisty)
						glRotated(dSliceAngle, 0, 1, 0);
					else if (j == m_tvTwistHist[m_nTwists].nTwisty - 3)
						glRotated(dSliceAngle, 0, 0, 1);
					else if (k == m_tvTwistHist[m_nTwists].nTwisty - 6)
						glRotated(dSliceAngle, 1, 0, 0);
				}

				glTranslated(0, i * 10 - 10, 0);
				glTranslated(0, 0, j * 10 - 10);
				glTranslated(k * 10 - 10, 0, 0);

				if (i != 1 || j != 1 || k != 1)
				{
					SET_MATERIAL(dvMaterials, 6)
					glCallList(LIST_BOX);
				}

				/*  */
				if (j == 2)	// front
				{
					SET_MATERIAL(dvMaterials, nvCube[FACE_FRONT][i][k])
					glCallList(LIST_FRONT);
				}
				else if (j == 0) // back
				{
					SET_MATERIAL(dvMaterials, nvCube[FACE_BACK][i][CUBE_NSQUARES - k - 1])
					glCallList(LIST_BACK);
				}

				if (k == 2) // right
				{
					SET_MATERIAL(dvMaterials, nvCube[FACE_RIGHT][i][CUBE_NSQUARES - j - 1])
					glCallList(LIST_RIGHT);
				} 
				else if (k == 0) // left
				{
					SET_MATERIAL(dvMaterials, nvCube[FACE_LEFT][i][j])
					glCallList(LIST_LEFT);
				}

				if (i == 0) // bottom
				{
					SET_MATERIAL(dvMaterials, nvCube[FACE_BOTTOM][j][k])
					glCallList(LIST_BOTTOM);
				} 
				else if (i == 2) // top
				{
					SET_MATERIAL(dvMaterials, nvCube[FACE_TOP][j][k])
					glCallList(LIST_TOP);
				}
				/* */

				glPopMatrix();
			}
		}
	}

	glPopMatrix();
}

BOOL OnMouseMove(UINT nFlags, UINT x, UINT y)
{
	if ( ptLastMousePos.x == -1 )
	{
		ptLastMousePos.x = x;
		ptLastMousePos.y = y;
		return FALSE;
	}

	if ( abs(x - ptLastMousePos.x) < MOUSE_SENSITIVITY_THRESHOLD 
		|| abs(y - ptLastMousePos.y) < MOUSE_SENSITIVITY_THRESHOLD)
		return FALSE;

	return TRUE;
}

DWORD GetRegInt(char *lpszKey, char *lpszValueName, DWORD dwDefValue)
{
	HKEY hKey;
	DWORD dwType;
	DWORD dwValue;
	DWORD dwSize;

	if (RegOpenKeyEx(HKEY_CURRENT_USER, lpszKey, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)
		return dwDefValue;

	dwSize = sizeof(DWORD);
	if (RegQueryValueEx(hKey, lpszValueName, NULL, &dwType, (LPBYTE)&dwValue, &dwSize) != ERROR_SUCCESS)
		dwValue = dwDefValue;

	RegCloseKey(hKey);

	return dwValue;
}

// fix so that keys are created recursively

BOOL SetRegInt(char *lpszKey, char *lpszValueName, DWORD dwValue)
{
	HKEY hKey = NULL;
	DWORD dwDisposition;
	char *lpszTmpKey;
	char szSubKey[1024];
	int nKeyLen;

	if (RegOpenKeyEx(HKEY_CURRENT_USER, lpszKey, 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS)
	{
		nKeyLen = strlen(lpszKey);
		lpszTmpKey = (char*)malloc(sizeof(char) * (nKeyLen + 2));
		strcpy(lpszTmpKey, lpszKey);
		lpszTmpKey[nKeyLen] = '\\';
		lpszTmpKey[nKeyLen + 1] = '\0';

		for (char *lpBuf = strchr(lpszTmpKey, '\\') + 1; lpBuf > (char*)1; lpBuf = strchr(lpBuf, '\\') + 1)
		{
			if (hKey)
				RegCloseKey(hKey);

			if (RegCreateKeyEx(HKEY_CURRENT_USER, lpszKey, 0, NULL, 
				REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition) != ERROR_SUCCESS)
			{
				free(lpszTmpKey);
				return FALSE;
			}

			strncpy(szSubKey, lpszTmpKey, lpBuf - lpszTmpKey - 1);
			szSubKey[lpBuf - lpszTmpKey - 1] = '\0';
		}

		free(lpszTmpKey);
	}

	if (RegSetValueEx(hKey, lpszValueName, 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD)) != ERROR_SUCCESS)
	{
		RegCloseKey(hKey);
		return FALSE;
	}

	RegCloseKey(hKey);

	return TRUE;
}

BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdc, LPRECT lprc, LPARAM dwData)
{
	PatBlt(hdc, lprc->left, lprc->top, lprc->right - lprc->left, lprc->bottom - lprc->top, BLACKNESS);
	return TRUE;
}
