
#include <windows.h>
#include <stdio.h>

#include "resource.h"
#include "MainDisplayInterface.h"
#include "ComputerOpponent.h"
#include "MaxLengthConstants.h"

#include <iostream.h>
#define OK 1
#define CANCEL 2


LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
BOOL CALLBACK CategoryDlgProc(HWND,UINT,WPARAM,LPARAM);
BOOL CALLBACK AboutDlgProc(HWND,UINT,WPARAM,LPARAM);

char szProgName[] = "HangMan";
char szMenuName[] = "HangManMenu";

HINSTANCE hInst;	// Needed for dialog box.

// Holds status of radio buttons in CategoryDlg Box.

int rbCategoryStatus1 = BST_CHECKED, rbCategoryStatus2 = BST_UNCHECKED,
	rbCategoryStatus3 = BST_UNCHECKED, rbCategoryStatus4 = BST_UNCHECKED,
	rbCategoryStatus5 = BST_UNCHECKED, rbCategoryStatus6 = BST_UNCHECKED;

int maxX, maxY; // holds maximum screen dimensions.


HDC memdc;	// Device handle for virtual window.
HBITMAP hbit;	// Handle to virtual bitmap.
HBRUSH hbrush, hOldBrush;	// Brush handle.

HPEN hOldPen, hBlackPen;	// Handles to Pens.
HFONT hOldFont, hNewFont;	// Handles to fonts.

MainDisplayInterface HangmanDisplay;
ComputerOpponent theComputerOpponent;

static int CategoryFileNumber = 1;

// Category File Names

static char szCategoryFile1[] = "GeneralWords.txt";
static char szCategoryFile2[] = "ComputerTerms.txt";
static char szCategoryFile3[] = "ComputerGames.txt";
static char szCategoryFile4[] = "MetalBands.txt";
static char szCategoryFile5[] = "HorrorFilms.txt";

// Default/opening screen settings

static char secretWordDefault[] = "CLICK ON 'NEW GAME' MENU OPTION TO START.";
static char categoryDefault[] = "ENTER GUESSES VIA THE KEYBOARD.";

static char categoryTitle[MaxWordLength];
static char wordDisplay[MaxWordLength], guessList[MaxGuessListLength];

static int guessesLeft, gamesWon, gamesPlayed;

static int dialogButtonPress = CANCEL;	// Value is set depending upon which dialog button is pressed.
										// When the cancel button was being pressed on the new game
										// dialog - a file was loaded anyway. To prevent this I will
										// check to see if the CANCEL button that was pressed
										// or not.

