// GridView.cpp : implementation file
//

#include "stdafx.h"
#include "Noughts.h"
#include "GameOptions.h"
#include "GridView.h"
#include "Globals.h"
#include "NoughtsDoc.h"
#include "Statistics.h"
#include "math.h"
#include "mmsystem.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

extern CString sDescription;
extern int Total_Nodes;		// both in CGridView

/////////////////////////////////////////////////////////////////////////////
// CGridView

IMPLEMENT_DYNCREATE(CGridView, CView)

CGridView::CGridView()
{
	lasttext = "Unwritten";
	startnew=false; 
	ResetComments();
	dwidth = 2;
	setdivider();
	if	(!nought.LoadBitmap(IDB_NOUGHT)) TRACE0("Failed to load nought bitmap!/n");
	if	(!cross.LoadBitmap(IDB_CROSS)) TRACE0("Failed to load cross bitmap!/n");
}

CGridView::~CGridView()
{
}

#define ID_SUGGESTION 0xE12D

BEGIN_MESSAGE_MAP(CGridView, CView)
	//{{AFX_MSG_MAP(CGridView)
	ON_WM_SIZE()
	ON_COMMAND(ID_EDIT_UNDO, OnUndo)
	ON_COMMAND(ID_POPUP_MARK, OnMark)
	ON_COMMAND(ID_POPUP_OPTIONS, OnOptions)
	ON_COMMAND(ID_VIEW_STATISTICS, OnStatistics)
	ON_COMMAND(ID_POPUP_NEW, OnNew)
	ON_COMMAND(ID_VIEW_VIEWOPTIONS, OnViewOptions)
	ON_COMMAND(ID_EDIT_CLEARALL, OnClearAll)
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_CONTEXTMENU()
	ON_WM_TIMER()
	ON_COMMAND(ID_OPTIONS_SUGGEST, OnSuggestion)
	ON_COMMAND(ID_OPTIONS_GAME, OnOptions)
	ON_COMMAND(ID_SUGGESTION, OnSuggestion)
	ON_COMMAND(ID_BUTTONSTATISTICS, OnStatistics)
	ON_WM_CAPTURECHANGED()
	ON_WM_CHAR()
	ON_WM_CANCELMODE()
	ON_COMMAND(ID_POPUP_UNDO, OnUndo)
	ON_COMMAND(ID_POPUP_STATISTICS, OnStatistics)
	ON_COMMAND(ID_POPUP_VIEWOPTIONS, OnViewOptions)
	ON_COMMAND(ID_RIGHTMOUSEMENU_SUGGESTION, OnSuggestion)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CGridView drawing

