// Copyright 1995, 1996, 1997 by Jon Dart. All Rights Reserved.
// arasavw.cpp : implementation of the CArasanView class
//

#include "stdafx.h"
#include "arasan.h"

#include "advanced.h"
#include "arasadoc.h"
#include "arasavw.h"
#include "book.h"
#include "bookread.h"
#include "display.h"
#include "epdstat.h"
#include "bearing.h"
#include "log.h"
#include "ecoinfo.h"
#include "movegen.h"
#include "movearr.h"
#include "notation.h"
#include "openings.h"
#include "clock.h"
#include "rmove.h"
#include "globals.h"
#include "srcoptsh.h"
#include "secondar.h"
#include "srclimit.h"
#include "chessio.h"
#include "showmove.h"
#include "hint.h"
#include "promotio.h"
#include "preferen.h"
#include "selectga.h"
#include "eco.h"
#include "util.h"
#include "genlpref.h"
#include "colorpag.h"
#include "calctime.h"
#include <stdio.h>
#include <strstrea.h>

Clock *the_clock = NULL;
DWORD pgnSelection = 0L;
static ifstream *pgn_file;
static char last_move_image[80];
static CStringList *pgnContents;

static const UINT MAIN_TIMER = 1;

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CArasanView

IMPLEMENT_DYNCREATE(CArasanView, CView)

BEGIN_MESSAGE_MAP(CArasanView, CView)
    //{{AFX_MSG_MAP(CArasanView)
    ON_WM_SIZE()
    ON_WM_MOVE()
    ON_WM_PAINT()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_COMMAND(ID_MAKEUSERMOVE, OnMakeUserMove)
    ON_COMMAND(ID_COMPUTE, OnComputeMove)
    ON_WM_TIMER()
        ON_COMMAND(ID_SRCLIMITS, OnSrclimits)
        ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
        ON_COMMAND(ID_FILE_SAVE, OnFileSave)
        ON_COMMAND(ID_EDIT_COPYPOSITION, OnEditCopyPosition)
        ON_COMMAND(ID_EDIT_COPYGAME, OnEditCopyGame)
        ON_COMMAND(ID_EDIT_COPYBITMAP, OnEditCopyBitmap)
        ON_COMMAND(ID_FORWARD, OnForward)
        ON_COMMAND(ID_HINT, OnHint)
        ON_COMMAND(ID_PAUSE, OnPause)
        ON_COMMAND(ID_PLAYWHITE, OnPlaywhite)
        ON_COMMAND(ID_PREFERENCES, OnPreferences)
        ON_COMMAND(ID_RESET, OnReset)
        ON_COMMAND(ID_ROTATEBOARD, OnRotateboard)
        ON_COMMAND(ID_SHOWGAME, OnShowgame)
        ON_COMMAND(ID_TAKEBACK, OnTakeback)
        ON_COMMAND(ID_FILE_EXIT, OnFileExit)
        ON_COMMAND(ID_BACKGROUND_COMPUTE, OnBackgroundCompute)
        ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
        ON_COMMAND(ID_BROWSE_NEXTGAME, OnBrowseNextgame)
        ON_COMMAND(ID_BROWSE_PREVIOUSGAME, OnBrowsePreviousgame)
        ON_COMMAND(ID_BROWSE_SELECTGAME, OnBrowseSelectgame)
        ON_COMMAND(ID_BEGIN_GAME, OnBeginGame)
        ON_COMMAND(ID_GAME_ENDOFGAME, OnEndGame)
        ON_COMMAND(ID_LOSSONTIME, OnLossOnTime)
        ON_UPDATE_COMMAND_UI(ID_BROWSE_NEXTGAME, OnUpdateBrowseNextgame)
        ON_UPDATE_COMMAND_UI(ID_BROWSE_PREVIOUSGAME, OnUpdateBrowsePreviousgame)
        ON_UPDATE_COMMAND_UI(ID_BROWSE_SELECTGAME, OnUpdateBrowseSelectgame)
        ON_UPDATE_COMMAND_UI(ID_PLAYWHITE,OnUpdatePlayWhite)
        ON_UPDATE_COMMAND_UI(ID_PAUSE, OnUpdatePause)
        ON_UPDATE_COMMAND_UI(ID_BEGIN_GAME, OnUpdateBeginGame)
        ON_UPDATE_COMMAND_UI(ID_GAME_ENDOFGAME, OnUpdateEndGame)
        ON_UPDATE_COMMAND_UI(ID_FORWARD, OnUpdateForward)
        ON_UPDATE_COMMAND_UI(ID_TAKEBACK, OnUpdateBack)
        ON_COMMAND(ID_FILE_ANNOTATE, OnFileAnnotate)
        ON_WM_CLOSE()
        ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, OnRecentFile)
        ON_WM_DROPFILES()
        ON_COMMAND(ID_COMPUTE_ONLY, OnComputeMove)
	ON_UPDATE_COMMAND_UI(ID_HINT, OnUpdateHint)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

static int legal_move(const Board &board, const Square &start,
  const Square &dest)
{
    // do a little basic sanity checking:
      if (!start.OnBoard() || !dest.OnBoard() || board[start].IsEmpty())
          return FALSE;

      ExtendedMove emove(board,start,dest);
      Move moves[Move_Generator::MaxMoves];
      Move_Generator mg( board, 0, Move::NullMove() );
      int found = 0;
          int n = mg.Generate_Moves(moves, FALSE);

      for (int i = 0; i < n; i++)
          {
          if (moves[i].StartSquare() == emove.StartSquare() &&
              moves[i].DestSquare() == emove.DestSquare())
          {
             found++;
             break;
          }
      }
      if (!found)
          return FALSE;
      else        
      {
          // check for king en prise
          Board board_copy(board);
          const ColorType side = board.Side();
          board_copy.MakeMove(emove);
          return !board_copy.any_attacks(
            board_copy.KingPos(side),OppositeColor(side));
          
      }
}

static void free_pgn()
{
    if (pgnContents)
    {
       pgnContents->RemoveAll();
    }
    if (pgn_file)
    {
       pgn_file->close();
       delete pgn_file;
       pgn_file = NULL;
    }
    pgnSelection = 0L;
}

/////////////////////////////////////////////////////////////////////////////
// CArasanView construction/destruction

void CArasanView::reset(enum ResetType type)
{
    if (type != NewPosition)
        current_board.Reset();
    users_move = TRUE;
    searching = FALSE;
    delete game_moves;
    Move_Record::freeAll(TRUE);
    game_moves = new Move_Array();
    
    if (type == NewGame)
       theLog->reset();
    else
       theLog->clear();
    theLog->write_header();
    last_move.MakeNull();
    predicted_move.MakeNull();
    *last_move_image = '\0';
    use_book = TRUE;
    quitting = FALSE;
    users_move = TRUE;
    last_cmd = ID_DUMMY;
    if (the_clock)
    {
       setup_clock_initially();
       the_clock->reset();
    }
    no_prev_search = TRUE;
    users_move = TRUE;
    computer_side = Black;
    stats.clear();
    eco = opening_name = "";
    if (type != NewGame)
    {
       players = "";
       free_pgn();
    }
    clear_status();
    draw_board();
    show_side();
    if (the_clock)
        the_clock->start(current_board.Side());
}

