#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/times.h>
#include <sys/file.h>
#include <pwd.h>


#import <appkit/appkit.h>

#import "chess_strings.h"
#import "Chess.h"
#import "gnuchess.h"
#import "gnuglue.h"
#import "Clock.h"
#import "Board.h"
#import "Board3D.h"
#import "ResponseMeter.h"

extern id NXWait, NXArrow;
extern short PieceList[2][16];
extern short PieceCnt[2];
extern gnuchess_main_init();
extern NewGame();
extern algbr();
/* extern getsectdata(); */
extern VerifyMove();
extern SelectMoveStart();
extern SelectMoveEnd();
extern SelectLoop();

short GameQueens[240];


#ifdef NeXT_DEBUG
#define chess_debug(x) { printf x; }
#else
#define chess_debug(x)
#endif

@implementation Chess

+ initialize
{
  gnuchess_main_init();
  return self;
}

+ new
{
  self = [super new];
  pwen = getpwuid( getuid() );

  pref.time_control_minutes = 5;
  pref.time_control_moves = 60;
  pref.opponent = white;
  pref.computer = black;
  pref.bothsides = false;
  pref.cheat = YES;
  pref.white_name = pwen->pw_gecos;
  pref.black_name = (char *)COMPUTER;
  white_color = NX_COLORWHITE;
  black_color = NX_COLORWHITE;
  [self newGame: self];
  return self;
}
  
/*
  Get InterfaceBuilder outlets.
*/
- clockWindow { return clockWindow; return self; }
- whiteClock { return whiteClock; }
- blackClock { return blackClock; }
- whiteMeter { return whiteMeter; }
- blackMeter { return blackMeter; }
- setBoard3D: anObject { gameBoard = board3D = anObject; return self;  }
- setBoard2D: anObject { board2D = anObject; [board2D removeFromSuperview]; return self;  }
/*
  Action methods for InterfaceBuilder objects.
*/

- print: sender
{
  [gameBoard printPSCode: sender];
  return self;
}

- (int) openFile:(char const *)s ok:(int *)flag {
    GetGame( s );
    *flag = YES;
    return 0;
}
- setBlackColor:sender
{
    float sat;
    
    black_color = [sender color];
    black_color = NXChangeAlphaComponent(black_color,1.0);
    black_color = NXChangeBrightnessComponent(black_color,1.0);
    sat = NXSaturationComponent(black_color);
    if(sat > .21 ) black_color = NXChangeSaturationComponent(black_color,.21);
    [self renderPreview];
    
    return self;
}
- setWhiteColor:sender
{
    float sat;
    
    white_color = [sender color];
    white_color = NXChangeAlphaComponent(white_color,1.0);
    white_color = NXChangeBrightnessComponent(white_color,1.0);
    sat = NXSaturationComponent(white_color);
    if(sat > .21 ) white_color = NXChangeSaturationComponent(white_color,.21);
    [self renderPreview];
    
    return self;
}