void CGridView::OnDraw(CDC* pDC)
{
	calcsize();

	CBrush	dividerbrush((COLORREF) 0x00);
	CBrush	slashbrush((COLORREF) 0x00FFFFFF);

	CString text = comment();

	pDC->SetBkColor(backcolour);

	if ((text!=lasttext) || (redrawgrid))
	{
		lasttext = text;

		if (commenttop-commentbottom>5)

		{
			RECT	textrect;
	
			textrect.top = cy-commenttop;
			textrect.bottom = cy-commentbottom;
			textrect.left = gridleft;
			textrect.right = cx-gridright;

			pDC->DrawEdge(&textrect, EDGE_ETCHED, BF_RECT );

			textrect.top = cy-commenttop+5;
			textrect.bottom = cy-commentbottom-5;
			textrect.left = gridleft+5;
			textrect.right = cx-gridright-5;


			CSize	fit = pDC->GetOutputTextExtent(text);

			int	flags = DT_VCENTER | DT_SINGLELINE;

			if (fit.cx<(textrect.right-textrect.left)) 

				flags = (flags | DT_CENTER);
					else
				flags = (flags | DT_LEFT);

			pDC->FillSolidRect(&textrect, backcolour);
			pDC->DrawText(text, &textrect, flags );

		}
	}

	if	((boxheight>0) && (boxwidth>0))		// Don't draw a box with negative coordinates
	{
	pDC->SelectObject(&slashbrush);

	RECT	dividerrect;

	dividerrect.top = gridtop;
	dividerrect.left = gridleft+boxwidth;
	dividerrect.bottom = cy-gridbottom;
	dividerrect.right = gridleft+boxwidth+dividerwidth;

	pDC->FillRect(&dividerrect, &dividerbrush);

	dividerrect.top = gridtop;
	dividerrect.left = cx-gridright-boxwidth-dividerwidth;
	dividerrect.bottom = cy-gridbottom;
	dividerrect.right = cx-gridright-boxwidth;

	pDC->FillRect(&dividerrect, &dividerbrush);

	dividerrect.top = gridtop+boxheight;
	dividerrect.left = gridleft;
	dividerrect.bottom = gridtop+boxheight+dividerheight;
	dividerrect.right = cx-gridright;

	pDC->FillRect(&dividerrect, &dividerbrush);

	dividerrect.top = cy-gridbottom-boxheight-dividerheight;
	dividerrect.left = gridleft;
	dividerrect.bottom = cy-gridbottom-boxheight;
	dividerrect.right = cx-gridright;

	pDC->FillRect(&dividerrect, &dividerbrush);

	CDC		noughttemp;
	CDC		crosstemp;

	pDC->SetStretchBltMode( HALFTONE );

	noughttemp.CreateCompatibleDC(pDC);
	noughttemp.SelectObject(&nought);

	crosstemp.CreateCompatibleDC(pDC);
	crosstemp.SelectObject(&cross);

	int c;
	for	(c=0; c<9; c++)
	{
		if (pDoc->statusbox[c]==1)
		{
			pDC->StretchBlt(boxx(c), boxy(c), boxwidth, boxheight, &crosstemp, 0, 0, 200, 200, SRCCOPY);
		}

		else

		if (pDoc->statusbox[c]==2)
		{
			pDC->StretchBlt(boxx(c), boxy(c), boxwidth, boxheight, &noughttemp, 0, 0, 200, 200, SRCCOPY);
		}
	}

	if (pDoc->slash)
	{

		float	slashmargin = (float) 0.2;
		float	slashextent = (float) 0.55;
		float	slashstart = (float) 0.45;
		
		RECT	slashrect;
		
		if ((pDoc->slash==1) | (pDoc->slash==2) | (pDoc->slash==3))

		{
			slashrect.top = (int) (gridtop+slashstart*boxheight+(pDoc->slash-1)*(boxheight+dividerheight));
			slashrect.left = (int) (gridleft+slashmargin*boxwidth);
			slashrect.bottom = (int) (gridtop+slashextent*boxheight+(pDoc->slash-1)*(boxheight+dividerheight));
			slashrect.right = (int) (cx-gridright-slashmargin*boxwidth);

		POINT	slashpoint;
		slashpoint.x = boxwidth/10;
		slashpoint.y = boxwidth/10;

		if (pDoc->rounded) pDC->RoundRect(&slashrect, slashpoint);
		else pDC->FillRect(&slashrect, &slashbrush);

		}

		else

		if ((pDoc->slash==4) | (pDoc->slash==5) | (pDoc->slash==6))

		{
			slashrect.left = (int) (gridleft+slashstart*boxwidth+(pDoc->slash-4)*(boxwidth+dividerwidth));
			slashrect.top = (int) (gridtop+slashmargin*boxheight);
			slashrect.right = (int) (gridleft+slashextent*boxwidth+(pDoc->slash-4)*(boxwidth+dividerwidth));
			slashrect.bottom = (int) (cy-gridbottom-slashmargin*boxheight);

		POINT	slashpoint;
		slashpoint.x = boxwidth/10;
		slashpoint.y = boxwidth/10;
		if (pDoc->rounded) pDC->RoundRect(&slashrect, slashpoint);
		else 
		{
			pDC->FillRect(&slashrect, &slashbrush);

		}
		}

	else

		if (pDoc->slash==7) 
		{	
			POINT	diagpoint[4];

			float	diffover  = (float) ((slashextent-0.5)/sqrt(2)+0.5);
			float	diffunder = (float) ((slashstart-0.5)/sqrt(2)+0.5);

			diagpoint[0].x = (int) (gridleft+diffunder*boxwidth);
			diagpoint[0].y = (int) (gridtop+diffover*boxheight);
			diagpoint[1].x = (int) (gridleft+diffover*boxwidth);
			diagpoint[1].y = (int) (gridtop+diffunder*boxheight);
			diagpoint[2].x = (int) (cx-gridright-diffunder*boxwidth);
			diagpoint[2].y = (int) (cy-gridbottom-diffover*boxheight);
			diagpoint[3].x = (int) (cx-gridright-diffover*boxwidth);
			diagpoint[3].y = (int) (cy-gridbottom-diffunder*boxheight);

			pDC->Polygon(&diagpoint[0], 4);
		}
	else
		if (pDoc->slash==8)
		{
			POINT	diagpoint[4];

			float	diffover  = (float) ((slashextent-0.5)/sqrt(2)+0.5);
			float	diffunder = (float) ((slashstart-0.5)/sqrt(2)+0.5);

			diagpoint[0].x = (int) (cx-gridright-diffunder*boxwidth);
			diagpoint[0].y = (int) (gridtop+diffover*boxheight);
			diagpoint[1].x = (int) (gridleft+diffover*boxwidth);
			diagpoint[1].y = (int) (cy-gridbottom-diffunder*boxheight);
			diagpoint[2].x = (int) (gridleft+diffunder*boxwidth);
			diagpoint[2].y = (int) (cy-gridbottom-diffover*boxheight);
			diagpoint[3].x = (int) (cx-gridright-diffover*boxwidth);
			diagpoint[3].y = (int) (gridtop+diffunder*boxheight);

			pDC->Polygon(&diagpoint[0], 4);
		}
	}

	if (pDoc->raised)
	{
		pDC->Draw3dRect(gridleft, gridtop, cx-gridleft-gridright+1, cy-gridtop-gridbottom+1, 0x00FFFFFF, 0x00404040);
	}
	}
}