CArasanView::CArasanView()
: disp(NULL),searcher(NULL),did_startup(FALSE),file_opened(FALSE)
{
    ecoCoder = new ECO();
    InitializeCriticalSection(&searchCritSection);
}

void CArasanView::OnDraw(CDC* pDC)
{
    // This is called during printing and for clipboard
    // operations.  Currently we just draw the board.
    CSize ext = pDC->GetWindowExt();
    // window size in logical units:
    CRect r(0,0,Util::Abs(ext.cx),Util::Abs(ext.cy)); 
    disp->draw_board(pDC,current_board,&r,FALSE);
}                   

void CArasanView::OnInitialUpdate()
{
    if (!the_clock)
    {
       searcher = new Search();
       the_clock = new Clock(m_hWnd);
    }
    // We may get here after opening a file
    if (!file_opened)
       reset(Initial);
    DragAcceptFiles(TRUE);
}

CArasanView::~CArasanView()
{
    quitting = TRUE;

    delete disp; 
    stop_search(FALSE);
    delete searcher;
    delete the_clock;
    if (pgnContents)
    {
       free_pgn();
       delete pgnContents;
       pgnContents = NULL;
    }
    delete ecoCoder;
    DeleteCriticalSection(&searchCritSection);
}

// handle updating to the next time control, if necessary
void CArasanView::update_time()
{
    if (the_clock->time_is_up())
       return;
    // We already made the move and changed the side to move,
    // so use "oppside" for the data we need:
    const ColorType oppside = current_board.OppositeSide();
    Time_Info &my_ti = ti[oppside];
    if (my_ti.get_search_type() == Tournament)
    {
        // see if we made time control
        Search_Limit limit = my_ti.get_search_limit();
        if (((game_moves->num_moves()-1)/2) - my_ti.get_last_time_control()
              + 1 == limit.limit.moves)
        {
            // we made it, update to next time control
            int period = my_ti.get_period();
            time_t bonus = the_clock->get_limit(oppside) 
                 - the_clock->elapsed_time(oppside);
            my_ti.set_bonus(bonus);
            if (++period < Max_Time_Controls)
            {
                Time_Control next_tc = 
                    global_options->get_time_control((Control)period);
                if (next_tc.get_search_type() != None)
                {
                   my_ti.set_time_control(next_tc);
                }
            }
            my_ti.set_last_time_control(1+((game_moves->num_moves()-1)/2));
            my_ti.set_period(period);
            setup_clock(oppside);
        }
    }
    else if (my_ti.get_search_type() == Incremental)
    {
       Search_Limit lim = my_ti.get_search_limit();
       if (lim.limit.increment > 0)
       {
           the_clock->set_limit(
             the_clock->get_limit(current_board.OppositeSide())
             + 100*lim.limit.increment,
             current_board.OppositeSide());
       }
    }
    the_clock->start(current_board.Side());
}

void CArasanView::update_board( const ExtendedMove &emove,
              Search::Statistics *stats,
              const char *result,
              const BOOL update_log)
{
     CDC *dc = GetDC();
     if (!emove.IsNull())
     {
        last_move = ReversibleMove(current_board,emove);
        Notation::Image(current_board,emove,last_move_image);
        if (update_log)
           theLog->add_move(current_board,last_move,last_move_image,stats,TRUE);
        current_board.MakeMove(emove);
        show_last_move( last_move_image ); // must call after add_move
        game_moves->add_move(current_board,emove);
        ecoCoder->classify(*game_moves,eco,opening_name);
        disp->draw_square(dc,emove.StartSquare());
        disp->draw_square(dc,emove.DestSquare());
        disp->draw_piece(dc,emove.DestSquare(),
                 current_board[emove.DestSquare()]);
        disp->show_eco(dc,eco,opening_name);
        Square target, oldrooksq, newrooksq;
        switch( emove.Special())
        {
        case ExtendedMove::Normal:
          break;
        case ExtendedMove::EnPassant:
              target = emove.DestSquare();
          if (emove.PieceMoved().Color() == White) 
             target += RankIncr;
          else
             target -= RankIncr;
          disp->draw_square(dc,target);
          break;
       case ExtendedMove::KCastle:
          oldrooksq = emove.StartSquare() + 3;
          newrooksq = emove.StartSquare() + 1;
          disp->draw_square(dc,oldrooksq);
          disp->draw_piece(dc,newrooksq,current_board[newrooksq]);
          break;
       case ExtendedMove::QCastle:
          oldrooksq = emove.StartSquare() - 4;
          newrooksq = emove.StartSquare() - 1;
          disp->draw_square(dc,oldrooksq);
          // must re-draw new rook square, so we get the right brush:
          disp->draw_square(dc,newrooksq);
          disp->draw_piece(dc,newrooksq,current_board[newrooksq]);
          break;
       case ExtendedMove::Promotion:
          disp->draw_piece(dc,emove.DestSquare(),
          Piece(emove.PromoteTo(),emove.PieceMoved().Color()));
          break;
        }
        disp->show_side(dc,current_board.Side());
        users_move = !users_move;
     }
     if (stats)
     {
         // TBD: need check for insufficient material, too
         if (game_moves->rep_count(current_board) == 3)
         {
             // draw by repetition
             stats->state = Search::Draw;
         }
         disp->show_status(dc,*stats);
         if (stats->num_nodes)
         {
           disp->show_search_counts(m_hWnd,
             stats->depth,
             stats->num_nodes);
         }
     }
     else
     {
         disp->clear_status_line(dc);
         disp->clear_search_counts(dc);
     }
     ReleaseDC(dc); 

     if (stats)
     {
         switch (stats->state)
         {
             case Search::Checkmate:
               MessageBox("Checkmate!");
               the_clock->stop();
               break;
             case Search::Stalemate:
               MessageBox("Stalemate!");
               the_clock->stop();
               break;
             case Search::Draw:
               MessageBox("Draw!");
               the_clock->stop();
               break;
             case Search::Resigns:
             {
               char msg[40];
               wsprintf(msg,"%s resigns!",
               Image(current_board.OppositeSide()));
               MessageBox(msg);
             }
             the_clock->stop();
             break;
           default:
             update_time();
             if (stats->value == 9999) the_clock->stop();
         }
     }
     else
     {
         CDC *dc = GetDC();
         if (result)
            disp->show_status(dc,result);
         else
            disp->clear_status_line(dc);
         ReleaseDC(dc);
         update_time();
     }
}

void CArasanView::show_last_move( char *move_image )
{
    CDC *pDC = GetDC();
    disp->show_move(pDC,move_image,theLog->current());
    ReleaseDC(pDC);
}

void CArasanView::show_last_move()
{
     CDC *pDC = GetDC();
     if (last_move.IsNull())
        disp->clear_move_area(pDC);
     else
     {
        disp->show_move(pDC,last_move_image,theLog->current());
     }
     ReleaseDC(pDC);
}

// Call this when a time control is reached to reset clock options for one
// side
void CArasanView::setup_clock(const ColorType side)
{
     const Search_Limit limits = ti[side].get_search_limit();
     switch (ti[side].get_search_type())
     {
     case Fixed_Ply:
     case Time_Limit:
         the_clock->count_up(); break;
     case Game:
     case Tournament:
     case Incremental:
         the_clock->count_down(
            100*(limits.limit.minutes*60L)+ti[side].get_bonus(),side);
         break;
     }
}