- renderPreview
{
    NXRect r;
    NXPoint pt = {0.0,0.0};
    static id blacky = nil;
    static id whitey = nil;
    static id image1 = nil;
    static id image2 = nil;
    
    
    if(!whitey) whitey = [NXImage newFromSection: "3d_white_sample.tiff"];
    if(!blacky) blacky = [NXImage newFromSection: "3d_black_sample.tiff"];
    if(!image1) image1 = [NXImage newFromSection: "3d_black_sample.tiff"];
    if(!image2) image2 = [NXImage newFromSection: "3d_black_sample.tiff"];
    
    [whitey getSize:&r.size];
    r.origin.x = r.origin.y = 0.0;
    
    [image2 lockFocus];
    [whitey composite:NX_COPY fromRect:&r toPoint:&pt];
    [image2 unlockFocus];
    [image1 lockFocus];
    [whitey composite:NX_COPY fromRect:&r toPoint:&pt];
    [image1 unlockFocus];
    
    [image1 lockFocus];
    NXSetColor(white_color);
    PScompositerect(0.0,0.0,r.size.width,r.size.height, NX_PLUSD);
    [image1 unlockFocus];
    
    [image2 lockFocus];
    [image1 composite:NX_SATOP fromRect:&r toPoint:&pt];
    [image2 unlockFocus];
    
    [[whitesample image] lockFocus];
    [image2 composite:NX_COPY fromRect:&r toPoint:&pt];
    [[whitesample image] unlockFocus];
    
    
    [image2 lockFocus];
    [blacky composite:NX_COPY fromRect:&r toPoint:&pt];
    [image2 unlockFocus];
    [image1 lockFocus];
    [blacky composite:NX_COPY fromRect:&r toPoint:&pt];
    [image1 unlockFocus];
    
    [image1 lockFocus];
    NXSetColor(black_color);
    PScompositerect(0.0,0.0,r.size.width,r.size.height, NX_PLUSD);
    [image1 unlockFocus];
    
    [image2 lockFocus];
    [image1 composite:NX_SATOP fromRect:&r toPoint:&pt];
    [image2 unlockFocus];
    
    [[blacksample image] lockFocus];
    [image2 composite:NX_COPY fromRect:&r toPoint:&pt];
    [[blacksample image] unlockFocus];

    [blacksample display];
    [whitesample display];
    return self;

}
- renderColors:sender
{
    id image2;
    id image1;
    NXRect r;
    NXPoint pt = {0.0,0.0};
    
    
    if( gameBoard != board3D ) return self;
    image1 = [NXImage newFromSection: "3d_pieces.tiff"];
    image2 = [gameBoard backgroundBitmap];
    
    [image2 getSize:&r.size];
    r.origin.x = r.origin.y = 0.0;
    [image2 lockFocus];
    [image1 composite:NX_COPY fromRect:&r toPoint:&pt];
    [image2 unlockFocus];
    
    [image1 lockFocus];
    NXSetColor(white_color);
    PScompositerect(0.0,0.0,336.0,r.size.height, NX_PLUSD);
    NXSetColor(black_color);
    PScompositerect(336.0,0.0,336.0,r.size.height, NX_PLUSD);
    [image1 unlockFocus];
    
    [image2 lockFocus];
    [image1 composite:NX_SATOP fromRect:&r toPoint:&pt];
    [image2 unlockFocus];
    
    [gameBoard display];
    if (image1) [image1 free];
    return self;
}
- plastic: sender
{
  id plastic;
  
  if( gameBoard == board2D )
    return self;
    
  plastic = [NXImage newFromSection: "3d_board.tiff"];
  [gameBoard setBackgroundBitmap: plastic];
  [gameBoard display];
  return self;
}


- gameBoard { return gameBoard; }
- view2D: sender
{
  NXRect b;
  id v;
  
  if( gameBoard == board2D )
    return self;
    
  v = [gameBoard superview];
  [v getBounds: &b];
  [v lockFocus];
  PSgsave();
  PSsetgray( NX_BLACK );
  PSrectfill( 0, 0, b.size.width, b.size.height );
  PSgrestore();
  [v unlockFocus];
  
  [gameBoard removeFromSuperview];
  [v addSubview: board2D];
  gameBoard = board2D;
  [gameBoard layoutBoard: board color: color];
  [gameBoard display];
  NXPing();
  return self;
}

- view3D: sender
{
  id v;
  
  if( gameBoard == board3D )
    return self;
    
  v = [gameBoard superview];
  [gameBoard removeFromSuperview];
  [v addSubview: board3D];
  gameBoard = board3D;
  [gameBoard layoutBoard: board color: color];
  [gameBoard display];
  NXPing();
  return self;
}