// Purpose of WinMain: Initialise application
//					   Display main window.
//					   Create message processing loop.

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreInst, LPSTR lpscCmdLine, int nCmdShow)
{
	// hInst: unique instance handle of current instance of application.
	// hPreInst: always contains NULL
	// lpscCmdLine: represents application's command line arguements. Normally NULL.
	// nCmdShow: represents possible ways that window can be displayed i.e. its visibility state.

	HWND hWnd;	// Handle to instance of window.
	MSG lpMsg;	// MSG structure defines members that encapsulate message info.
	WNDCLASS wcApp;	// WNDCLASS structure defines window.

	// Define window class

	wcApp.lpszClassName = szProgName;	// specifies the windows class name.
	wcApp.hInstance = hInst;
	wcApp.lpfnWndProc = WndProc;	// pointer to WndProc.
	wcApp.hCursor = LoadCursor(NULL, IDC_ARROW);	// handle to the class cursor.
	wcApp.hIcon = LoadIcon(hInst, "HangManIcon");							// handle to the class icon.
	wcApp.lpszMenuName = szMenuName;
	wcApp.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);// handle to the class background
																// brush.
	wcApp.style = CS_HREDRAW|CS_VREDRAW;
	wcApp.cbClsExtra = 0;					// number of extra bytes allocated to window class.
	wcApp.cbWndExtra = 0;					// number of extra bytes allocated to window instance.

	// Register window class.

	if (!RegisterClass (&wcApp))
		return 0;

	// Creating an instance of window.

	hWnd = CreateWindow(szProgName, "Martin's Hangman",
						WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
						CW_USEDEFAULT, 750,
						500, (HWND)NULL, (HMENU)NULL,
						hInst, (LPSTR)NULL);


	// Display Window.

	ShowWindow(hWnd, nCmdShow);	// hWnd: handle to window created in CreateWindow function.
								// nCmdShow: window's visibility state.
								// ShowWindow: erases window with background brush.
	UpdateWindow(hWnd);			// Causes area to be painted by generating a WM_PAINT message.

	// Message processing loop. Loop ends when message is WM_QUIT.

	while (GetMessage (&lpMsg,0,0,0))// retrieves next message from applications message queue
	{
		TranslateMessage(&lpMsg);	// needed for keyboard messages.
		DispatchMessage(&lpMsg);	// sends messages to the correct windows procedure.
	}

	return (lpMsg.wParam); // returns WM_QUIT's wParam.
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// All messages are usually processed within this procedure.

	HDC hdc;			// display context handle.
	PAINTSTRUCT ps;		// structure stores client area info.
	int response;
	int successStatus;	// temp holder for TRUE/FALSE.

	switch(message)
	{

	case WM_CREATE:


		// Get screen coordinates.

		maxX = GetSystemMetrics(SM_CXSCREEN);
		maxY = GetSystemMetrics(SM_CYSCREEN);

		// Create virtual window.

		hdc = GetDC(hWnd);
		memdc = CreateCompatibleDC(hdc);
		hbit = CreateCompatibleBitmap(hdc, maxX, maxY);
		SelectObject(memdc, hbit);
		hbrush = (HBRUSH) GetStockObject(WHITE_BRUSH);
		SelectObject(memdc, hbrush);
		PatBlt(memdc, 0, 0, maxX, maxY, PATCOPY);
		ReleaseDC(hWnd, hdc);


		// create custom black pen

		hBlackPen = CreatePen(PS_SOLID, 8, RGB(0,0,0));

		// save default pen

		hOldPen = (HPEN) SelectObject(memdc, hBlackPen);
		SelectObject(memdc, hOldPen);

		// Create new fonts
		
		hNewFont = CreateFont(30,0,0,0,FW_BOLD,
						  FALSE,FALSE,FALSE,ANSI_CHARSET,
						  OUT_DEFAULT_PRECIS,
						  CLIP_DEFAULT_PRECIS,
						  PROOF_QUALITY,
						  34,"Arial");

		hOldFont = (HFONT) SelectObject(memdc, hNewFont);
		SelectObject(memdc, hNewFont);

		// Drawing Stuff
		// Draw Gallows

		guessesLeft = theComputerOpponent.getNumGuessesLeft();
		theComputerOpponent.getUsedGuessList(guessList);
		HangmanDisplay.updateDisplay(memdc, hBlackPen, guessesLeft, 0, 0, secretWordDefault, categoryDefault, guessList);

		break;

	  //////////////////////////////////////////////////////////
		case WM_CHAR:

			enum gameState {lost, won, inProgress, notStarted};

			int gameStatus;

			gameStatus = theComputerOpponent.getGameState();

			char tempCharacter[1];

			if (gameStatus != notStarted)	// Then process any key presses as guesses otherwise don't.
			{
				if (gameStatus == inProgress)
				{
					sprintf(tempCharacter, "%c", (char) wParam);

					// Computer Opponent checks the players guess.

					theComputerOpponent.checkGuess(tempCharacter[0]);

					theComputerOpponent.getSecretWord(wordDisplay);
					guessesLeft = theComputerOpponent.getNumGuessesLeft();
					theComputerOpponent.getScore(gamesPlayed, gamesWon);
					theComputerOpponent.getUsedGuessList(guessList);
					HangmanDisplay.updateDisplay(memdc, hBlackPen, guessesLeft, gamesWon, gamesPlayed, wordDisplay, categoryTitle, guessList);

				}

				InvalidateRect(hWnd,NULL,TRUE);	// updates window before lost/won check
												// otherwise last guess will not be shown
												// on display.

				gameStatus = theComputerOpponent.getGameState();	// check game state again
																	// after previous guess has been taken.
				if ((gameStatus == lost)|| (gameStatus == won))
				{
					if (gameStatus == lost)
					{
						// Update the display and reveal secret word.

						theComputerOpponent.getUncoveredWord(wordDisplay);
						guessesLeft = theComputerOpponent.getNumGuessesLeft();
						theComputerOpponent.getScore(gamesPlayed, gamesWon);
						theComputerOpponent.getUsedGuessList(guessList);
						HangmanDisplay.updateDisplay(memdc, hBlackPen, guessesLeft, gamesWon, gamesPlayed, wordDisplay, categoryTitle, guessList);
						sndPlaySound("c:lostGame.wav", SND_ASYNC);
						response = MessageBox(hWnd,"Oops!\nAnother game?","Another Game",MB_YESNO|MB_ICONQUESTION);
						
					}
					else
					{
						sndPlaySound("c:wonGame.wav", SND_ASYNC);
						response = MessageBox(hWnd,"Well Done!\nAnother game?","Another Game",MB_YESNO|MB_ICONQUESTION);
					}

					

					if (response == IDYES)
					{
						// Choose new word and update display.

						successStatus = theComputerOpponent.chooseNewWord();
						theComputerOpponent.getSecretWord(wordDisplay);
						guessesLeft = theComputerOpponent.getNumGuessesLeft();
						theComputerOpponent.getScore(gamesPlayed, gamesWon);
						theComputerOpponent.getUsedGuessList(guessList);
						HangmanDisplay.updateDisplay(memdc, hBlackPen, guessesLeft, gamesWon, gamesPlayed, wordDisplay, categoryTitle, guessList);

						if (!successStatus)
							MessageBox(hWnd,"Sorry no more words left in this category.","No more words.",MB_OK);
					}
					else
					{
						// Reset Game.

						theComputerOpponent.resetComputerOpponent();

						theComputerOpponent.getUsedGuessList(guessList);
						guessesLeft = theComputerOpponent.getNumGuessesLeft();
						// Display default screen.

						HangmanDisplay.updateDisplay(memdc, hBlackPen, guessesLeft, 0, 0, secretWordDefault, categoryDefault, guessList);
					}
				}
			}
			
			InvalidateRect(hWnd,NULL,TRUE);	// send WM_PAINT message.
			break;

	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case ID_HANGMAN_NEWGAME:

			response = MessageBox(hWnd,"Are you sure you wish to start a new game?","New Game",MB_YESNO|MB_ICONQUESTION);
			if (response == IDYES)
			{

				// The following variables have to bet set before the dialog appears
				// each time. Otherwise it will appear with no options checked.

				rbCategoryStatus1 = BST_CHECKED, rbCategoryStatus2 = BST_UNCHECKED,
				rbCategoryStatus3 = BST_UNCHECKED, rbCategoryStatus4 = BST_UNCHECKED,
				rbCategoryStatus5 = BST_UNCHECKED, rbCategoryStatus6 = BST_UNCHECKED;

				DialogBox(hInst,"CategoryDlg",hWnd,(DLGPROC) CategoryDlgProc);

				// If the CANCEL button has been pressed in the dialog box we do 
				// not want to load a file. Show the original start up screen instead.

				if (dialogButtonPress == OK)
				{

					// Ask Computer Opponent to start game and load appropriate file.
				
					switch(CategoryFileNumber)
					{
					case 1:
						successStatus = theComputerOpponent.startNewGame(szCategoryFile1);
						break;
					case 2:
						successStatus = theComputerOpponent.startNewGame(szCategoryFile2);
						break;
					case 3:
						successStatus = theComputerOpponent.startNewGame(szCategoryFile3);
						break;
					case 4:
						successStatus = theComputerOpponent.startNewGame(szCategoryFile4);
						break;
					case 5:
						successStatus = theComputerOpponent.startNewGame(szCategoryFile5);
						break;
					}

					if(successStatus == 0)	// if file doesn't load.
					{
						MessageBox(hWnd,"File Not Found.\nMake sure the category file is in the same folder as Hangman.exe","File Not Found",MB_OK|MB_ICONEXCLAMATION);
					}
					else
					{
						theComputerOpponent.getSecretWord(wordDisplay);
						theComputerOpponent.getCategory(categoryTitle);
						theComputerOpponent.getUsedGuessList(guessList);
						guessesLeft = theComputerOpponent.getNumGuessesLeft();

						HangmanDisplay.updateDisplay(memdc, hBlackPen, guessesLeft, 0, 0, wordDisplay, categoryTitle, guessList);
					}
				}
			}
			InvalidateRect(hWnd,NULL,TRUE);
			break;
			

		case ID_HANGMAN_EXIT:
			response = MessageBox(hWnd,"Are you sure you wish to exit?","Exit",MB_YESNO|MB_ICONQUESTION);
			if (response == IDYES)
				PostQuitMessage(0);
			break;

		case ID_HELP_ABOUT:
			DialogBox(hInst,"AboutDlgBox",hWnd,(DLGPROC) AboutDlgProc);
			break;
			
		default:
			break;
		}

		InvalidateRect(hWnd,NULL,TRUE);
		break;

	case WM_PAINT:
		hdc=BeginPaint(hWnd, &ps);
		
		// ------------------------- my routines below -------------------------------

		// Copy portion of virtual window, that needs repainted, into physical window.


		BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top,
			   ps.rcPaint.right - ps.rcPaint.left,
			   ps.rcPaint.bottom - ps.rcPaint.top,
			   memdc,
			   ps.rcPaint.left, ps.rcPaint.top,
			   SRCCOPY);


		// ------------------------- my routines above -------------------------------

		ValidateRect(hWnd,NULL);
		EndPaint(hWnd, &ps);	// finished processing paint messages.
		break;

	case WM_DESTROY:	// when user selects close option from application's system menu.
		DeleteObject(hBlackPen);
		DeleteObject(hNewFont);
		DeleteDC(memdc);
	    
		PostQuitMessage(0);	// terminates program.
		break;

	default:
		return(DefWindowProc(hWnd,message,wParam,lParam));	// empty's apps message queue of
															// unrecognised messages.
		break;
	}
	
	return 0;
}