// Call this on startup and after clock options change
void CArasanView::setup_clock_initially()
{
     ti[White].set_time_control(global_options->get_time_control(First));
     ti[Black].set_time_control(global_options->get_time_control(First));
     ti[White].set_period(0);
     ti[Black].set_period(0);
     ti[White].set_bonus(0);
     ti[Black].set_bonus(0);
     setup_clock(White);
     setup_clock(Black);
}

void CArasanView::compute_move(Board &board, const Time_Info &ti,
        const BOOL background,
        Search::Statistics &stats,
        ExtendedMove &emove)
{
      if (global_options->use_book() && use_book)
      {                 
          Move move = opening_book->book_move(current_board,
              background ? Balanced : global_options->book_style());
          if (!move.IsNull())
          {
              emove = ExtendedMove(current_board,move);
              Board board_copy(current_board);
              board_copy.MakeMove(emove);
              if (board_copy.CheckStatus() == Board::InCheck)
                  stats.state = Search::Check;
              else
                  stats.state = Search::Normal;
              stats.value = 0;
              stats.elapsed_time = 0;
              stats.num_moves = stats.num_nodes = 0L;
              stats.best_line[0] = move;
              stats.best_line[1].MakeNull();
              if (!background)
              {
                 last_move = ReversibleMove(current_board,emove);
              }
              // suppress display of search statistics:
              no_prev_search = TRUE;
              // tell UI we're done
              OnSearchComplete(TRUE);
              return;
          }
      }
      // no book move

      // The search routine has its own timer, so we disable ours.
      KillTimer(MAIN_TIMER);

      // ensure that we don't start one search while in the middle of 
      // stopping another.
      EnterCriticalSection(&searchCritSection);
      
      // Determine how much time to use .. depends on the type
      // of time control selected.
      int ply_limit = 99;
      int time_target = 99999;
      const Search_Limit limits = ti.get_search_limit();
      time_t time_left = the_clock->time_left(current_board.Side());
      time_t opp_time = the_clock->time_left(current_board.OppositeSide());
      switch (ti.get_search_type())
      {
         case Fixed_Ply:
           ply_limit = limits.max_ply;
           break;
         case Time_Limit:
           time_target = limits.centiseconds; 
           break;
         case Time_Target:
           time_target = limits.centiseconds; 
           break;
         case Tournament:
           time_target = calc_time_limit(limits.limit.moves, 
                           limits.limit.minutes, 0,
                           time_left, opp_time);
           break;
         case Game:
           time_target = calc_time_limit(0, limits.limit.minutes, 0,
                           time_left, opp_time);            
           break;
         case Incremental:
           time_target = calc_time_limit(0, limits.limit.minutes, 
                           limits.limit.increment,
                           time_left, opp_time);            
           break;
         default:
           break;
    }

    // If we have done a background search while waiting for the
    // user to move, and if we predicted the opponent's last move,
    // then we may either have already found a move to make or
    // at least we are partway through the search and need only
    // extend it.
    if (!background && !predicted_move.IsNull() &&
        predicted_move == last_move && global_options->think_when_idle())
    {
       DWORD result = WaitForSingleObject(searchThread,0);
       if (result == WAIT_OBJECT_0)
       {
          // The background thread has already completed, probably
          // because it found a mate or forced move.
          if (!stats.best_line[0].IsNull())
          {
             // We got a move.  OnSearchComplete was already
             // called when the background search finished,
             // but call it again now with background = FALSE,
             // so that we process it as we would a normal move.
             searcher->set_background(FALSE);
             background_search = FALSE;
             OnSearchComplete(TRUE);
          }
       }
       else
       {
          SuspendThread(searchThread);
          time_t now = GetTickCount()/10;
          // We predicted the last move so all we need to do is
          // to allow the background search to continue.  We 
          // give it a little less than the usual amount of time
          // because we have already done some searching.
          int elapsed_time = now - searcher->get_start_time();
          if (elapsed_time > time_target/5 &&
              ti.get_search_type() != Time_Limit) 
             time_target = time_target + elapsed_time - time_target/5;
          else
             time_target += elapsed_time;
          TRACE("elapsed time = %d sec.\n",(now - searcher->get_start_time())/100);            
          TRACE("time target = %d sec.\n",time_target/100);            
          if (time_target <= 0) time_target = 30;

          // Extend the time for the search
          if (ti.get_search_type() == Fixed_Ply)
             searcher->set_ply_limit(ply_limit);
          else
             searcher->set_time_limit(time_target);
          searcher->set_background(FALSE);
          background_search = FALSE;
          ResumeThread(searchThread);
       }
       LeaveCriticalSection(&searchCritSection);
       return; // no more to do, until the search thread ends.
    }
    // kill any existing search thread
    stop_search(FALSE);
    extern int num_searches;
#ifdef _DEBUG
    if (num_searches > 0)
	MessageBox("Bug");
#endif
    // This call initiates a new thread to do the search.  When
    // it has completed (or been terminated), we will get a
    // call to OnSearchComplete.
    searching = TRUE;
    searchThread =
         searcher->start_search(this, board,
         ti.get_search_type(),
         time_target,
         ply_limit,
         background,
         stats,
         Search::Silent);
    LeaveCriticalSection(&searchCritSection);
}

void CArasanView::clear_status()
{
      CDC *pDC = GetDC();
      disp->clear_status_line(pDC);
      disp->clear_move_area(pDC);
      disp->clear_search_counts(pDC);
      disp->show_eco(pDC, eco,opening_name);
      disp->show_players(pDC, players);
      ReleaseDC(pDC);
}


BOOL CArasanView::open_file(LPCSTR fileName)
{
         // framework may call this with fileName == NULL, in
         // response to OnFileNew
         if (!fileName)
         {
             OnNewGame();
             return TRUE;
         }
         BOOL status = FALSE;
         // get the extension:
         CString ext;
         CString name(fileName);
         int i = name.GetLength()-1;
         while (i>=0 && name[i] != '.') --i;
         if (name[i] == '.')
            ext = name.Right(name.GetLength()-i-1);
         free_pgn();
         pgn_file = new ifstream();

         pgn_file->open(fileName,ios::in | ios::binary | ios::nocreate);
         if (!pgn_file->is_open() || !pgn_file->good())
         {
              CString msg;
              AfxFormatString1(msg,IDS_CANT_OPEN,fileName);
              MessageBox(msg);
              return status;
         }

         ext.MakeLower();
         if (ext == "pgn")     
         {
             GetDocument()->SetPathName(fileName);

             current_board.Reset();
             theLog->clear();        
             pgnContents = new CStringList;
             
             // might take awhile, put up a wait cursor. A
             // progress bar would be nicer ...
             BeginWaitCursor();
             // scan just the PGN headers, so we can tell the
             // user what's in the file:
             ChessIO::scan_pgn(*pgn_file,*pgnContents);
             EndWaitCursor();

             DWORD pgnOffset = 0L;
             if (pgnContents->GetCount() > 1)
             {   
                 // let the user choose a game to view
                 SelectGame dlg(*pgnContents,this);
                 if (dlg.DoModal() == IDCANCEL)
                 {
                     return FALSE;  
                 }
                 pgnOffset = dlg.get_offset();
                 pgnSelection = dlg.get_selection();
             }
             status = ChessIO::load_pgn(*pgn_file,*theLog,pgnOffset,players);
             reset(NewGame);
        }
        else if (ext == "fen")
        {       
             status = ChessIO::load_fen(*pgn_file,current_board);
             if (!status)
             {
                 CString msg;
                 AfxFormatString2(msg,IDS_INVALID_BOARD,fileName,ext);
                 MessageBox(msg);   
                 current_board.Reset();
                
             }
             else
             {
                 GetDocument()->SetPathName(fileName);
                 use_book = FALSE;
                 CString msg;
                 AfxFormatString1(msg,IDS_BOARD_LOADED,fileName);
                 theLog->write((LPCSTR)msg);
                 theLog->write_eol();
                 reset(NewPosition);
                 //draw_board();
                 //show_side();
             }
         }
/** LATER            
         else if (str == "epd")
         {
             EpdStatusDialog dlg(this,fileName,pgn_file,current_board);
             dlg.DoModal();
         }
**/
         else
         {          
             CString msg;
             msg.LoadString(IDS_UNKNOWN_FILE);
             MessageBox((LPCSTR)msg);
             return FALSE;
         }
         // If we read a pgn file consisting of >1 game, keep the file
         // open:
         if (!pgnContents || pgnContents->GetCount() <= 1)
         {
            if (pgn_file) pgn_file->close();
            delete pgn_file;
            pgn_file = NULL;
         }
         if (status)
             file_opened = TRUE;
         return status;                       
}

