// Copyright 1994 by Jon Dart.  All Rights Reserved.

#include "stdafx.h"
#include "display.h"
#include "square.h"
#include "constant.h"
#include "resource.h"
#include "mainfrm.h"
#include "globals.h"
#include "util.h"
#include <string.h>
#include <stdio.h>

#define WHITE RGB(0xff,0xff,0xff)
#define BLACK RGB(0,0,0)

static CBitmap *pawn_bitmap = NULL;
static CBitmap *pawn_outline = NULL;
static CBitmap *knight_bitmap = NULL;
static CBitmap *knight_outline = NULL;
static CBitmap *bishop_bitmap = NULL;
static CBitmap *bishop_outline = NULL;
static CBitmap *rook_bitmap = NULL;
static CBitmap *rook_outline = NULL;
static CBitmap *queen_bitmap = NULL;
static CBitmap *queen_outline = NULL;
static CBitmap *king_bitmap = NULL;
static CBitmap *king_outline = NULL;
static CBitmap *square_bitmap = NULL;

#define STATUS_AREA_WIDTH       122
#define TEXT_OFFSET             114 /* from right edge */
#define TO_MOVE_Y               3
#define TIME_Y                  28
#define MOVE_Y                  90
#define STATUS_Y                106
#define PLY_Y                   132
#define PLAYERS_Y               164
#define ECO_Y                   230

int Display::spacing = 0;
BOOL Display::mono = FALSE;
static const LOGFONT textLF = { 8,0,0,0,700,0,0,0,ANSI_CHARSET,
  OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
  DEFAULT_PITCH | FF_SWISS,
  "MS Sans Serif"};
static CFont *textFont = NULL;

static void clearRect(CDC *pDC, CRect &rect)
{
     CRgn rgn;
     rgn.CreateRectRgnIndirect(rect);
     CBrush brush;
     brush.CreateSolidBrush(GetSysColor(COLOR_WINDOW));
     pDC->FillRgn(&rgn,&brush);
}

Display::Display( CWnd *pWin, const CRect &initial_size )
: turned(FALSE)
{
     TEXTMETRIC my_tm;
     textFont = new CFont();
     textFont->CreateFontIndirect(&textLF);

     CDC *pDC = pWin->GetDC();
     pDC->SelectObject(textFont);
     pDC->GetTextMetrics(&my_tm);
     mono = (pDC->GetDeviceCaps(NUMCOLORS) <= 2);
	 if (mono)
	 {
        // 16- and 32-bit color modes do not seem
		// to report NUMCOLORS correctly.
		mono = pDC->GetDeviceCaps(BITSPIXEL) == 1 &&
			pDC->GetDeviceCaps(PLANES) == 1;

	 }
     pWin->ReleaseDC(pDC);
     char_width = my_tm.tmAveCharWidth;
     max_char_width = my_tm.tmMaxCharWidth;
     char_height = my_tm.tmHeight;
     spacing = my_tm.tmHeight + my_tm.tmExternalLeading;
     vertical_border_width = char_width + 7;
     horiz_border_height = char_height + 4;

     set_size(initial_size);
     if (pawn_bitmap)
        return;
     pawn_bitmap = new CBitmap();
     pawn_bitmap->LoadBitmap(IDB_PAWN);
     pawn_outline = new CBitmap();
     pawn_outline->LoadBitmap(IDB_PAWNO);
     knight_bitmap = new CBitmap();
     knight_bitmap->LoadBitmap(IDB_KNIGHT);     
     knight_outline = new CBitmap();
     knight_outline->LoadBitmap(IDB_KNIGHTO);
     bishop_bitmap = new CBitmap();
     bishop_bitmap->LoadBitmap(IDB_BISHOP);
     bishop_outline = new CBitmap();
     bishop_outline->LoadBitmap(IDB_BISHOPO);
     rook_bitmap = new CBitmap();
     rook_bitmap->LoadBitmap(IDB_ROOK);
     rook_outline = new CBitmap();
     rook_outline->LoadBitmap(IDB_ROOKO);
     queen_bitmap = new CBitmap();
     queen_bitmap->LoadBitmap(IDB_QUEEN);
     queen_outline = new CBitmap();
     queen_outline->LoadBitmap(IDB_QUEENO);
     king_bitmap = new CBitmap();
     king_bitmap->LoadBitmap(IDB_KING);
     king_outline = new CBitmap();
     king_outline->LoadBitmap(IDB_KINGO);
     square_bitmap = new CBitmap();
     square_bitmap->LoadBitmap(IDB_SQUARE_BITMAP);
}