/////////////////////////////////////////////////////////////////////////////
// CGridView diagnostics

#ifdef _DEBUG
void CGridView::AssertValid() const
{
	CView::AssertValid();
}

void CGridView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CGridView message handlers

BOOL CGridView::PreCreateWindow(CREATESTRUCT& cs) 
{
    cs.style =  cs.style | WS_BORDER | WS_DLGFRAME;
	cs.dwExStyle = cs.dwExStyle | WS_EX_WINDOWEDGE;
	
	return CView::PreCreateWindow(cs);
}

void CGridView::OnSize(UINT nType, int newcx, int newcy) 
{
	calcsize();
	ResetComments();
	cx = newcx;
	cy = newcy;
	redrawgrid = true;

	CView::OnSize(nType, newcx, newcy);
}

void CGridView::OnUndo() 
{
	redrawgrid = true;
	pDoc->undo();
	ResetComments();
}


BOOL CGridView::OnEraseBkgnd(CDC* pDC) 
{
	RECT viewcoords ;
	CWnd* pWnd = pDC->GetWindow();
	pWnd->GetClientRect(&viewcoords);
	pDC->FillSolidRect(0,0,viewcoords.right,viewcoords.bottom,backcolour);

	return TRUE;
}

void CGridView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	int p=pDoc->pturn();
	if (((p==1) && (pDoc->player1==0)) | ((p==2) && (pDoc->player2==0)) && (!pDoc->winner))

	{
	int	box=boxhit(point.x, point.y);
	if (box) selectbox(box-1);
	CView::OnLButtonDown(nFlags, point);
	}
}

int CGridView::boxx(int box)
{
	if ((box == 0) | (box == 3) | (box == 6))	return (gridleft);
	if ((box == 1) | (box == 4) | (box == 7))	return (gridleft+boxwidth+dividerwidth);
	if ((box == 2) | (box == 5) | (box == 8))	return (cx-gridright-boxwidth);
	abort();
	return	-1;
}

int CGridView::boxy(int box)
{
	if ((box == 0) | (box == 1) | (box == 2))	return (gridtop);
	if ((box == 3) | (box == 4) | (box == 5))	return (gridtop+boxheight+dividerheight);
	if ((box == 6) | (box == 7) | (box == 8))	return (cy-gridbottom-boxheight);
	abort();
	return	-1;
}


int CGridView::boxhit(int x, int y)
{
	commentcount = 0;

	int	tester = gridleft;
	if ( (x > tester) && (x < (tester + boxwidth))) return testvertical(y, 1);
		else

		{
		tester = tester + boxwidth + dividerwidth;
		if ( (x > tester) && (x < (tester + boxwidth))) return testvertical(y, 2);
			else

			{
			tester = tester + boxwidth + dividerwidth;
			if ( (x > tester) && (x < (tester + boxwidth))) return testvertical(y, 3);
			}
		}

	return 0;
}

void CGridView::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	CMenu	*Floatingmenu;
	m_floatmenu.LoadMenu(IDR_POPUPMENU);
	ScreenToClient(&point);
	if (boxhit(point.x, point.y) && !pDoc->statusbox[boxhit(point.x, point.y)-1] && (!pDoc->winner)) 
		Floatingmenu = m_floatmenu.GetSubMenu(1);
	else Floatingmenu = m_floatmenu.GetSubMenu(0);

	if (pDoc->count()) 
		Floatingmenu->EnableMenuItem(ID_POPUP_UNDO, MF_ENABLED);
		else
		Floatingmenu->EnableMenuItem(ID_POPUP_UNDO, MF_GRAYED);

	menux=point.x;
	menuy=point.y;
	ClientToScreen(&point);
	Floatingmenu->TrackPopupMenu(TPM_RIGHTBUTTON | TPM_LEFTALIGN,point.x,point.y, this);
	m_floatmenu.DestroyMenu();
}