/////////////////////////////////////////////////////////////////////////////
// CArasanView printing
// Note: this code is not accessible in Arasan 2.0.  There are various
// problems with the printing implementation, esp. on color printers.

void CArasanView::OnPrepareDC( CDC *pDC, CPrintInfo *pInfo)
{
    CSize size;
    if (!pInfo) //screen display
    {
        pDC->SetMapMode(MM_ANISOTROPIC);
        CRect rect;
        GetClientRect(&rect);
        size.cx = rect.Width(); size.cy = rect.Height();
        pDC->SetViewportExt(size);
    }
    else // printing
    {
 
        // "pages" have no meaning for Arasan.  So terminate
        // printing if the page count > 1.
        if (pInfo->m_nCurPage > 1)
           pInfo->m_bContinuePrinting = FALSE;
    }
}

BOOL CArasanView::OnPreparePrinting(CPrintInfo* pInfo)
{
    // default preparation
    return DoPreparePrinting(pInfo);
}

void CArasanView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: add extra initialization before printing
}

void CArasanView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: add cleanup after printing
}

void CArasanView::OnPrint(CDC *pDC, CPrintInfo *pInfo)
{
    // physical size in MM:
    int horzSize = pDC->GetDeviceCaps(HORZSIZE);
    int vertSize = pDC->GetDeviceCaps(VERTSIZE);

    pDC->SetMapMode(MM_LOMETRIC);

    horzSize -= 25;
    vertSize -= 25;
    int minSize = (horzSize > vertSize) ? vertSize : horzSize;
    // make drawing region square and allow for a 25mm (about 1 inch) margin
    pInfo->m_rectDraw = CRect(250,-250,10*minSize,-10*minSize);
    pDC->Rectangle(pInfo->m_rectDraw);

    CRect old_size = disp->set_size(pInfo->m_rectDraw);
    disp->draw_board(pDC,current_board,&(pInfo->m_rectDraw),FALSE);  
    disp->set_size(old_size);
}

/////////////////////////////////////////////////////////////////////////////
// CArasanView diagnostics

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

void CArasanView::Dump(CDumpContext& dc) const
{
    CView::Dump(dc);
}

CArasanDoc* CArasanView::GetDocument() // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CArasanDoc)));
    return (CArasanDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CArasanView message handlers

BOOL CArasanView::OnCommand(WPARAM id, LPARAM lParam)
{
#ifdef _WIN32
     id &= 0xffff;
#endif
     last_cmd = id;
     if (id >= WM_USER)
     {
         if (quitting) // shutting down, ignore user commands
                return TRUE; 
         if (searching)
         {
             switch (id)
             {
                // These commands don't modify the board, the side to move,
                // or the search parameters.  So there is no need to stop
                // any search in progress.
                case ID_SHOWGAME:
                case ID_ROTATEBOARD:
                case ID_PAUSE:
                case ID_RESUME:
                case ID_SAVEBOARD:
                case ID_PREFERENCES:
                case ID_HINT:
                case ID_DUMMY:
                case ID_HELP:
                case ID_HELP_INDEX:
                case ID_HELP_FINDER:
                case ID_HELP_USING:
                case ID_SRCLIMITS: // we stop the search if necessary in OnSearchlimits
                   break;
                case ID_COMPUTE:
                   // A compute command during a search forces the computer
                   // to move now.
                   if (!background_search)
                   {
                      stop_search(TRUE);
                      return TRUE;
                   }
                   break;
                case ID_MAKEUSERMOVE:
                   // Don't stop any background search in progress.
                   // We test later on to see if this is needed.
                   break;
                default:
                // For all other commands, we first terminate the search.  All
                // commands will be ignored until the search terminates.
                stop_search(FALSE);
                // Now we can execute the user's command
             }
         }
     }
     if (id == ID_FILE_OPEN)
     {
        if (*initialFilename)
        {
           // Special case, user has started Arasan
           // with a filename argument.  This code is also
           // called when a file is dragged into the Arasan
           // window.
           open_file(initialFilename);
           *initialFilename = '\0';
           return TRUE;
        }
     }

     return CView::OnCommand(id, lParam);
}

void CArasanView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    
    CRect r(0,0,cx,cy);            
    if (!disp)
    {
        disp = new Display(this,r);
        SetTimer(MAIN_TIMER,1000, NULL);
    }
    else
       disp->set_size(r);

    CWnd *mainWnd = AfxGetMainWnd();
    if (mainWnd)
    {
       CRect rect;
       mainWnd->GetWindowRect(&rect);
       global_options->set_app_geometry(rect.left,rect.right,rect.Width(),rect.Height());
    }
}

void CArasanView::OnMove(int, int)
{
    CWnd *mainWnd = AfxGetMainWnd();
    if (mainWnd)
    {
       CRect rect;
       mainWnd->GetWindowRect(&rect);
       global_options->set_app_geometry(rect.left,rect.right,rect.Width(),rect.Height());
    }
}


void CArasanView::OnPaint()
{
    if (!disp || quitting) return;
    CPaintDC dc(this); // device context for painting
    
    // Do not call CView::OnPaint() for painting messages
    CRect clientArea;
    GetClientRect(&clientArea);
    disp->set_size(clientArea);
    CArasanDoc *pDoc = (CArasanDoc*)GetDocument();
    disp->draw_board(&dc,current_board,&clientArea);
    show_side();
    show_last_move();
    the_clock->show_time(White);
    the_clock->show_time(Black);
    if (searching && !background_search)
    {
       // search in progress, show current stats  
       Display::show_search_counts(GetSafeHwnd(),
          searcher->get_ply(),searcher->get_numnodes());
    }
    else if (!no_prev_search)
    {
       // show prevous search results
       Display::show_search_counts(GetSafeHwnd(),stats.depth,
                                   stats.num_nodes);
    }
    disp->show_status(&dc,stats);
    disp->show_eco(&dc,eco,opening_name);
    if (players.GetLength() > 0) 
        disp->show_players(&dc,players);
    if (*initialFilename && !did_startup)
    {
       PostMessage(WM_COMMAND,ID_FILE_OPEN,0);
 
       did_startup = TRUE;
    }

}