Display::~Display()
{
     delete king_bitmap;
     delete king_outline;
     delete queen_bitmap;
     delete queen_outline;
     delete rook_bitmap;
     delete rook_outline;
     delete bishop_bitmap;
     delete bishop_outline;
     delete knight_bitmap;
     delete knight_outline;
     delete pawn_bitmap;
     delete pawn_outline;
     delete square_bitmap;
     delete textFont;
}

void Display::draw_piece(CDC *pDC,const Square &loc,
                         const CRect &square,CBitmap *bitmap,
                         const ColorType color,CBitmap *outline)
{   
    CBrush *pBrush = get_brush(pDC, loc);
    CBrush *oldBrush = pDC->SelectObject(pBrush);
    // make dest square a little smaller, to avoid clobbering
    // black space between squares:
    CRect new_area(square);
    CRect src_rect(1,1,62,62);
    new_area.InflateRect(-1,-1);
    if (color == Black)
    {
       CDC memdc;
       memdc.CreateCompatibleDC(pDC);
       memdc.SelectObject(bitmap); 
       pDC->StretchBlt(new_area.left,new_area.top,new_area.Width(),
         new_area.Height(),&memdc,src_rect.left,src_rect.top,
         src_rect.Width(),src_rect.Height(),MERGECOPY);
    }
    else
    {
       CDC memdc;
       memdc.CreateCompatibleDC(pDC);
       memdc.SelectObject(bitmap);
       pDC->StretchBlt(new_area.left,new_area.top,new_area.Width(),
          new_area.Height(),&memdc,src_rect.left,src_rect.top,
          src_rect.Width(),src_rect.Height(),MERGEPAINT);
       if (outline)
       {
          CDC memdc;
          memdc.CreateCompatibleDC(pDC);
          memdc.SelectObject(outline);
          pDC->StretchBlt(new_area.left,new_area.top,new_area.Width(),
             new_area.Height(),&memdc,src_rect.left,src_rect.top,
             src_rect.Width(),src_rect.Height(),SRCAND);
       }
    }
    pDC->SelectObject(oldBrush);
    delete pBrush;
}
                       
void Display::draw_piece( CDC *pDC, const Square &loc, const CRect &square, const Piece &piece)
{
      // Note: pDC should have the proper background brush selected on 
      // entry
      
      switch(piece.Type())
      {
          case Piece::Pawn:
          {
             draw_piece(pDC,loc,square,pawn_bitmap,piece.Color(),
               pawn_outline);
             break;
          }
          case Piece::Rook:
          {
             draw_piece(pDC,loc,square,rook_bitmap,piece.Color(),
               rook_outline);
             break;
          }
          case Piece::Knight:
          {
             draw_piece(pDC,loc,square,knight_bitmap,piece.Color(),
               knight_outline);
             break;
          }
          case Piece::Bishop:
          {
             draw_piece(pDC,loc,square,bishop_bitmap,piece.Color(),
               bishop_outline);
             break;
          }
          case Piece::Queen:
          {
             draw_piece(pDC,loc,square,queen_bitmap,piece.Color(),
               queen_outline);
             break;
          }
          case Piece::King:
          {
             draw_piece(pDC,loc,square,king_bitmap,piece.Color(),
               king_outline);
             break;
          }
          default:
             break;
       }
}

const CRect Display::set_size( const CRect &size)
{
    CRect old_size = display_area;
    display_area = size;
    width = size.Width();
    height = Util::Abs(size.Height()) - horiz_border_height;
    square_height = height/8;
    square_width = (width - STATUS_AREA_WIDTH - vertical_border_width) / 8; 
    yDir = Util::Sign(display_area.bottom - display_area.top);
    return old_size;
}