- levelSliding: sender
/*
  Change the text displayed below the level slider to indicate
  what the level means.
*/
{
  char buf[32];
  int moves = 0, minutes = 0;
  
  Level = [levelSlider intValue];
  switch (Level){
    case 1 : moves = 60; minutes = 5; break;
    case 2 : moves = 60; minutes = 15; break;
    case 3 : moves = 60; minutes = 30; break;
    case 4 : moves = 40; minutes = 30; break;
    case 5 : moves = 40; minutes = 60; break;
    case 6 : moves = 40; minutes = 120; break;
    case 7 : moves = 40; minutes = 240; break;
    case 8 : moves = 1; minutes = 15; break;
    case 9 : moves = 1; minutes = 60; break;
    case 10 : moves = 1; minutes = 600; break;
  }
  if( moves > 1 )
    sprintf( buf, "%d moves in %d minutes", moves, minutes );
  else
    sprintf( buf, "%d move in %d minutes", moves, minutes );
  [levelText setStringValue: buf];
  return self;
}

- setPreferences: sender
/*
  Actually set the preferences.
*/
{
  int whiteSide = [whiteSideMatrix selectedRow];
  int blackSide = [blackSideMatrix selectedRow];
  int change = NO;
  int button;
  
  
  button = NXRunAlertPanel( 0,
  	"Start a new game with these Preferences?", "Yes", "No", 0
  );
  switch( (Level = [levelSlider intValue]) ){
    case 1 : pref.time_control_moves = 60; pref.time_control_minutes = 5; break;
    case 2 : pref.time_control_moves = 60; pref.time_control_minutes = 15; break;
    case 3 : pref.time_control_moves = 60; pref.time_control_minutes = 30; break;
    case 4 : pref.time_control_moves = 40; pref.time_control_minutes = 30; break;
    case 5 : pref.time_control_moves = 40; pref.time_control_minutes = 60; break;
    case 6 : pref.time_control_moves = 40; pref.time_control_minutes = 120; break;
    case 7 : pref.time_control_moves = 40; pref.time_control_minutes = 240; break;
    case 8 : pref.time_control_moves = 1; pref.time_control_minutes = 15; break;
    case 9 : pref.time_control_moves = 1; pref.time_control_minutes = 60; break;
    case 10 : pref.time_control_moves = 1; pref.time_control_minutes = 600; break;
  }
  
  if( whiteSide == 0 && blackSide == 0 ){
    pref.bothsides = 1;
    pref.opponent = white;
    pref.computer = black;
  }
  else if( whiteSide == 1 && blackSide == 0 ){
    pref.bothsides = 0;
    pref.opponent = white;
    pref.computer = black;
  }
  else if( whiteSide == 0 && blackSide == 1 ){
    pref.bothsides = 0;
    pref.opponent = black;
    pref.computer = white;
  }
  else
    chess_debug(( "Unsupported combination\n" ));
    
  pref.white_name = (char *)[whiteName stringValue];
  pref.black_name = (char *)[blackName stringValue];
  if ( button == NX_ALERTDEFAULT ) {
      [self newGame: self];
  }
  return self;
}

- usePreferences
/*
  Use current preferences
*/
{
  TCmoves = pref.time_control_moves;
  TCminutes = pref.time_control_minutes;
  TCflag = (TCmoves > 1);
  SetTimeControl();
  
  bothsides = pref.bothsides;
  opponent = pref.opponent;
  computer = pref.computer;
  if( bothsides ){
    [startButton setEnabled: YES];
    [startButton display];
  }
  else{
    [startButton setEnabled: NO];
    [startButton display];
  }
    
  [self setWhiteName: pref.white_name blackName: pref.black_name];
  return self;
}

- openGame: sender
/*
  Read a saved game.
*/
{
  id op = [OpenPanel new];
  const char *types[2];
  
  types[0] = "chess";
  types[1] = 0;
  finished = 0;
  [op setRequiredFileType: "chess"];
  if( [op runModalForTypes: types] ){
    if( filename )
      free( filename );
    filename = (char *)malloc( strlen( [op filename] ) + 1 );
    strcpy( filename, [op filename] );
    GetGame( filename );
  }
  return self;
}
  