void CArasanView::OnLButtonDown(UINT nFlags, CPoint point)
{
       if (!disp || quitting) 
           return;
       if (!users_move)
       {
           // not the user's move
           if (global_options->beep_on_error())
               ::MessageBeep(MB_ICONEXCLAMATION);
           start_square = Square::Invalid();
           return;
       }
       // find out where we are on the board:
       start_square = disp->mouse_loc(point);
       if (current_board[start_square].IsEmpty())
       {
           start_square = Square::Invalid();
           return; // no piece on this square
       }
       else
       {
           CDC *pDC = GetDC();
           disp->highlight_square(pDC,start_square);
           ReleaseDC(pDC);
       }
       CView::OnLButtonDown(nFlags, point);
}

void CArasanView::OnLButtonUp(UINT nFlags, CPoint point)
{
       if (!disp || quitting) 
           return;
       Square dest = disp->mouse_loc(point);
       if (!dest.OnBoard() || !users_move || start_square.IsInvalid() ||
           start_square == dest)
       {
           // ignore event
       }
       else if (!legal_move(current_board,start_square,dest))
       {
          if (global_options->beep_on_error())
             ::MessageBeep(MB_ICONEXCLAMATION);
       }
       else
       {
          // move is legal
          CDC *pDC = GetDC();
          disp->unhighlight_square(pDC,start_square);
          ReleaseDC(pDC);
          last_move = ReversibleMove(current_board,start_square,dest);
          if (searching)
          {
              last_cmd = ID_MAKEUSERMOVE;
           }
          PostMessage(WM_COMMAND,ID_MAKEUSERMOVE,0L);
       }
       CView::OnLButtonUp(nFlags, point);
}

void CArasanView::OnMakeUserMove()
{
      start_square = last_move.StartSquare();
      Square dest = last_move.DestSquare();
      ExtendedMove emove;
      if (current_board[start_square].Type() == Piece::Pawn &&
          dest.Rank(current_board.Side()) == 8)
      {
         // promotion, prompt for piece to promote to
         PromotionDialog pdlg(this);
         if (pdlg.DoModal() == IDCANCEL)
         {
             return;
         }
         Move m;
         switch (pdlg.m_PieceType)
         {
            case 0: m = Move(start_square,dest,Piece::Queen); break;
            case 1: m = Move(start_square,dest,Piece::Rook); break;
            case 2: m = Move(start_square,dest,Piece::Bishop); break;
            case 3: m = Move(start_square,dest,Piece::Knight); break;
         }        
         emove = ExtendedMove(current_board,m);
      }
      else
          emove = ExtendedMove(current_board,start_square,dest);
      update_board(emove,NULL,NULL,TRUE);

      // calculate a reply move:
      PostMessage(WM_COMMAND,ID_COMPUTE,0L);
}

void CArasanView::OnComputeMove()
{
     ExtendedMove emove;
     CDC *pDC = GetDC();
     disp->clear_search_counts(pDC);
     disp->clear_status_line(pDC);
     ReleaseDC(pDC);
     no_prev_search = FALSE;
     background_search = FALSE;
     compute_move(current_board,ti[current_board.Side()],
        FALSE, stats, emove );
}

// This is called after a search in a different thread terminates.
void CArasanView::OnSearchComplete(BOOL makeMove)
{
     if (quitting)
     {
         return; // nothing to do
     }
     ExtendedMove emove = ExtendedMove(current_board,stats.best_line[0]);
     // restore the main timer
     SetTimer(MAIN_TIMER,1000, NULL);
     searching = FALSE;
     if (!background_search)
     {
         last_move = ReversibleMove(current_board,emove);
         predicted_move.MakeNull();
         if (!stats.best_line[0].IsNull() && !stats.best_line[1].IsNull())
            predicted_move = stats.best_line[1];
         TRACE("search done: elapsed time = %d secs.\n",stats.elapsed_time/100);
     }
     if (makeMove && !background_search && (stats.state != Search::Terminated ||
         last_cmd == ID_COMPUTE))
     {
          if (last_cmd != ID_COMPUTE_ONLY)
          {
               update_board(emove,&stats,NULL,TRUE);
               if (users_move && global_options->beep_after_move())
                  ::MessageBeep(MB_OK);
               if (users_move && global_options->think_when_idle() &&
                   !predicted_move.IsNull() &&
                   stats.state != Search::Resigns &&
                   stats.state != Search::Draw &&
                   stats.state != Search::Stalemate &&
                   stats.state != Search::Checkmate)
               {
                   PostMessage(WM_COMMAND,ID_BACKGROUND_COMPUTE,0L);
               }
          }
     }
}

void CArasanView::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == MAIN_TIMER && the_clock != NULL)
    {
       the_clock->update();
    }
    CView::OnTimer(nIDEvent);
}

void CArasanView::OnSrclimits()
{
     Search_Limit_Options primary_options(global_options->get_time_control(First),First);
     Search_Limit_Options secondary_options(global_options->get_time_control(Second),Second);
     Search_Limit_Options old_primary(primary_options);
     Search_Limit_Options old_secondary(secondary_options);
      // Put the dialog box up.
     CSearchOptionsSheet srcSheet(IDS_SRCOPT_TITLE);
     SearchLimitPage srcPage1(&srcSheet);
     SecondarySearchOptions srcPage2;

     srcPage1.m_PlyOrMoves = primary_options.move_or_ply;
     if (primary_options.search_type >= 2) srcPage1.m_Time 
        = primary_options.time_limit;
     srcPage1.m_SearchType = primary_options.search_type;

     srcPage2.m_2ndaryType = secondary_options.search_type;
     srcPage2.m_PlyData = secondary_options.move_or_ply;
     srcPage2.m_TimeData = secondary_options.time_limit;

     srcSheet.AddPage(&srcPage1);
     srcSheet.AddPage(&srcPage2);
     if (srcSheet.DoModal() == IDOK)
     {
        primary_options.search_type = srcPage1.m_SearchType;
        primary_options.move_or_ply = srcPage1.m_PlyOrMoves;
        primary_options.time_limit = srcPage1.m_Time;
        secondary_options.search_type = srcPage2.m_2ndaryType;
        secondary_options.move_or_ply = srcPage2.m_PlyData;
        secondary_options.time_limit = srcPage2.m_TimeData;
        if (primary_options != old_primary ||
            secondary_options != old_secondary)
        {
            Time_Control new_tc;

            primary_options.parse(new_tc,First);
            global_options->set_time_control(new_tc,First);
            char msg[100];
            wsprintf(msg,"=== Primary time control: %s",new_tc.Image());
            theLog->write(msg); theLog->write_eol();
            if (new_tc.get_search_type() == Tournament)
            {  
              secondary_options.parse(new_tc,Second);
              wsprintf(msg,"=== Secondary time control: %s",new_tc.Image());
              theLog->write(msg); theLog->write_eol();
            }
            else
            {
              new_tc.set_search_type(None);
            }
            global_options->set_time_control(new_tc,Second);
            setup_clock_initially();
            if (!users_move)
            {
               stop_search(FALSE /* do not make move*/);
           }
        }
     }
}