int CGridView::testvertical(int y, int box)
{
	int	tester = gridtop;

	if ( (y > tester) && (y < (tester + boxheight))) return box;

	tester =	tester + boxheight + dividerheight;
	if ( (y > tester) && (y < (tester + boxheight))) return box + 3;

	tester = tester + boxheight + dividerheight;
	if ( (y > tester) && (y < (tester + boxheight))) return box + 6;

	return 0;

}

void CGridView::OnMark()
{
	selectbox(boxhit(menux, menuy)-1);
}

void CGridView::OnNew()
{
	pDoc->clearall();
	ResetComments();
}

int CGridView::selectbox(int box)
{
	ResetComments();

	if ((boxwidth>4) && (boxheight>4))

	{
	if (( pDoc->statusbox[box] ) != 0 )	return 0;
	pDoc->setbox(box);
	redrawgrid = true;

	if (pDoc->statisticsbox.windowon)
	{
		pDoc->statisticsbox.Invalidate();
		pDoc->statisticsbox.UpdateWindow();
	}
	return box;
	}

	return 0;
}

void CGridView::OnStatistics()
{	
	pDoc->statisticsbox.dostats();
}


void CGridView::OnClearAll()

{
	startnew=FALSE;
	pDoc->clearall();
}

void CGridView::OnViewOptions()

{
	ViewOptions vopt(this);
	vopt.autostart = pDoc->autostart;
	vopt.raised = pDoc->raised;
	vopt.rounded = pDoc->rounded;
	vopt.dwidth = dwidth;

	if (vopt.DoModal() == IDOK)

	{
	PlaySound((LPCSTR)IDR_SOUND3, AfxGetInstanceHandle(), SND_RESOURCE | SND_NOWAIT );

	pDoc->autostart = vopt.autostart;
	if (pDoc->raised != vopt.raised) 
	{
		pDoc->raised = vopt.raised;
		Invalidate();
		UpdateWindow();
	}
	
	if (pDoc->rounded != vopt.rounded)
	{
		pDoc->rounded = vopt.rounded;
		if (pDoc->slash)
		{
			Invalidate();
			UpdateWindow();
		}

	}

	pDoc->SetModifiedFlag(FALSE);

	if (dwidth != vopt.dwidth)
	{
		dwidth = vopt.dwidth;
		setdivider();
		Invalidate(TRUE);
		UpdateWindow();
	}
	}
}

void CGridView::OnOptions()

{
	CGameOptions	gopt(this);
	
	gopt.player1 = pDoc->player1;
	gopt.player2 = pDoc->player2;
	gopt.noswap = pDoc->noswap;

	if (gopt.DoModal()==IDOK)
	{
	pDoc->player1 = gopt.player1;
	pDoc->player2 = gopt.player2;
	pDoc->noswap = gopt.noswap;
	PlaySound((LPCSTR)IDR_SOUND3, AfxGetInstanceHandle(), SND_RESOURCE | SND_NOWAIT );
	}

	ResetComments();
}