- saveAsGame: sender
/*
  Save a game.
*/
{
  id sp = [SavePanel new];
  
  [sp setRequiredFileType: "chess"];
  if( [sp runModal] ){
    if( filename )
      free( filename );
    filename = (char *)malloc( strlen( [sp filename] ) + 1 );
    strcpy( filename, [sp filename] );
    SaveGame( filename );
  }
  return self;
}

- saveGame: sender
{
  if( filename )  
    SaveGame( filename );
  else
    [self saveAsGame: sender];
  return self;
}
  
- listGame: sender
/*
  Save a game.
*/
{
  id sp = [SavePanel new];
  [sp setRequiredFileType: 0];
  if( [sp runModal] )
    ListGame( [sp filename] );
  return self;
}

- newGame: sender
/*
  Start a new game.
*/
{
  forceCount = hintCount = undoCount = 0;
  finished = 0;
  NewGame();
  [self setTitle];
  [self usePreferences];
  if( bothsides ){
    [startButton setEnabled: YES];
    [startButton display];
  }
  else{
    [startButton setEnabled: NO];
    [startButton display];
     if( computer == white && !mate && !quit )
      [self selectMove: computer iop: 1];
  }
  return self;
}

- chooseSide: sender
/*
  Set the text fields next to the side matrices.
*/
{
  int whiteSide = [whiteSideMatrix selectedRow];
  int blackSide = [blackSideMatrix selectedRow];
  
  if( whiteSide == 1 && blackSide == 0 ){
    [whiteName setStringValue: pwen->pw_gecos];
    [blackName setStringValue: "Computer"];
  }
  else if( whiteSide == 0 && blackSide == 1 ){
    [blackName setStringValue: pwen->pw_gecos];
    [whiteName setStringValue: "Computer"];
  }
  else if( whiteSide == 1 && blackSide == 1 ){
    [blackName setStringValue: pwen->pw_gecos];
    [whiteName setStringValue: pwen->pw_gecos];
  }
  else{
    [blackName setStringValue: "Computer"];
    [whiteName setStringValue: "Computer"];
  }
  return self;
}


-forceMove: sender
{
  timeout = true;
  return self;
}

BOOL playing = NO;

    
- startGame: sender
{
  id menuList = [[self mainMenu] itemList];
  int i, r, c;
  
  [menuList getNumRows: &r numCols: &c];
  if( [sender state] == 1 && playing == NO ){
    quit = false;
    playing = YES;
    
    /* This is the loop that makes the computer play.  It can be terminated */
    /* by several conditions.  The "Stop" button may be clicked, cmd-. may  */
    /* be pressed, or the game may end. */
    while( !mate && !quit && !finished && playing )
      [self selectMove: player iop: 1];
    quit = true;
    timeout = true;
    playing = NO;
    for( i = 0; i < r; i++ )
      [[menuList cellAt: i :0] setEnabled: YES];
    [[self mainMenu] display];
    [sender setState: 0];
  }
  else if( [sender state] == 0 && playing == YES ){
    quit = true;
    timeout = true;
    playing = NO;
    for( i = 0; i < r; i++ )
      [[menuList cellAt: i :0] setEnabled: YES];
    [[self mainMenu] display];
  }
  return self;
}

- hint: sender
/*
  Give the player a hint.
*/
{
  short from = hint>>8;
  short to = hint & 0xff;
  int row, col;
  
  if( hint ){
    algbr( (short)(hint>>8), (short)(hint & 0xff), false );
    chess_debug(( "hint:  %s\n", mvstr1 ));
    hintCount++;
    row = (int)(from / 8);
    col = (int)(from % 8);
    [gameBoard highlightSquareAt: row : col];
    row = (int)(to / 8);
    col = (int)(to % 8);
    [gameBoard highlightSquareAt: row : col];
    [self setTitle];
  }
  else
    NXRunAlertPanel( 0, "I don't have a hint.", 0, 0, 0 );
  return self;
}