void Display::draw_board( CDC *pDC, const Board &board, 
        const CRect *drawArea, BOOL show_text)
{
     CRgn drawRegion;
     drawRegion.CreateRectRgnIndirect(drawArea);     
     int vert_incr, vert_start, horiz_incr, horiz_start;
     if (turned)
     {
        vert_incr = -yDir*square_height;
        vert_start = drawArea->top + 7*yDir*square_height;
        horiz_incr = -square_width;
        horiz_start = drawArea->left + 7*square_width;
     }
     else
     {
        vert_incr = yDir*square_height;
        vert_start = drawArea->top;
        horiz_start = drawArea->left;
        horiz_incr = square_width;
     }
     int vert = vert_start;
     int horiz;
     const int right_edge = square_width*8;
     const int bottom_edge = square_height*8;

     pDC->SelectObject(textFont);
     for (int i = 0; i < 8; i++)
     {
        horiz = horiz_start;

        // draw text in vertical border:
        if (show_text)
        {
           COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));

           int oldmode = pDC->SetMapMode(MM_TEXT);
           int text_start = (i*square_height) + square_height/2 - char_height/2; 
           CRect rc(right_edge+3, text_start,
                    right_edge+vertical_border_width-3,
                    text_start+max_char_width);
           char border_text[1];
           *border_text = (turned) ? '1' + i : '8' - i;
           pDC->DrawText(border_text,1,&rc,DT_LEFT);

           // draw text in horizontal border:
           text_start = (i*square_width) + square_width/2 - char_width/2;
           rc.SetRect(text_start,bottom_edge+2,text_start+max_char_width,
                      bottom_edge+horiz_border_height);
           *border_text = (turned) ? 'H' - i : 'A' + i;
           pDC->DrawText(border_text,1,&rc,DT_LEFT);
           pDC->SetMapMode(oldmode);
		   pDC->SetBkColor(oldColor);
        }
                
        for (int j = 0; j < 8; j++)
        {
           Square sq(j+1,i+1,Black);
           CRect square(horiz,vert,
                        horiz+square_width,vert+yDir*square_height);
           CRgn squareRegion;
           squareRegion.CreateRectRgnIndirect(&square);
           {

               Piece piece(board[sq]);
               draw_square(pDC, sq); 
               if (!piece.IsEmpty())
               {
                  draw_piece(pDC,sq,square,piece);
               }
 
           }
           horiz += horiz_incr;
        }
        vert += vert_incr;
     }
}

void Display::square_rect(const Square &sq, CRect &out )
{
     CRect size = get_size();
     int rank = sq.Rank(Black);
     int file = sq.File();
     if (turned)
     {
        rank = 9-rank;
        file = 9-file;
     }

     CPoint orig(size.left + square_width*(file-1),
                 size.top + yDir*square_height*(rank-1));
     CPoint end(size.left + square_width*file,
                size.top + yDir*square_height*rank);
     out = CRect(orig.x,orig.y,end.x,end.y);
}

void Display::draw_piece( CDC *pDC, const Square &loc, const Piece &p )
{
     CRect area;
     square_rect(loc,area);

     draw_piece(pDC,loc,area,p);
}

void Display::draw_square( CDC *pDC, const Square &loc )
{
     CRect area;
     square_rect(loc,area);
     CBrush *pBrush = get_brush(pDC, loc);

     CBrush *oldBrush = pDC->SelectObject(pBrush);
     pDC->Rectangle(area);
     pDC->SelectObject(oldBrush);
     delete pBrush;
}

void Display::highlight_square( CDC *, const Square & )
{
     // unimplemented
}

void Display::unhighlight_square( CDC *, const Square & )
{
     // unimplemented
}

void Display::set_turned(BOOL turnit)
{
     turned = turnit;
}

Square Display::mouse_loc( CPoint &p)
{
     int x,y;
     x = (p.x/square_width)+1;
     if (turned)
        x = 9-x;
     y = (p.y/square_height)+1;
     if (x > 8 || y > 8)
        return Square(0);
     else
        return Square(x,y,turned ? White : Black);
}

void Display::show_side( CDC *pDC, const ColorType side )
{
     static char *wstr = "WHITE to move";
     static char *bstr = "BLACK to move";
     int start = width - TEXT_OFFSET;
     CRect rcText(start,TO_MOVE_Y,width-5,TO_MOVE_Y+spacing);
     pDC->SelectObject(textFont);
	 COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
     pDC->DrawText((side == White) ? wstr : bstr,
       lstrlen(wstr), &rcText, DT_LEFT | DT_WORDBREAK);
	 pDC->SetBkColor(oldColor);
}

