/*
  Copyright (C) 2005 Dan Alcantara (dfalcantara@ucdavis.edu, http://wwwcsif.cs.ucdavis.edu/~alcantar)
  Based on the board game "Blokus" by Educational Insights, Inc.
  
  This program is free software; you can redistribute 
  it and/or modify it under the terms of the GNU General 
  Public License as published by the Free Software Foundation; 
  either version 2 of the License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 
  more details.
  
  You should have received a copy of the GNU General Public License along 
  with this program; if not, write to the Free Software Foundation, Inc., 
  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  
  Full license visible at http://www.opensource.org/licenses/gpl-license.php
*/

#include <iostream>
#include <fstream>

#include <wx/wx.h>
#include <wx/glcanvas.h>
#include <wx/colordlg.h>
#include <wx/filedlg.h>
#include <wx/utils.h>

#include "game.h"
#include "options.h"
#include "help.h"
#include "docs.h"
#include "blokish.xpm"

#ifndef WIN32
	#include <sys/types.h>
	#include <pwd.h>
#endif

enum
{
  ID_NEW_GAME = 100,
  ID_CHANGE_FIRST,
  ID_CHANGE_SECOND,
  ID_CHANGE_THIRD,
  ID_CHANGE_FOURTH,
  ID_LOAD_GAME,
  ID_SAVE_GAME,
  ID_CHANGE_AI,
  ID_SHOW_HELP,
  ID_SHOW_ABOUT
};

class BlokishAIThread:public wxThread
{
public:
  BlokishAIThread(BlokishGame *game):wxThread(wxTHREAD_JOINABLE)
  {
    mpGame = game;
    mpGame->SetOutcome(BlokishGame::WaitForAI);
    Create();
  }

  virtual ExitCode Entry()
  {
    mpGame->ResetAI();
    mpGame->TakeAIStep();
    return 0;
  }

protected:
  BlokishGame *mpGame;
};
BlokishAIThread	*gAIThread;
bool gbAllowNewThreadToStart = true;;

class BlokishCanvas:public wxGLCanvas
{
  struct Color
  {
    Color(float r = 0, float g = 0, float b = 0, float a = 0)
    {
      values[0] = r;
      values[1] = g;
      values[2] = b;
      values[3] = a;
    }

    float& operator[](int which)
    {
      return values[which];
    }

    float values[4];
  };

public:
  BlokishCanvas(wxWindow *parent, wxWindowID id, wxPoint pos, wxSize size, int attribList[], BlokishGame &game)
    :wxGLCanvas(parent, id, pos, size, 0, wxString::FromAscii(""), attribList), mpGame(&game)
  {
    mnNumPiecesPerRow = 7;
    
    mvColor.clear();
    mvColor.reserve(4);

    #ifdef WIN32
      std::ifstream settingsFile(".blokishrc", std::ios::binary);
    #else
			wxString filename = wxGetHomeDir();
      filename += wxString::FromAscii("/.blokishrc");
      std::ifstream settingsFile(filename.fn_str(), std::ios::binary);
    #endif

    if(false == settingsFile.is_open())
    {
      mvColor.push_back(Color(0.25, 0.25, 1.00));
      mvColor.push_back(Color(1.00, 1.00, 0.25));
      mvColor.push_back(Color(1.00, 0.25, 0.25));
      mvColor.push_back(Color(0.25, 1.00, 0.25));
    }
    else
    {
      AI::AIOptions *options = mpGame->GetOptions();

      for(unsigned int nPlayer = 0; nPlayer < 4; nPlayer++)
      {
        float color[3];
        for(unsigned int nColor = 0; nColor < 3; nColor++)
        {
          settingsFile.read((char*) &(color[nColor]), sizeof(color[nColor]));
        }
        mvColor.push_back(Color(color[0], color[1], color[2]));

        settingsFile.read((char*) &(options[nPlayer]), sizeof(options[nPlayer]));

      }
    }
  }

  void WriteSettings()
  {
    AI::AIOptions *options = mpGame->GetOptions();

    #ifdef WIN32
      std::cout << "Saving to .blokishrc.\n";
      std::ofstream settingsFile(".blokishrc", std::ios::binary);
    #else
			wxString filename = wxGetHomeDir();
      filename += wxString::FromAscii("/.blokishrc");
      std::ofstream settingsFile(filename.fn_str(), std::ios::binary);

      if(false == settingsFile.is_open())
      {
				std::cerr << "Failed to open file for writing.\n";
				exit(1);
      }
    #endif

    for(unsigned int nPlayer = 0; nPlayer < 4; nPlayer++)
    {
      for(unsigned int nColor = 0; nColor < 3; nColor++)
      {
        settingsFile.write((char *) &(mvColor[nPlayer][nColor]), sizeof(mvColor[nPlayer][nColor]));
      }
      
      settingsFile.write((char*) &(options[nPlayer]), sizeof(options[nPlayer]));
    }
  }