void CArasanView::OnFileOpen()
{
     CString filter;
     filter.LoadString(IDS_FILE_OPEN_FILTER);
     CFileDialog dlg(TRUE,
                     ".pgn",  // default file extension
                     NULL,    // no default filename
                     OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                     (LPCSTR)filter,
                     this);
     if (dlg.DoModal() == IDOK)
     {
         free_pgn();
         open_file(dlg.GetPathName());
     }                  
}

void CArasanView::OnFileSave()
{
    CString filter;
    filter.LoadString(IDS_FILE_OPEN_FILTER);
    CFileDialog dlg(FALSE,
                    "pgn",  // default file extension
                    NULL,    // no default filename
                    OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                    (LPCSTR)filter,
                    this);
    if (dlg.DoModal() == IDOK)
    {
        // check the file extension
        CString str = dlg.GetFileExt();
        CString fileName = dlg.GetPathName();
        // remove any leading or trailing spaces.
        // Under NT, these can actually wind up in the file name!
        const int last = fileName.GetLength()-1;
        int j = 0;
        while (j<=last)
        {
            if (fileName[j] != ' ')
               break;
            ++j;
        }
        int i = last;
        while (i)
        {
            if (fileName[i] != ' ')
               break;
            i--;
        }
        fileName = fileName.Mid(j,i+1-j);
        ofstream ofs(fileName,ios::out);
        if (!ofs.good())
        {
            CString msg;
            AfxFormatString1(msg,IDS_CANT_OPEN,fileName);
            MessageBox(msg);     
            return;
        }          
        str.MakeLower();
        str = str.Left(3);
        if (str == "pgn")
        {
            ChessIO::store_pgn(ofs,*theLog,computer_side,NULL,eco);
        }
        else if (str == "fen" || str == "epd")
        {
            ChessIO::store_fen(ofs,current_board);
        }
        else
        {
            CString msg;
            msg.LoadString(IDS_UNKNOWN_FILE);
            MessageBox((LPCSTR)msg);         
        }
        ofs.close();
    }
}

void CArasanView::OnEditCopyPosition()
{
   if (OpenClipboard())
   {   
       ::EmptyClipboard();
       HGLOBAL hBuf = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,256);
       char *buf = (char*)GlobalLock(hBuf);
       ZeroMemory(buf,256);
       ChessIO::write_fen(current_board,buf,FALSE);
       buf[strlen(buf)] = '\015'; // carriage return
       buf[strlen(buf)] = '\012'; // line feed
       HANDLE hndl = ::SetClipboardData(CF_TEXT,hBuf);
       GlobalUnlock(hBuf);
       // N.b. do not free the memory
       CloseClipboard();
   }
}

void CArasanView::OnEditCopyGame()
{
   if (OpenClipboard())
   {   
       ::EmptyClipboard();
       HGLOBAL hBuf = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,4096);
       char *buf = (char*)GlobalLock(hBuf);
       ZeroMemory(buf,4096);
       static char tmp[4096];
       // Create memory stream for output
       strstream ofs(tmp,256,ios::out | ios::in);
       // Write into the memory stream
       ChessIO::store_pgn(ofs,*theLog,computer_side,NULL,eco);
       // CF_TEXT clipboard format requires carriage return and
       // line feed at line end.  So ensure this:
       char *p = tmp;
       char *q = buf;
       while (*p)
       {
          if (*p == '\n')
             *q++ = '\015'; // carriage return
          *q++ = *p++;
       }
       *q = '\0';
       HANDLE hndl = ::SetClipboardData(CF_TEXT,hBuf);
       GlobalUnlock(hBuf);
       // N.b. do not free the memory
       CloseClipboard();
   }
}

void CArasanView::OnEditCopyBitmap()
{
    // We should really be using the OLE interfaces to get this
    // done ..
    CMetaFileDC metaDC;
    CDC *pDC =GetDC();

    if (metaDC.CreateEnhanced(pDC, NULL, NULL, "Arasan chessboard"))
    {
        metaDC.SetMapMode(MM_ANISOTROPIC);
        CRect area;
        GetClientRect(&area);
        disp->draw_board(&metaDC,current_board,&area,FALSE);
        HENHMETAFILE hEnhMetaFile = metaDC.CloseEnhanced();
        if (OpenClipboard())
        {   
           ::EmptyClipboard();
           HANDLE hndl = ::SetClipboardData(CF_ENHMETAFILE,hEnhMetaFile);
           CloseClipboard();
        }
    }
    ReleaseDC(pDC);
}

void CArasanView::draw_board()
{
    CDC *pDC = GetDC();
    CRect r;
    GetClientRect(&r);
    disp->draw_board(pDC,current_board,&r);
    ReleaseDC(pDC);
}

void CArasanView::show_side()
{
    CDC *pDC = GetDC();
    disp->show_side(pDC,current_board.Side());
    ReleaseDC(pDC);
}

void CArasanView::load_pgn(ifstream &pgn_file, DWORD offset)
{
    theLog->clear();
    ChessIO::load_pgn(pgn_file,*theLog,offset,players);
    reset(NewGame);
}

void CArasanView::OnForward()
{
    if (theLog->go_forward())
    {
        last_move = theLog->last_move();
        const char *result = NULL;
        const Log_Entry &lastEntry = (*theLog)[theLog->current()-1];
        result = lastEntry.result();
        update_board(last_move,NULL,result,FALSE);
    }
}

void CArasanView::OnHint()
{
    HintDialog dlg(this, current_board);
    if (dlg.DoModal() == IDCANCEL)
    {
        return;
    }
    else
    {
        // got a move, execute it
        ExtendedMove emove(current_board,dlg.hintMove);
        last_move = ReversibleMove(current_board,emove);
        if (searching)
        {
            // tell the search to terminate
            last_cmd = ID_MAKEUSERMOVE;
            stop_search(FALSE);
        }
        PostMessage(WM_COMMAND,ID_MAKEUSERMOVE,0L);
        return;
    }        
}

void CArasanView::OnPause()
{
    if (the_clock->is_running())
       the_clock->pause();
    else
       the_clock->resume();
}

void CArasanView::OnPlaywhite()
{
     computer_side = OppositeColor(computer_side);
     users_move = !users_move;
     // Note: we have already terminated any search in progress.
     // We are going to start a new one here.
     if (users_move)
     {
         if (global_options->think_when_idle())
             PostMessage(WM_COMMAND, ID_BACKGROUND_COMPUTE,0L);
     }
     else
     {
         PostMessage(WM_COMMAND,ID_COMPUTE,0L);
     }
}