- undoMove: sender
/*
  Undo last two half moves.
*/
{
  if( GameCnt >= 0 ){
    Undo(); Undo();
    undoCount++;
    // mate = false;	/* Glenn Reid (NeXT) Mon Apr 16 00:39:35 PDT 1990 */
    [self setTitle];
  }
  else
    NXRunAlertPanel( 0, "No more moves to undo.", 0, 0, 0 );
  return self;
}

- info: sender
{
  char *copyright;
  int copy_size;
  NXStream *copy_stream;
  id text;
  
  copyright = (char *)getsectdata( "__COPYRIGHT", "COPYING", &copy_size);
  if( copyright != NULL ){
    copy_stream = NXOpenMemory( copyright, copy_size, NX_READONLY );
    text = [infoScroll docView];
    [text readText: copy_stream];
    [text sizeToFit];
    [text setFont: [Font newFont: "Times" size: 14.0]];
    [infoScroll display];
  }
  [infoPanel makeKeyAndOrderFront: sender];
  return self;
}



/*
  Support methods.
*/

- setFinished: (int) f { finished = f; [self finishedAlert]; return self; }
- (int)finished { return finished; }
- (int)bothsides { return pref.bothsides; }
- storePosition: (int) row : (int) col {
    currentRow = row;
    currentCol = col;
  return self;
}
- showPosition: sender
{
    [gameBoard highlightSquareAt: currentRow : currentCol];
    [gameBoard flashSquareAt: currentRow : currentCol];
    return self;
}

- finishedAlert
{
  printf( "opponent %d, computer %d\n", opponent, computer );
  switch( finished ){
  case DRAW_GAME:
    NXRunAlertPanel( 0, "The game is a draw.", 0, 0, 0 );
    break;
  case WHITE_MATE:
    NXRunAlertPanel( 0, "The game is over.  Black wins.", 0, 0, 0 );
    break;
  case BLACK_MATE:
    NXRunAlertPanel( 0, "The game is over.  White wins.", 0, 0, 0 );
    break;
  case OPPONENT_MATE:
    NXRunAlertPanel( 0, "The game is over.\nCongratulations, you win.", 0, 0, 0 );
    break;
  default:
    break;
  }
  return self;
}

- (int)makeMoveFrom: (int)r1 : (int)c1 to: (int)r2 : (int)c2
{ 
  unsigned short mv;
  char *move;
  short oldguy, newguy;
  
  oldguy = [gameBoard typeAt: r1 : c1];
  move = convert_rc( r1, c1, r2, c2, [gameBoard typeAt: r1 : c1] );
  player = opponent;
  if( !VerifyMove( move, 0, &mv ) ){
    ShowMessage( "Illegal move" );
    NXBeep();
    return( 0 );
  }
  else {
    /* if your pawn is promoted to queen and then you Undo, the queen
       should turn back into a pawn.
       Glenn Reid (NeXT) Mon Apr 16 00:49:26 PDT 1990
    */
    newguy = [gameBoard typeAt: r2 : c2];
    if ( (newguy == queen) && (oldguy == pawn) ) {
	fprintf ( stderr, "pawn becomes queen...\n" );
	GameQueens[GameCnt] = oldguy;
    } else {
	GameQueens[GameCnt] = 0;
    }
    chess_debug(( "opponent move time %d move %s\n",
       GameList[GameCnt].time, move ));
    [self updateClocks: opponent];
    InCheck();
    NXPing();
    [gameBoard layoutBoard: board color: color];
    [gameBoard display];
    NXPing();
    Sdepth = 0;
    ft = 0;
    if( !(quit || mate || force) )
      [self selectMove: computer iop: 1];
  }
 return( 1 );
}