  void OnIdle(wxIdleEvent &event)
  {
    if(gbAllowNewThreadToStart && mpGame->GetOutcome() == BlokishGame::BeginAIThread)
    {
      gAIThread = new BlokishAIThread(mpGame); 
      gAIThread->Run();

      Refresh(false);
      Update();
    }
    else if(mpGame->GetOutcome() == BlokishGame::WaitForAI)
    {
			Refresh(false);
    }
    else if(mpGame->GetOutcome() == BlokishGame::FinishAIThread)
    {
      if(gAIThread != NULL)
      {
        if(gAIThread->IsRunning())
        {
			    gAIThread->Delete();
        }
			  delete gAIThread;
        gAIThread = NULL;
      }

      if(mpGame->FinishTurn() == BlokishGame::GameOver)
      {
        wxMessageBox(wxString::FromAscii("Game over!"));
      }

      Refresh(false);
      Update();
    }
    else if(mpGame->GetOutcome() == BlokishGame::GetPlayerInput)
    {
      Refresh(false);
    }

    event.Skip();
    event.RequestMore();
  }

  void OnDraw(wxPaintEvent &event)
  {
    wxPaintDC(this);
    SetCurrent();

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    wxSize size = GetClientSize();

    // Reset
    glViewport(0, 0, size.GetWidth() / 2, size.GetHeight());
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-.01, mpGame->GetNumCols()+.01, -.01, mpGame->GetNumRows()+.01, -1, 1);

    glClear(GL_COLOR_BUFFER_BIT);

    // Draw the board
    glBegin(GL_QUADS);
      glColor4f(1, 1, 1, 0.9);
      glVertex2f(0, 0);
      glColor4f(1, 1, 1, 0.95);
      glVertex2f(0, mpGame->GetNumRows());
      glColor4f(1, 1, 1, 1.0);
      glVertex2f(mpGame->GetNumCols(), mpGame->GetNumRows());
      glColor4f(1, 1, 1, 0.95);
      glVertex2f(mpGame->GetNumCols(), 0);
    glEnd();

    glBegin(GL_LINES);
      glColor4f(0, 0, 0, 1);
      glVertex2f(mpGame->GetNumCols(), 0);
      glVertex2f(mpGame->GetNumCols(), mpGame->GetNumRows());
    glEnd();
    
    float round = mpGame->GetRound();
    
    for(int nRow = int(mpGame->GetNumRows()-1); nRow >= 0; nRow--)
    {
      for(int nCol = 0; nCol < mpGame->GetNumCols(); nCol++)
      {
        glPushMatrix();
        glTranslatef(nCol, nRow, 0);

        int age = int(round) - mpGame->GetAge(nRow, nCol);

        float factor = 0.0;
        if(age < 4)
        {
          age = 4 - age;
          factor = age / 4.0;
        }

        DrawSquare(mpGame->GetSpaceID(nRow, nCol), 1.0f);

				// Draws dots on the more recent pieces.
        if(mpGame->GetSpaceID(nRow, nCol) < MAX_PLAYER_ID && factor != 0.0f)
          DrawSquare(usable, factor, false);
        
				// Draws dots where pieces could fit
        if(mpGame->IsUsableSpace(nRow, nCol))
        {
          DrawSquare(usable, 1.0, false);
        }

        glPopMatrix();
      }
    }

    glViewport(size.GetWidth() / 2, 0, size.GetWidth() / 2, size.GetHeight());
    glLoadIdentity();
    glOrtho(0, mnNumPiecesPerRow*6, 0, mnNumPiecesPerRow*6, -1, 1);

    glBegin(GL_QUADS);
      glColor4f(.75, .75, .75, 1);
      glVertex2f(0, 0);
      glColor4f(.875, .875, .875, 1);
      glVertex2f(0, mnNumPiecesPerRow*6);
      glColor4f(1, 1, 1, 1);
      glVertex2f(mnNumPiecesPerRow*6, mnNumPiecesPerRow*6);
      glColor4f(.875, .875, .875, 1);
      glVertex2f(mnNumPiecesPerRow*6, 0);
    glEnd();
      