BOOL CALLBACK CategoryDlgProc(HWND hdWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
	case WM_INITDIALOG:

		// Set default radio button.

		SendDlgItemMessage(hdWnd, IDC_RADIO1, BM_SETCHECK, rbCategoryStatus1, 0);
		return 1;

	case WM_COMMAND:

		switch(LOWORD(wParam))
		{
		case IDCANCEL:
			dialogButtonPress = CANCEL;
			EndDialog(hdWnd,0);
			return 1;

		case IDOK:

			// Update global radio button status variables

			rbCategoryStatus1 = SendDlgItemMessage(hdWnd, IDC_RADIO1, BM_GETCHECK, 0, 0);
			rbCategoryStatus2 = SendDlgItemMessage(hdWnd, IDC_RADIO2, BM_GETCHECK, 0, 0);
			rbCategoryStatus3 = SendDlgItemMessage(hdWnd, IDC_RADIO3, BM_GETCHECK, 0, 0);
			rbCategoryStatus4 = SendDlgItemMessage(hdWnd, IDC_RADIO4, BM_GETCHECK, 0, 0);
			rbCategoryStatus5 = SendDlgItemMessage(hdWnd, IDC_RADIO5, BM_GETCHECK, 0, 0);
			rbCategoryStatus6 = SendDlgItemMessage(hdWnd, IDC_RADIO6, BM_GETCHECK, 0, 0);

			// change szCategoryFileName[] to reflect the users category choice.


			if(rbCategoryStatus1 == BST_CHECKED)
				CategoryFileNumber = 1;

			if(rbCategoryStatus2 == BST_CHECKED)
				CategoryFileNumber = 2;

			if(rbCategoryStatus3 == BST_CHECKED)
				CategoryFileNumber = 3;

			if(rbCategoryStatus4 == BST_CHECKED)
				CategoryFileNumber = 4;

			if(rbCategoryStatus5 == BST_CHECKED)
				CategoryFileNumber = 5;

			dialogButtonPress = OK;
			EndDialog(hdWnd,0);
			return 1;
		}
	}
	return 0;
}

BOOL CALLBACK AboutDlgProc(HWND hdWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
	case WM_INITDIALOG:
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			EndDialog(hdWnd,0);
			break;

		default:
			return 0;
		}
		break;
		default:
			return 0;
	}
	return 1;
}