- selectMove: (int)side iop: (int)iop
{
  id menuList = [[self mainMenu] itemList];
  int i, r, c;
  
  if( side == black ) {
    ShowMessage( "Black's move" );
  } else {
    ShowMessage( "White's move" );
  }
    
  [forceButton setEnabled: YES];
  [forceButton display];
  NXPing();
  
  [menuList getNumRows: &r numCols: &c];
  for( i = 0; i < r-1; i++ )
    [[menuList cellAt: i :0] setEnabled: NO];
  [[self mainMenu] display];
  if( !bothsides )
    [setButton setEnabled: NO];
  [gameBoard setEnabled: NO];
  
  bzero( &move_info, sizeof( struct MoveInfo ) );
  move_info.iop = iop;
  move_info.side = side;
  Sdepth = 0;
  SelectMoveStart( &move_info );

  while (!timeout && Sdepth < MaxSearchDepth)
    SelectLoop( &move_info );

  [self selectMoveEnd];
  
  [self setTitle];
  InCheck();
  NXPing();
  return self;
}

- selectMoveEnd
{
  id menuList = [[self mainMenu] itemList];
  int i, r, c;
  
  SelectMoveEnd( &move_info );
  [self updateClocks: computer];
  chess_debug(("computer move time %d, move %s\n",
       GameList[GameCnt].time, mvstr1 ));
  InCheck();
  NXPing();

  [menuList getNumRows: &r numCols: &c];
  if( !bothsides )
    for( i = 0; i < r; i++ )
      [[menuList cellAt: i :0] setEnabled: YES];
  [[self mainMenu] display];
  [setButton setEnabled: YES];
  [gameBoard setEnabled: YES];
  NXPing();

  [forceButton setEnabled: NO];
  [forceButton display];
  NXPing();
  

  return self;
}



- setWhiteName: (char *)wname blackName: (char *)bname
{
  [whiteClockText setStringValue: wname];
  [blackClockText setStringValue: bname];
  if( [whiteSideMatrix selectedRow] == 1 ){
    [whiteName setStringValue: wname];
  }
  else{
    [whiteName setStringValue: COMPUTER];
  }
  if( [blackSideMatrix selectedRow] == 1 ){
    [blackName setStringValue: bname];
  }
  else{
    [blackName setStringValue: COMPUTER];
  }
  return self;
}

- (int)whiteTime { return wtime; }
- (int)blackTime { return btime; }
- updateClocks: (int)side
{
  if( !blackClock || !whiteClock )
    return self;
    
  if( side == white ){
    wtime += GameList[GameCnt].time;
    if( [clockWindow isVisible] ){
      [whiteClock setSeconds: wtime];
      [whiteClock display];
    }
  }
  else{
    btime += GameList[GameCnt].time;
    if( [clockWindow isVisible] ){
      [blackClock setSeconds: wtime];
      [blackClock display];
    }
  }
  return self;
}

- updateClocks: (int)side seconds: (int) seconds
{
  if( side == white ){
    if( [clockWindow isVisible] ){
      [whiteClock setSeconds: wtime];
      [whiteClock display];
    }
  }
  else{
    if( [clockWindow isVisible] ){
      [blackClock setSeconds: wtime];
      [blackClock display];
    }
  }
  return self;
}


- setTitle
/*
  Change the board windows title to display the number of cheat commands
  issued.
*/
{
  char buf[64];
  if( undoCount || hintCount ){
    sprintf( buf, "Chess:  " );
    if( undoCount ){
      if( undoCount == 1 )
	sprintf( buf+strlen(buf), "1 Undo  " );
      else
	sprintf( buf+strlen(buf), "%d Undos  ", undoCount );
    }
    if( hintCount ){
      if( hintCount == 1 )
	sprintf( buf+strlen(buf), "1 Hint" );
      else
	sprintf( buf+strlen(buf), "%d Hints", hintCount );
    }
  }
  else
    sprintf( buf, "Chess" );
  [boardWindow setTitle: buf];
  return self;
}

- setTitleMessage: (char *)m
{
  char *om;
  char buf[128];
  
  [self setTitle];
  om = (char *)[boardWindow title];
  strcpy( buf, om );
  strcat( buf, "   :   " );
  strcat( buf, m );
  [boardWindow setTitle: buf];
  return self;
}

@end