void Display::show_move( CDC *pDC, const char *move_image,
  int number )
{
     pDC->SelectObject(textFont);
     int start = width - TEXT_OFFSET;
     CRect rcText(start,(int)MOVE_Y,start+width-5,
      (int)MOVE_Y+spacing);
     char text[80]; 
     // erase any previous text:
     clearRect(pDC,rcText);
     int move_num = (number-1)/2;
     if (number % 2)
       wsprintf(text,"%d  %s",move_num+1,move_image);
     else
       wsprintf(text,"%d ... %s",move_num+1,move_image);
	 COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
     pDC->DrawText(text,strlen(text), &rcText, DT_LEFT | DT_WORDBREAK);
	 pDC->SetBkColor(oldColor);
}

void Display::clear_move_area( CDC *pDC)
{
     int start = width - TEXT_OFFSET;
     CRect rcText(start,MOVE_Y,start+width-5,
      MOVE_Y+spacing);
      // erase any previous text:
     clearRect(pDC,rcText);
}

void Display::clear_status_line( CDC *pDC )
{
     int start = width - TEXT_OFFSET;
     CRect rcText(start,STATUS_Y,width-5,
        STATUS_Y+spacing);
     // erase any previous contents of this field:
     clearRect(pDC,rcText);
}

void Display::show_status( CDC *pDC, const Search::Statistics stats)
{
     static char *images[] =
     {
        "         ",
        "         ",
        "Check!   ",
        "Mate!    ",
        "Stalemate",
        "Draw!    ",
        "I resign!"
     };
     if (!textFont) // not initialized yet
         return;
     pDC->SelectObject(textFont);
     int start = width - TEXT_OFFSET;
     CRect rcText(start,STATUS_Y,width-5,
        STATUS_Y+spacing);
     clearRect(pDC,rcText);
     char text[30];
     if ((stats.state == Search::Normal || stats.state == Search::Check) &&
         (stats.value > Constants::BIG-100))
     {
        if (stats.value == Constants::BIG-1)
           strcpy(text,"Checkmate!");
        else
           wsprintf(text,"Mate in %d!",(Constants::BIG-stats.value-1)/2);
     }
     else
        wsprintf(text,"%s",images[stats.state]);
	 COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
     pDC->DrawText(text,strlen(text), &rcText, DT_LEFT | DT_WORDBREAK);
	 pDC->SetBkColor(oldColor);
}

void Display::show_status( CDC *pDC, const char *message)
{
     if (!textFont) // not initialized yet
         return;
     pDC->SelectObject(textFont);
     int start = width - TEXT_OFFSET;
     CRect rcText(start,STATUS_Y,width-5,
        STATUS_Y+spacing);
     clearRect(pDC,rcText);
 	 COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
     pDC->DrawText(message,strlen(message), &rcText, DT_LEFT | DT_WORDBREAK);
	 pDC->SetBkColor(oldColor);
}

void Display::show_time( HWND handle, const time_t time,
                         const ColorType side )
{
     // all straight Windows code - because we have no access to
     // MFC classes 
     if (!textFont) // not initialized yet
        return;
     HDC hdc = GetDC(handle);
     ASSERT(hdc);
     SelectObject(hdc,textFont->m_hObject);
     char time_str[20];
     unsigned hours = (unsigned)(time/3600U);
     unsigned minutes = (unsigned) ((time - (hours*3600U))/60U);
     unsigned seconds = (unsigned)(time - (hours*3600U) - (minutes*60U));
     // convert time to ASCII:
     wsprintf(time_str,"%02d:%02d:%02d",
        (int)hours,(int)minutes,(int)seconds);
     RECT rcText, client_rect, text_rect;
     GetClientRect( handle, &client_rect );
     int width = client_rect.right - client_rect.left;
     int vbase = TIME_Y;
 	 SetBkColor(hdc,GetSysColor(COLOR_WINDOW));

     if (side == White)
     {
        SetRect(&text_rect,width-TEXT_OFFSET,vbase,width-5,vbase+spacing);
        DrawText(hdc,"WHITE:",6,&text_rect,DT_LEFT);
        SetRect(&rcText,width-TEXT_OFFSET,vbase+spacing,width-5,vbase+2*spacing);
     }
     else
     {
        SetRect(&text_rect,width-TEXT_OFFSET,vbase+2*spacing,width-5,
                vbase+3*spacing);
        DrawText(hdc,"BLACK:",6,&text_rect,DT_LEFT);
        SetRect(&rcText,width-TEXT_OFFSET,vbase+3*spacing,width-5,vbase+4*spacing);
     }
     DrawText(hdc,time_str,lstrlen(time_str),&rcText,
                DT_LEFT | DT_WORDBREAK);
	 
     BOOL ret = ReleaseDC(handle,hdc);
     ASSERT(ret);
}