    // Draw the unused pieces
    if(false == mpGame->IsGameOver())
    { // Draw the current player's unused pieces
      DrawUnusedPlayerPieces();

      // Draw the players' pieces in the top
      for(int player = 0; player < 4; player++)
      {
        mnNumPiecesPerRow = int(sqrt(mpGame->GetUnplayedPlayerPieces(BlokishID(player)).size()) + 1);
        glLoadIdentity();
        glOrtho(-1, mnNumPiecesPerRow*6 + 1, -1, mnNumPiecesPerRow*6 + 1, -1, 1);
        
        int boxDimension = size.GetWidth() / 8;
        int boxBorder = 0;

        glViewport( int((size.GetWidth()*(5.00 + player * 1.25))/ 10) + boxBorder, 
                    size.GetHeight() - boxDimension - boxBorder - 1, 
                    boxDimension - 2*boxBorder, 
                    boxDimension - 2*boxBorder);

        // Draw the box surrounding the pieces
        if(mpGame->IsDead(BlokishID(player), true))
          glColor4f(.35, .35, .35, .75);
        else
          glColor4f(mvColor[player][0], mvColor[player][1], mvColor[player][2], 1);

        glPushMatrix();
        glLoadIdentity();
        glOrtho(0, 1, 0, 1, -1, 1);
        DrawSquare(BlokishID(player), 1.0f, true, mpGame->IsDead(BlokishID(player), true));
        glPopMatrix();

        DrawUnusedPlayerPieces(BlokishID(player));
      }

      mnNumPiecesPerRow = 7;
    }
    else
    { // Draw all the players' unused pieces evenly
      int maxPieces = 0;
      maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(first).size()));
      maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(second).size()));
      maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(third).size()));
      maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(fourth).size()));

      mnNumPiecesPerRow = int(sqrt(float(maxPieces)) + 1);

      glLoadIdentity();
      glOrtho(0, mnNumPiecesPerRow*6, 0, mnNumPiecesPerRow*6, -1, 1);

      glViewport(size.GetWidth() / 2, size.GetHeight()/2, size.GetWidth() / 4, size.GetHeight()/2);
      glPushMatrix();
      glLoadIdentity();
      glOrtho(0, 1, 0, 1, -1, 1);
      DrawSquare(first);
      glPopMatrix();
      DrawUnusedPlayerPieces(first);

      glViewport(size.GetWidth() * 3 / 4, size.GetHeight()/2, size.GetWidth() / 4, size.GetHeight()/2);
      glPushMatrix();
      glLoadIdentity();
      glOrtho(0, 1, 0, 1, -1, 1);
      DrawSquare(second);
      glPopMatrix();
      DrawUnusedPlayerPieces(second);
      
      glViewport(size.GetWidth() / 2, 0, size.GetWidth() / 4, size.GetHeight()/2);
      glPushMatrix();
      glLoadIdentity();
      glOrtho(0, 1, 0, 1, -1, 1);
      DrawSquare(third);
      glPopMatrix();
      DrawUnusedPlayerPieces(third);

      glViewport(size.GetWidth() * 3 / 4, 0, size.GetWidth() / 4, size.GetHeight()/2);
      glPushMatrix();
      glLoadIdentity();
      glOrtho(0, 1, 0, 1, -1, 1);
      DrawSquare(fourth);
      glPopMatrix();
      DrawUnusedPlayerPieces(fourth);

      mnNumPiecesPerRow = 7;
    }

    // Draw the selected piece
    if(mpGame->GetOutcome() < BlokishGame::DOING_AI)
    {
      BlokishPiece *selectedPiece = mpGame->GetSelectedPiece();
      if(selectedPiece != NULL)
      {
        glViewport(0, 0, size.GetWidth(), size.GetHeight());
        glLoadIdentity();
        glOrtho(0, mpGame->GetNumCols() * 2, 0, mpGame->GetNumRows(), -1, 1);

        float xCoordinate = mpGame->GetNumCols() * 2.0 * mMouseX / float(size.GetWidth());
        float yCoordinate = mpGame->GetNumRows() * (size.GetHeight() - mMouseY) / float(size.GetHeight());

        glTranslatef(xCoordinate - .5, yCoordinate - .5, 0);

        // Translate the window position into a board position
        int row = int(mpGame->GetNumRows() * (size.GetHeight() - mMouseY) / float(size.GetHeight()));
        int col = int(mpGame->GetNumCols() * mMouseX / float(size.GetWidth() / 2.0));
        if(mpGame->PieceCanBePlaced(selectedPiece, row, col))
          DrawPiece(selectedPiece, 1.0f);
        else
          DrawPiece(selectedPiece, 0.5f);
      }
    }

    SwapBuffers();
  }

  void DrawUnusedPlayerPieces(BlokishID player = none)
  {
    glPushMatrix();

    std::vector<BlokishPiece*> &pieces = mpGame->GetUnplayedPlayerPieces(player);

    glTranslatef(3, 3, 0);
    int piecesDrawn = 0;
    for(std::vector<BlokishPiece*>::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
    {
      DrawPiece(*itr, 1.0f);

      piecesDrawn++;
      if(piecesDrawn % mnNumPiecesPerRow == 0)
      {
        piecesDrawn = 0;
        glTranslatef(-6 * (mnNumPiecesPerRow - 1), 6, 0);
      }
      else
      {
        glTranslatef(6, 0, 0);
      }
    }
    glPopMatrix();
  }

  void DrawPiece(BlokishPiece *piece, float alpha = 1.0f)
  {
    const std::vector<Square> &squares = piece->GetSquares();

    for(std::vector<Square>::const_iterator itr = squares.begin(); itr != squares.end(); itr++)
    {
      glPushMatrix();
      glTranslatef(itr->mX, itr->mY, 0);
      DrawSquare(piece->GetOwner(), alpha);
      glPopMatrix();
    }
  }

  void DrawSquare(const BlokishID id, float alpha = 1.0, bool drawBox = true, bool gray = false)
  {
    const float bounds[] = {.15, .85};

    if(drawBox)
    {
      glBegin(GL_LINE_LOOP);
        glColor4f(0,0,0,.1);
        glVertex2f(0,0);
        glVertex2f(0,1);
        glVertex2f(1,1);
        glVertex2f(1,0);
      glEnd();
    }
    
    if(id == usable)
    {
      glColor4f(0, 0, 0, .25 * alpha);
      
      glBegin(GL_TRIANGLE_FAN);
        glVertex2f(.5, .5);

        for(float i = 0; i <= M_PI * 2; i += .157)
        {
          glVertex2f(.5 + .1*cos(i), .5 + .1*sin(i));
        }

      glEnd();
    }
    else if(id == first || id == second || id == third || id == fourth)
    {
      glBegin(GL_QUADS);
        if(gray)
          glColor4f(.75, .75, .75, alpha * .75);
        else
          glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .75);
        glVertex2f(bounds[0], bounds[0]);
        glVertex2f(bounds[0], bounds[1]);
        glVertex2f(bounds[1], bounds[1]);
        glVertex2f(bounds[1], bounds[0]);
      glEnd();

      glBegin(GL_QUADS);
        if(gray)
          glColor4f(.75, .75, .75, alpha);
        else
          glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha);
        glVertex2f( 0,  0);
        glVertex2f( 0,  1);
        glVertex2f(bounds[0],  bounds[1]);
        glVertex2f(bounds[0],  bounds[0]);

        if(gray)
          glColor4f(.75, .75, .75, alpha * .80);
        else
          glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .80);
        glVertex2f(0, 0);
        glVertex2f(1, 0);
        glVertex2f(bounds[1], bounds[0]);
        glVertex2f(bounds[0], bounds[0]);
        
        if(gray)
          glColor4f(.75, .75, .75, alpha * .60);
        else
          glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .60);
        glVertex2f(1, 0);
        glVertex2f(1, 1);
        glVertex2f(bounds[1], bounds[1]);
        glVertex2f(bounds[1], bounds[0]);

        if(gray)
          glColor4f(.75, .75, .75, alpha * .40);
        else
          glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .40);
        glVertex2f(0, 1);
        glVertex2f(1, 1);
        glVertex2f(bounds[1], bounds[1]);
        glVertex2f(bounds[0], bounds[1]);
      glEnd();

      glBegin(GL_LINE_LOOP);
        if(gray)
          glColor4f(.25, .25, .25, 1.0);
        else
          glColor4f(mvColor[id][0] * .25, mvColor[id][1] * .25, mvColor[id][2] * .25, 1.0);
        glVertex2f(0,0);
        glVertex2f(0,1);
        glVertex2f(1,1);
        glVertex2f(1,0);
      glEnd();
    }
  }

  void OnErase(wxEraseEvent &event)
  {
    event.Skip();
  }

  void OnRightDown(wxMouseEvent &event)
  {
    ReflectHorizontal();
  }

  void ReflectHorizontal()
  {
    BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
    if(outcome == BlokishGame::BeginAIThread || outcome == BlokishGame::WaitForAI || outcome == BlokishGame::FinishAIThread)
    { // Don't interfere.
      return;
    }
    
    if(mpGame->GetSelectedPiece() != NULL)
    {
      mpGame->GetSelectedPiece()->ReflectHorizontal();
    }
    else
    {
      PieceVector &pieces = mpGame->GetUnplayedPlayerPieces();

      for(PieceVector::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
      {
        (*itr)->ReflectHorizontal();
      }
    }
  }

  void OnLeftDown(wxMouseEvent &event)
  {

    BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
    if(outcome > BlokishGame::DOING_AI)
    { // Don't interfere.
      return;
    }

    wxPaintDC(this);
    SetCurrent();

    wxSize size = GetClientSize();

    if(event.GetX() >= size.GetWidth() / 2)
    { // Picking a new piece
      float pixel[3];
      glReadBuffer(GL_FRONT);
      glReadPixels(event.GetX(), size.GetHeight() - event.GetY(), 1, 1, GL_RGB, GL_FLOAT, pixel);

      int displayWidth = size.GetWidth() / 2;
      int displayHeight = size.GetHeight();

      int displayPieceWidth = displayWidth / mnNumPiecesPerRow;
      int displayPieceHeight = displayHeight / mnNumPiecesPerRow;
      
      int x = event.GetX() - size.GetWidth() / 2;
      int y = size.GetHeight() - event.GetY();

      int row = y / displayPieceHeight;
      int col = x / displayPieceWidth;
      int nPiece = row * mnNumPiecesPerRow + col;

      mpGame->SetSelectedPiece(nPiece);
    }
    else
    { // Clicking on the board
      if(mpGame->GetSelectedPiece() != NULL)
      {
        // Translate the window position into a board position
        int row = int(mpGame->GetNumRows() * (size.GetHeight() - event.GetY()) / float(size.GetHeight()));
        int col = int(mpGame->GetNumCols() * event.GetX() / float(size.GetWidth() / 2.0));

        if(mpGame->PlacePiece(mpGame->GetSelectedPiece(), row, col))
        {
          if(BlokishGame::GameOver == mpGame->FinishTurn())
          {
            Refresh(false);
            Update();
            wxMessageBox(wxString::FromAscii("Game over!"));
          }
        }
      }
    }
  }

  void OnMotion(wxMouseEvent &event)
  {
    mMouseX = event.GetX();
    mMouseY = event.GetY();
    event.Skip();
  }
  
  void ChangeColor(int which)
  {
    wxColour newColour = wxGetColourFromUser(this);
    if(newColour.Ok())
    {
      mvColor[which][0] = newColour.Red() / 255.0f;
      mvColor[which][1] = newColour.Green() / 255.0f;
      mvColor[which][2] = newColour.Blue() / 255.0f;
    }

    WriteSettings();
  }

  void OnMousewheel(wxMouseEvent &event)
  {
    if(event.GetWheelRotation() < 0)
    {
      RotateClockwise();
    }
    else
    {
      RotateCounterClockwise();
    }
  }
  
  void OnKeyDown(wxKeyEvent &event)
  {
    if(event.GetKeyCode() == WXK_LEFT)
    {
      RotateCounterClockwise();
    }
    else if(event.GetKeyCode() == WXK_RIGHT)
    {
      RotateClockwise();
    }
  }

  void RotateCounterClockwise()
  {
    BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
    if(outcome == BlokishGame::BeginAIThread || outcome == BlokishGame::WaitForAI || outcome == BlokishGame::FinishAIThread)
    { // Don't interfere.
      return;
    }

    if(mpGame->GetSelectedPiece() != NULL)
    {
      mpGame->GetSelectedPiece()->RotateCounterClockwise();
    }
    else
    {
      PieceVector &pieces = mpGame->GetUnplayedPlayerPieces();

      for(PieceVector::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
      {
        (*itr)->RotateCounterClockwise();
      }
    }
  }
  
  void RotateClockwise()
  {
    BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
    if(outcome == BlokishGame::BeginAIThread || outcome == BlokishGame::WaitForAI || outcome == BlokishGame::FinishAIThread)
    { // Don't interfere.
      return;
    }

    if(mpGame->GetSelectedPiece() != NULL)
    {
      mpGame->GetSelectedPiece()->RotateClockwise();
    }
    else
    {
      PieceVector &pieces = mpGame->GetUnplayedPlayerPieces();

      for(PieceVector::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
      {
        (*itr)->RotateClockwise();
      }
    }
  }

  void OnChangeAI()
  {
    std::vector<bool> aiStatus;
    aiStatus.push_back(mpGame->IsComputerControlled(0));
    aiStatus.push_back(mpGame->IsComputerControlled(1));
    aiStatus.push_back(mpGame->IsComputerControlled(2));
    aiStatus.push_back(mpGame->IsComputerControlled(3));
    
    AI::AIOptions *optionsVector = mpGame->GetOptions();
    
    OptionsDialog options(this, -1, wxString::FromAscii("Options"), aiStatus, optionsVector);

    for(int i = 0; i < 4; i++)
    {
      mpGame->SetComputerControlled(i, options.IsAI(i));
      optionsVector[i].weightSize = options.GetSizeWeight(i);
      optionsVector[i].weightFriendlySpaces = options.GetFriendlySpaceWeight(i);
      optionsVector[i].weightEnemySpaces = options.GetEnemySpaceWeight(i);
      optionsVector[i].numFriendlyPiecesToCheck = options.GetNumFriendlyPieces(i);
      optionsVector[i].numEnemyPiecesToCheck = options.GetNumEnemyPieces(i);
    }

    if(mpGame->GetOutcome() != BlokishGame::GameOver)
    {
      if(mpGame->GetOutcome() < BlokishGame::DOING_AI)
      {
        if(options.IsAI(int(mpGame->GetCurrentPlayer())))
        {
          mpGame->SetOutcome(BlokishGame::BeginAIThread);
        }
        else if(options.IsAI(int(mpGame->GetCurrentPlayer())))
        {
          mpGame->SetOutcome(BlokishGame::GetPlayerInput);
        }
      }
    }

    WriteSettings();
  }

protected:
  BlokishGame              *mpGame;
  int                     mMouseX;
  int                     mMouseY;
  int                     mnNumPiecesPerRow;
  std::vector<Color>      mvColor;
  
DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(BlokishCanvas, wxGLCanvas)
  EVT_KEY_DOWN(BlokishCanvas::OnKeyDown)
  EVT_MOTION(BlokishCanvas::OnMotion)
  EVT_PAINT(BlokishCanvas::OnDraw)
  EVT_ERASE_BACKGROUND(BlokishCanvas::OnErase)
  EVT_LEFT_DOWN(BlokishCanvas::OnLeftDown)
  EVT_RIGHT_DOWN(BlokishCanvas::OnRightDown)
  EVT_MOUSEWHEEL(BlokishCanvas::OnMousewheel)
END_EVENT_TABLE()

class BlokishFrame:public wxFrame
{
public:
  BlokishFrame(wxWindow *parent, wxWindowID id, wxString title):wxFrame(parent, id, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxCLOSE_BOX | wxSYSTEM_MENU), mGauge(NULL)
  {
    wxIcon BlokishIcon(blokish_xpm);
    SetIcon(BlokishIcon);
    if(BlokishIcon.Ok() == false)
    {
      std::cerr << "Nope.\n";
      exit(1);
    }

    CreateMenuBar();
    CreateStatusBar();

    int attribList[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER};
    mCanvas = new BlokishCanvas(this, -1, wxDefaultPosition, wxSize(800, 400), attribList, mGame);
    
    wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
    mainSizer->Add(mCanvas, 0, 0);
    mainSizer->SetSizeHints(this);
    SetSizer(mainSizer);
    CenterOnScreen();
  }
  
protected:
  void CreateMenuBar()
  {
    menubar = new wxMenuBar();

    wxMenu *file = new wxMenu(wxString::FromAscii(""));
    file->Append(ID_NEW_GAME, wxString::FromAscii("&New game"));
    file->Append(ID_SAVE_GAME, wxString::FromAscii("&Save game"));
    file->Append(ID_LOAD_GAME, wxString::FromAscii("&Load game"));
    file->AppendSeparator();
    file->Append(wxID_EXIT, wxString::FromAscii("E&xit"));

    wxMenu *options = new wxMenu(wxString::FromAscii(""));
    options->Append(ID_CHANGE_AI, wxString::FromAscii("Artificial intelligence"));
    options->AppendSeparator();
    options->Append(ID_CHANGE_FIRST,  wxString::FromAscii("Change player &1's color"));
    options->Append(ID_CHANGE_SECOND, wxString::FromAscii("Change player &2's color"));
    options->Append(ID_CHANGE_THIRD,  wxString::FromAscii("Change player &3's color"));
    options->Append(ID_CHANGE_FOURTH, wxString::FromAscii("Change player &4's color"));

    wxMenu *help = new wxMenu(wxString::FromAscii(""));
    help->Append(ID_SHOW_HELP, wxString::FromAscii("&Help pages"));
    help->AppendSeparator();
    help->Append(ID_SHOW_ABOUT, wxString::FromAscii("&About"));

    menubar->Append(file, wxString::FromAscii("&File"));
    menubar->Append(options, wxString::FromAscii("&Options"));
    menubar->Append(help, wxString::FromAscii("&Help"));
    
    SetMenuBar(menubar);
  }

  void OnHelp(wxCommandEvent &event)
  {
    HelpDialog dialog(help_str, this, -1, wxString::FromAscii("Blokish help"));
  }

  void OnAbout(wxCommandEvent &event)
  {
    HelpDialog dialog(about_str, this, -1, wxString::FromAscii("About"));
  }
  
  void CreateStatusBar()
  {
    statusBar = new wxStatusBar(this, -1);
    statusBar->SetFieldsCount(5);
    mGauge = new wxGauge(statusBar, -1, mGame.GetNumCols() * mGame.GetNumRows(), wxDefaultPosition);
    SetStatusBar(statusBar);
  }

	void OnClose(wxCloseEvent &event)
	{
		if(gAIThread != NULL)
		{
			std::cout << "Waiting for thread to complete... " << std::flush;
      gbAllowNewThreadToStart = false;
			gAIThread->Delete();
			delete gAIThread;
			gAIThread = NULL;
			std::cout << "done.\n" << std::flush;
		}
		Destroy();
	}
	
  void OnExit(wxCommandEvent &event)
  {
    Close();
  }

  void OnNewGame(wxCommandEvent &event)
  {
		WaitForAIToFinish();
    mGame.Reset();
		gbAllowNewThreadToStart = true;
  }

	void WaitForAIToFinish()
	{
		if(gAIThread != NULL)
		{
			gbAllowNewThreadToStart = false;

      if(gAIThread->IsRunning())
      {
			  gAIThread->Wait();
      }
		}
	}
	
  void OnSaveGame(wxCommandEvent &event)
  {
		WaitForAIToFinish();
    wxString filename = wxFileSelector(wxString::FromAscii("Select a filename to save as"), wxString::FromAscii(""), wxString::FromAscii("default.sav"), wxString::FromAscii(".sav"), wxString::FromAscii("*.sav"), wxSAVE);
    if(!filename.empty())
    {
      std::ofstream outFile(filename.fn_str(), std::ios::binary);
      mGame.Write(outFile);
    }
		gbAllowNewThreadToStart = true;
  }
  
  void OnLoadGame(wxCommandEvent &event)
  {
		WaitForAIToFinish();
    wxString filename = wxFileSelector(wxString::FromAscii("Select a game to load"), wxString::FromAscii(""), wxString::FromAscii(""), wxString::FromAscii(".sav"), wxString::FromAscii("*.sav"), wxOPEN | wxFILE_MUST_EXIST);
    if(!filename.empty())
    {
      std::ifstream inFile(filename.fn_str(), std::ios::binary);
      if(false == mGame.Read(inFile))
      {
        wxMessageBox(wxString::FromAscii("File given was not a blokish save game."));
      }
			inFile.close();
    }
		gbAllowNewThreadToStart = true;
  }

  void OnIdle(wxIdleEvent &event)
  {
    char buffer[256];

		if(manPrevScores[0] != mGame.GetScore(first))
		{
    	sprintf(buffer, "Player 1: %d", mGame.GetScore(first));
    	statusBar->SetStatusText(wxString::FromAscii(buffer), 0);
		}

		if(manPrevScores[1] != mGame.GetScore(second))
		{
    	sprintf(buffer, "Player 2: %d", mGame.GetScore(second));
    	statusBar->SetStatusText(wxString::FromAscii(buffer), 1);
		}
 
		if(manPrevScores[2] != mGame.GetScore(third))
		{
    	sprintf(buffer, "Player 3: %d", mGame.GetScore(third));
    	statusBar->SetStatusText(wxString::FromAscii(buffer), 3);   
		}

		if(manPrevScores[3] != mGame.GetScore(fourth))
		{
    	sprintf(buffer, "Player 4: %d", mGame.GetScore(fourth));
    	statusBar->SetStatusText(wxString::FromAscii(buffer), 4);
		}
    
    if(mGame.GetOutcome() == BlokishGame::WaitForAI)
    {
      statusBar->SetStatusText(wxString::FromAscii(""), 2);
      mGauge->Show();
      mGauge->SetValue(mGame.GetNumDone());
    }
    else if(mGame.GetOutcome() == BlokishGame::GameOver)
    {
      mGauge->Hide();
      statusBar->SetStatusText(wxString::FromAscii("Game is over"), 2);
    }
    else if(mGame.GetOutcome() == BlokishGame::BeginAIThread)
    {
      mGauge->Hide();
      statusBar->SetStatusText(wxString::FromAscii("Starting AI thread"), 2);
    }
    else if(mGame.GetOutcome() == BlokishGame::FinishAIThread)
    {
      mGauge->Hide();
      statusBar->SetStatusText(wxString::FromAscii("Finishing AI thread"), 2);
    }
    else
    {
      mGauge->Hide();
      statusBar->SetStatusText(wxString::FromAscii("Awaiting player input..."), 2);
    }

		manPrevScores[0] = mGame.GetScore(first);
		manPrevScores[1] = mGame.GetScore(second);
		manPrevScores[2] = mGame.GetScore(third);
		manPrevScores[3] = mGame.GetScore(fourth);
    
    mCanvas->OnIdle(event);
    event.Skip();
    event.RequestMore();
  }

  void OnChangeAI(wxCommandEvent &event)
  {
    mCanvas->OnChangeAI();
  }

  void OnChangeFirst(wxCommandEvent &event)   { mCanvas->ChangeColor(0); }
  void OnChangeSecond(wxCommandEvent &event)  { mCanvas->ChangeColor(1); }
  void OnChangeThird(wxCommandEvent &event)   { mCanvas->ChangeColor(2); }
  void OnChangeFourth(wxCommandEvent &event)  { mCanvas->ChangeColor(3); }

  void OnSize(wxSizeEvent &event)
  {
    if(mGauge != NULL)
    {
      wxRect rect;
      statusBar->GetFieldRect(2, rect);
      mGauge->SetSize(rect.x + 2, rect.y + 2, rect.width - 4, rect.height - 4);
    }
  }
  
	int												 manPrevScores[4]; 
  BlokishGame::TurnOutcome   mLastIdleOutcome;
  BlokishCanvas            	*mCanvas;
  BlokishGame                mGame;
  wxMenuBar               	*menubar;
  wxStatusBar             	*statusBar;
  wxGauge                 	*mGauge;

DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(BlokishFrame, wxFrame)
  EVT_SIZE(BlokishFrame::OnSize)
  EVT_MENU(ID_SHOW_HELP, BlokishFrame::OnHelp)
  EVT_MENU(ID_SHOW_ABOUT, BlokishFrame::OnAbout)
  EVT_MENU(ID_CHANGE_AI, BlokishFrame::OnChangeAI)
  EVT_MENU(ID_NEW_GAME, BlokishFrame::OnNewGame)
  EVT_MENU(ID_SAVE_GAME, BlokishFrame::OnSaveGame)
  EVT_MENU(ID_LOAD_GAME, BlokishFrame::OnLoadGame)
  EVT_MENU(wxID_EXIT, BlokishFrame::OnExit)
	EVT_CLOSE(BlokishFrame::OnClose)
  EVT_IDLE(BlokishFrame::OnIdle)
  EVT_MENU(ID_CHANGE_FIRST, BlokishFrame::OnChangeFirst)
  EVT_MENU(ID_CHANGE_SECOND, BlokishFrame::OnChangeSecond)
  EVT_MENU(ID_CHANGE_THIRD, BlokishFrame::OnChangeThird)
  EVT_MENU(ID_CHANGE_FOURTH, BlokishFrame::OnChangeFourth)
END_EVENT_TABLE()

class BlokishApp:public wxApp
{
public:
  bool OnInit()
  {
		CreateDocStrs();
    BlokishFrame *frame = new BlokishFrame(NULL, -1, wxString::FromAscii("Blokish"));
    frame->Show();
    return true;
  }
};

IMPLEMENT_APP(BlokishApp)