void CArasanView::OnPreferences()
{
    GeneralPreferences gprefs;
    OpeningPreferences oprefs;
    global_options->get_general_preferences(gprefs);
    CPreferencesSheet prefsSheet(IDS_PREFS_TITLE);
    GeneralPrefsPage genPage;
    CColorPage colorPage;
    OpeningsPage openingsPage;
    AdvancedOptionsPage advPage;
    
    genPage.m_BeepAfterMove = gprefs.beep_after_move;
    genPage.m_BeepOnError = gprefs.beep_on_error;
    genPage.m_CanResign = gprefs.can_resign;
    genPage.m_ThinkWhenIdle = gprefs.think_when_idle;

    COLORREF lightColor, darkColor;
    global_options->get_colors(&lightColor,&darkColor);
    colorPage.lightColor = lightColor;
    colorPage.darkColor = darkColor;
    
    global_options->get_opening_preferences(oprefs);
    openingsPage.m_BookEnabled = oprefs.use_book;
    openingsPage.m_Style = oprefs.style;

    SearchOps src_opts;
    global_options->get_search_ops(src_opts);
    advPage.m_ForcedExtensions = src_opts.forced_extensions;
    advPage.m_History = src_opts.history;
    advPage.m_AutoSize = (src_opts.auto_size_hash_table ?
        1 : 0);
    advPage.m_PawnPushExtensions = src_opts.pawn_push_extensions;
    advPage.m_CheckExtensions = src_opts.check_extensions;
    char hashSizeText[20];
    wsprintf(hashSizeText,"%d",src_opts.hash_table_size);
    advPage.m_HashSize = hashSizeText;
    advPage.m_Nulls = src_opts.null_depth;
    advPage.m_Killers = src_opts.killers;
    int save_hash_table_size = src_opts.hash_table_size;
    BOOL save_auto_size_hash_table = src_opts.auto_size_hash_table;
    
    prefsSheet.AddPage(&genPage);
    // add the Color page only if we are not on a mono
    // system:
    if (!Display::is_mono())
    {
       prefsSheet.AddPage(&colorPage);
    }
    prefsSheet.AddPage(&openingsPage);
    prefsSheet.AddPage(&advPage);

    if (prefsSheet.DoModal() == IDOK)
    {
        gprefs.beep_on_error = genPage.m_BeepOnError;
        gprefs.beep_after_move = genPage.m_BeepAfterMove;
        gprefs.think_when_idle = genPage.m_ThinkWhenIdle;
        gprefs.can_resign = genPage.m_CanResign;
        global_options->update_general_preferences(gprefs);

        if (!Display::is_mono())
        {
           global_options->set_colors(colorPage.lightColor,colorPage.darkColor);
        }
        oprefs.use_book = openingsPage.m_BookEnabled;
        oprefs.style = (styles)openingsPage.m_Style;
        global_options->update_opening_preferences(oprefs);

        src_opts.forced_extensions =  advPage.m_ForcedExtensions;
        src_opts.history = advPage.m_History;
        src_opts.pawn_push_extensions = advPage.m_PawnPushExtensions;
        src_opts.check_extensions = advPage.m_CheckExtensions;
        src_opts.auto_size_hash_table = advPage.m_AutoSize;
        int hashSize;
        sscanf((LPCSTR)advPage.m_HashSize,"%d",&hashSize);
        src_opts.hash_table_size = hashSize;
        src_opts.null_depth = advPage.m_Nulls;
        src_opts.killers = advPage.m_Killers;
        global_options->update_search_ops(src_opts);
        if (src_opts.auto_size_hash_table != save_auto_size_hash_table ||
            src_opts.hash_table_size != save_hash_table_size)
        {
            stop_search(FALSE);
            searcher->reinit();
        }
        Invalidate();
    }
}

void CArasanView::OnReset()
{    
     the_clock->reset();
}

void CArasanView::OnRotateboard()
{
     disp->set_turned(!disp->is_turned());
     draw_board();
}

void CArasanView::OnShowgame()
{
     ShowMovesDialog dlg(this,*theLog);
     dlg.DoModal();     
}

void CArasanView::OnTakeback()
{
    if (theLog->current() == 0)
    {
         MessageBox("No moves to take back.");
         return;
    }
    else
    {
         current_board.UndoMove((*theLog)[theLog->current()-1].move());
         theLog->back_up();
         game_moves->remove_move();
         if (theLog->current())
         {
             last_move = (*theLog)[theLog->current()-1].move();
             strcpy(last_move_image,(*theLog)[theLog->current()-1].image());
         }
         else
         {
             last_move.MakeNull();
             *last_move_image = '\0';
         }
         stats.clear();
         show_last_move();
         // should really be smarter about updating the board:
         draw_board();
         show_side();
         CDC *pDC = GetDC();
         disp->clear_search_counts(pDC);
         disp->clear_status_line(pDC);
         if (theLog->current() == 0)
         {
             eco = "";
             opening_name = "";
         }
         else
             ecoCoder->classify(*game_moves,eco,opening_name);
         disp->show_eco(pDC,eco,opening_name);
         ReleaseDC(pDC);
    }
    no_prev_search = TRUE;
    the_clock->start(current_board.Side());
    users_move = !users_move;
}

void CArasanView::OnNewGame()
{
    current_board.Reset();
    draw_board();
    show_side();
    use_book = TRUE;
    no_prev_search = TRUE;
    the_clock->start(White);        
}
 

void CArasanView::OnFileExit()
{
    stop_search(FALSE);
    ::SendMessage(AfxGetMainWnd()->m_hWnd,WM_CLOSE,0,0L);
}

void CArasanView::OnBackgroundCompute()
{
    if (!global_options->think_when_idle())
          return;
    background_search = TRUE;
    Search_Limit limit;
    if (ti[current_board.OppositeSide()].get_search_type() == Fixed_Ply)
    {
       // obey the ply limit if one is given
       limit.max_ply = ti[current_board.OppositeSide()].get_search_limit().max_ply;
    }
    else
    {
       // We do a search to maximum depth.  We don't really
       // expect to finish this; we will be interrupted
       // by a user command.
       limit.max_ply = Constants::MaxPly;
    }
    
    Time_Info ti;
    ti.set_time_control(Time_Control( Fixed_Ply, limit ));
    ti.set_period(0);
    ponder_board = current_board;
    ExtendedMove emove(current_board,predicted_move);
    ReversibleMove rmove(current_board,emove);
    ponder_board.MakeMove(rmove);
    compute_move(ponder_board, ti,
                 TRUE, stats, emove);
}


/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
        CAboutDlg();

// Dialog Data
        //{{AFX_DATA(CAboutDlg)
        enum { IDD = IDD_ABOUTBOX };
        //}}AFX_DATA

// Implementation
protected:
        virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
        //{{AFX_MSG(CAboutDlg)
                // No message handlers
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
        //{{AFX_DATA_INIT(CAboutDlg)
        //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
        CDialog::DoDataExchange(pDX);
        //{{AFX_DATA_MAP(CAboutDlg)
        //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
        //{{AFX_MSG_MAP(CAboutDlg)
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CArasanView::OnAppAbout()
{
        CAboutDlg aboutDlg;
        aboutDlg.DoModal();
}

void CArasanView::computer_plays(const ColorType side)
{
    computer_side = side;
}    

void CArasanView::OnBrowseNextgame()
{
    ++pgnSelection;
    POSITION pos = pgnContents->FindIndex(pgnSelection);
    CString &str = pgnContents->GetAt(pos);
    int indx = str.Find('\t');
    if (indx >= 0)
    {
       DWORD offset;
       CString num = str.Right(str.GetLength()-indx-1);
       players = str.Left(indx);
       sscanf(num,"%ld",&offset);
       load_pgn(*pgn_file,offset);
    }
}