CString CGridView::comment()
{
	CString nodes;

	if (pDoc->result())		// If there is a result on the board...
	{
		switch (commentcount)
		{
	case 0:
	case 1:
	case 2:
		if (pDoc->result()==1)
			return "Crosses wins!";
			else return "Noughts wins!";	// Who wins?
	case 3:
	case 4:
		if (pDoc->WinningPlayer()==1)
			return "Player 1 wins!";
			else return "Player 2 wins!";	// Who wins?

	case 5:
		nodes = "For that last move, I searched through ";
		char buffer[33];
		nodes += _itoa (Total_Nodes, buffer, 10);
		nodes +=" moves.";
		return nodes;

	case 6:
		if (pDoc->WinningPlayer()==1)
			if (pDoc->player1)
			return "Computer wins!";
			else return "You win!";			// Who wins?

		if (pDoc->WinningPlayer()==2)
			if (pDoc->player2)
			return "Computer wins!";
			else return "Human wins!";	// Who wins?

	case 7:
		if (pDoc->multipledirections) return "Winning in multiple directions!";
		else return "Ready for another game?";
		}
	}

	if (pDoc->count()==0) 
	{
		switch (commentcount)
		{
		case 0:
		case 1:
		case 2:
			return "New game";
		case 3:
		case 4:
		case 5:
			return "Make your move";
		case 6:
		case 7:
			return "Noughts and Crosses";
		}

	}

	if (pDoc->count()==9)
	{
		switch (commentcount)
		{
		case 0:
		case 1:
		case 2:
			return "It's a draw";
		case 3:
		case 4:
		case 5:
			return "New game?";
		case 6:
		nodes = "For that last move, I searched through ";
		char buffer[33];
		nodes += _itoa (Total_Nodes, buffer, 10);
		nodes +=" moves.";
		return nodes;

		case 7:
			return "Noughts and Crosses";
		}
	}

	switch (commentcount)
	{
	case 0:
	case 1:
	case 2:
		if ((pDoc->player1==1) || (pDoc->player2==1))	// If computer is playing properly
		return sDescription;
	case 3:
	case 4:
		return "Your move";
	case 5:
	case 6:
		nodes = "For my last move, I searched through ";
		char buffer[33];
		nodes += _itoa (Total_Nodes, buffer, 10);
		nodes +=" moves.";
		return nodes;
	case 7:
		if (pDoc->turn()==1)
			return "It's crosses' turn to move";
		else
			return "It's noughts' turn to move";

	}

	return "Noughts and Crosses";
}

void CGridView::OnInitialUpdate() 
{
	CView::OnInitialUpdate();
	
	pDoc = (CNoughtsDoc*) GetDocument();
	SetTimer(1, timeout, NULL);
	SetTimer(2, timeout*6, NULL);
}

void CGridView::OnTimer(UINT nIDEvent) 
{
	pDoc->computerturn();		// Let the computer move every half second if it should

	if (nIDEvent==1) return;	// Wait for the major timer

	commentcount++;
	if (commentcount>7) commentcount = 0;		// Change comments every 2 seconds

	Invalidate(FALSE);
	UpdateWindow();

	if (startnew)
	{
		PlaySound((LPCSTR)IDR_SOUND2, AfxGetInstanceHandle(), SND_RESOURCE | SND_NOWAIT );
		OnNew();
		startnew=false;
	}

	if ((pDoc->winner) && (pDoc->autostart) && (pDoc->countwin)) startnew=true;
	if ((pDoc->count()==9) && (pDoc->autostart) && (pDoc->countwin)) startnew=true;

	CView::OnTimer(nIDEvent);
}

void CGridView::setdivider()
{
	switch (dwidth)
	{
	case 1: dividerfraction = (float) 0.075;
			break;

	case 2: dividerfraction = (float) 0.1;
			break;

	case 3: dividerfraction = (float) 0.125;
			break;

	case 4: dividerfraction = (float) 0.15;
			break;
	default:	abort();
	}

	redrawgrid = true;
	calcsize();
}

void CGridView::calcsize()
{
	gridleft = cx/10;
	if (gridleft>dgridleft) gridleft = dgridleft;

	gridright = cx/10;
	if (gridright>dgridright) gridright = dgridright;

	gridtop = cy/10;
	if (gridtop>dgridtop) gridtop = dgridtop;

	gridbottom = dgridbottom;
	
	dividerwidth = (int) ((cx-gridleft-gridright) * dividerfraction / 2) ;
	if (dividerwidth<2) dividerwidth=2;

	dividerheight = (int) ((cy-gridtop-gridbottom) * dividerfraction / 2);
	if (dividerwidth<2) dividerwidth=2;

	boxwidth = (int) ((cx-gridleft-gridright) * (1-dividerfraction) / 3);
	boxheight = (int) ((cy-gridtop-gridbottom) * (1-dividerfraction) / 3);
}

void CGridView::OnSuggestion()
{
	if (pDoc->count()==9) return;
	if (pDoc->winner) return;
	int square = pDoc->computemove(pDoc->statusbox);
	CString message;
	switch (square)
	{
	case 0: message = "I suggest the top-left square";
		break;
	case 1:
		message = "The top centre square is the best";
		break;
	case 2:
		message = "The top-right square is best";
		break;
	case 3:
		message = "The left centre square looks good";
		break;
	case 4:
		message = "I recommend taking the centre square";
		break;
	case 5:
		message = "How about the right centre square?";
		break;
	case 6:
		message = "The lower left square";
		break;
	case 7:
		message = "Take the lower centre square";
		break;
	case 8:
		message = "The lower right square is the best for you";
		break;
	}

	MessageBox(message, "Suggestion", MB_OK | MB_ICONINFORMATION);
}


void CGridView::ResetComments()
{
	commentcount = 0;
}