void Display::clear_search_counts(  CDC *pDC )
{
     int start = width - TEXT_OFFSET;
     CRect rcText(start,PLY_Y,width-5,
        PLY_Y+2*spacing);
     clearRect(pDC,rcText);
}

void Display::show_search_counts( HWND handle, const int ply,
     const long nodes)
{
     if (!textFont)
        return;
     
     CRect client_rect, text_rect;
     GetClientRect( handle, (LPRECT)&client_rect );
     int width = client_rect.right - client_rect.left;
     int vbase = PLY_Y;
     HDC hdc = GetDC(handle);
     ASSERT(hdc);
     SelectObject(hdc,textFont->m_hObject);
     text_rect.SetRect(width-TEXT_OFFSET,vbase,width-5,vbase+spacing);
     char msg[20];
     wsprintf(msg,"Ply: %d",ply);
 	 SetBkColor(hdc,GetSysColor(COLOR_WINDOW));

     DrawText(hdc,msg,lstrlen(msg),&text_rect,
                DT_LEFT);
     wsprintf(msg,"Nodes: %ld",nodes);
     text_rect.OffsetRect(0,spacing);
     DrawText(hdc,msg,lstrlen(msg),&text_rect,
                DT_LEFT);
     BOOL ret = ReleaseDC(handle,hdc);
     ASSERT(ret);
}

CBrush *Display::get_brush( CDC *pDC, const Square &loc)
{
     BOOL useMonoBrush = (pDC->IsKindOf(RUNTIME_CLASS(CMetaFileDC)) || is_mono());
     COLORREF lightSquareColor, darkSquareColor;
     global_options->get_colors(&lightSquareColor,&darkSquareColor);
     CBrush *brush;
     brush = new CBrush();
     BOOL dark = ((loc.Rank(Black) + loc.File()) % 2);
     if (dark)
     {
         
        if (useMonoBrush)
            brush->CreatePatternBrush(square_bitmap);
        else
            brush->CreateSolidBrush(darkSquareColor);
     }
     else
     {
        if (useMonoBrush)
           brush->CreateSolidBrush(WHITE);
        else
           brush->CreateSolidBrush(lightSquareColor);
     }            
     return brush;
}

void Display::show_eco( CDC *pDC, LPCSTR code, LPCSTR opening_name)
{
     if (!textFont) // not initialized yet
         return;
     pDC->SelectObject(textFont);
     int start = width - TEXT_OFFSET;
     CRect rcText(start,ECO_Y,width-5,ECO_Y+spacing);
     clearRect(pDC,rcText);
	 COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));

     pDC->DrawText(code,strlen(code),&rcText,DT_LEFT);
     rcText.SetRect(start,ECO_Y+spacing,width-5,ECO_Y+4*spacing);
     clearRect(pDC,rcText);

     pDC->DrawText(opening_name,strlen(opening_name),
                   &rcText, DT_LEFT | DT_WORDBREAK);
	 pDC->SetBkColor(oldColor);
}

void Display::show_players( CDC *pDC, LPCSTR players)
{
     if (!textFont) // not initialized yet
         return;
     pDC->SelectObject(textFont);
     int start = width - TEXT_OFFSET;
     CRect rcText;
     rcText.SetRect(start,PLAYERS_Y,width-5,PLAYERS_Y+4*spacing);
     clearRect(pDC,rcText);
	 COLORREF oldColor = pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
     pDC->DrawText(players,strlen(players),
                   &rcText, DT_LEFT | DT_WORDBREAK);
	 pDC->SetBkColor(oldColor);
}
     
     