void CArasanView::OnBrowsePreviousgame()
{
    --pgnSelection;
    POSITION pos = pgnContents->FindIndex(pgnSelection);
    CString &str = pgnContents->GetAt(pos);
    int indx = str.Find('\t');
    if (indx >= 0)
    {
       DWORD offset;
       CString num = str.Right(str.GetLength()-indx-1);
       players = str.Left(indx);
       sscanf(num,"%ld",&offset);
       load_pgn(*pgn_file,offset);
    }
}

void CArasanView::OnBrowseSelectgame()
{
    DWORD offset = 0L;
    if (pgnContents && pgnContents->GetCount() > 1)
    {   
       SelectGame dlg(*pgnContents,this);
       if (dlg.DoModal() == IDCANCEL)
       {
          return;  
       }
       offset = dlg.get_offset();
       pgnSelection = dlg.get_selection();
       POSITION pos = pgnContents->FindIndex(pgnSelection);
       CString &str = pgnContents->GetAt(pos);
       int indx = str.Find('\t');
       if (indx >= 0) players = str.Left(indx);
    }
    load_pgn(*pgn_file,offset);
}

void CArasanView::OnBeginGame()
{
    reset(NewGame);
}

void CArasanView::OnEndGame()
{
    while (theLog->go_forward()) 
    {
        last_move = theLog->last_move();
        const char *result = NULL;
        const Log_Entry &lastEntry = (*theLog)[theLog->current()-1];
        result = lastEntry.result();
        ExtendedMove emove(current_board,last_move);
        last_move = ReversibleMove(current_board,emove);
        Notation::Image(current_board,last_move,last_move_image);
        current_board.MakeMove(last_move);
        game_moves->add_move(current_board,emove);
        ecoCoder->classify(*game_moves,eco,opening_name);
    }
    show_last_move( last_move_image ); // must call after add_move
    const Log_Entry &lastEntry = (*theLog)[theLog->current()-1];
    const char *result = lastEntry.result();
    CDC *pDC = GetDC();
    disp->show_status(pDC,result);
    ReleaseDC(pDC);
    draw_board();
    show_side();
}

void CArasanView::OnLossOnTime()
{
    char msg[80];
    sprintf(msg,"%s loses on time!",Image(current_board.Side()));
    MessageBox(msg);
}

void CArasanView::OnUpdateBrowseNextgame(CCmdUI* pCmdUI)
{
     pCmdUI->Enable(pgnContents && (pgnContents->GetCount() > 1) &&
                    (pgnSelection < pgnContents->GetCount()-1));
}    

void CArasanView::OnUpdateBrowsePreviousgame(CCmdUI* pCmdUI)
{
     pCmdUI->Enable(pgnContents && (pgnContents->GetCount() > 1) &&
                    (pgnSelection > 0));        
}

void CArasanView::OnUpdateBrowseSelectgame(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(pgnContents && pgnContents->GetCount() > 1);
}

void CArasanView::OnUpdatePlayWhite(CCmdUI* pCmdUI)
{
    pCmdUI->SetCheck(computer_side == White);
}

void CArasanView::OnUpdatePause(CCmdUI* pCmdUI)
{
    pCmdUI->SetCheck(!the_clock->is_running() && !the_clock->is_stopped());
    pCmdUI->Enable(!the_clock->is_stopped());
}


void CArasanView::OnUpdateBeginGame(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(theLog->num_moves() > 0 &&
       theLog->current() != 0);
}

void CArasanView::OnUpdateEndGame(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(theLog->num_moves() > 0 &&
      theLog->current() < theLog->num_moves());
}

void CArasanView::OnUpdateForward(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(theLog->current() < theLog->num_moves());
}

void CArasanView::OnUpdateBack(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(theLog->current() != 0);
}

void CArasanView::OnUpdateHint(CCmdUI* pCmdUI) 
{
    // Only allow hints when it is the user's turn to move
    pCmdUI->Enable(users_move);	
}

void CArasanView::OnFileAnnotate() 
{
     CString filter;
     filter.LoadString(IDS_ANNOTATE_FILTER);
     CFileDialog dlg(TRUE,
                     ".epd",  // default file extension
                     NULL,    // no default filename
                     OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                     (LPCSTR)filter,
                     this);
     if (dlg.DoModal() == IDOK)
     {
         CString fileName = dlg.GetPathName();
                 // get the extension:
         CString ext;
         CString name(fileName);
         int i = name.GetLength()-1;
         while (i>=0 && name[i] != '.') --i;
         if (name[i] == '.')
            ext = name.Right(name.GetLength()-i-1);
         ifstream file;
         file.open(fileName,ios::in | ios::binary);
         if (!file.good())
         {           
              CString msg;
              AfxFormatString1(msg,IDS_CANT_OPEN,fileName);
              MessageBox(msg);
              return;
         }
         ext.MakeLower();
         if (ext == "epd")     
         {
             current_board.Reset();
             theLog->clear();        
             if (pgnContents)
                 free_pgn();
             
             // might take awhile, put up a wait cursor. A
             // progress bar would be nicer ...
             BeginWaitCursor();
             
             EpdStatusDialog dlg(this,name,file,current_board,TRUE);
             dlg.DoModal();
             EndWaitCursor();

             reset(NewGame);
         }
         else
         {          
             CString msg;
             msg.LoadString(IDS_UNKNOWN_FILE);
             MessageBox((LPCSTR)msg);
         }
     }                  
}

void CArasanView::OnDropFiles(HDROP hDropInfo)
{
     *initialFilename = '\0';
     ::DragQueryFile(hDropInfo, 0, initialFilename, MAX_PATH);
     if (*initialFilename)
     {
        SendMessage(WM_COMMAND,ID_FILE_OPEN,(LPARAM)0);
     }
     ::DragFinish(hDropInfo);
}

BOOL CArasanView::OnRecentFile(UINT nID)
{ 
    // For some reason, this message comes to the view class, but
    // the default handler for it is in CWinApp, so route it there
    CArasanApp *app = (CArasanApp*)AfxGetApp();
    return app->OnRecentFile(nID);
}

void CArasanView::OnClose()
{
    // save the window size and position for the next time we are run.
    CRect rect;
    AfxGetMainWnd()->GetWindowRect(&rect);
    global_options->set_app_geometry(rect.left,rect.right,rect.Width(),rect.Height());
    quitting = TRUE;
    stop_search(FALSE);
}

void CArasanView::stop_search(BOOL makeMove)
{
    EnterCriticalSection(&searchCritSection);
    if (searching)
    {
       LeaveCriticalSection(&searchCritSection);
       searcher->terminate_now(makeMove);
       int retVal = WaitForSingleObject(searchThread,0);
       if (retVal != WAIT_OBJECT_0)
       {
          retVal = WaitForSingleObject(searchThread,10000);
          if (retVal == WAIT_TIMEOUT)
          {
              TRACE("warning: search thread not completed.");
#ifdef _DEBUG
              MessageBox("search thread not completed.");
#endif
          }
       }
       // Show the final results of the search in the status bar
       Search::Statistics stats;
       searcher->update_stats(stats);
       searcher->show_status(current_board,0,stats,stats.best_line[0]);
       extern int num_searches;
       // ASSERT(num_searches == 0);
    }
    else
       LeaveCriticalSection(&searchCritSection);
}

void CArasanView::waitForSearch()
{
    WaitForSingleObject(searchThread,INFINITE);
}



