#import <c.h>

#include <sys/types.h>
#include <sys/time.h>
#import <appkit/appkit.h>

#import "chess_strings.h"
#import "Chess.h"
#import "gnuchess.h"
#import "Board3D.h"
#import "Square3D.h"

#define PIECE_WIDTH_3D 55
#define BACK_STORE_WIDTH 80
#define PIECE_HEIGHT_3D 95
#define BACK_STORE_HEIGHT 110

extern PSWtrapezoid ();

/*  Each set of points describes the vertical lines along the board. */
struct NXLine {
  NXPoint a,b;
};

#define BASE_X 89
struct NXLine vertical[] = {
  {{BASE_X,122},{BASE_X+61,467}},
  {{BASE_X+71,122},{BASE_X+111,467}},
  {{BASE_X+135,122},{BASE_X+162,467}},
  {{BASE_X+200,122},{BASE_X+212,467}},
  {{BASE_X+265,122},{BASE_X+265,467}},
  {{BASE_X+330,122},{BASE_X+316,467}},
  {{BASE_X+393,122},{BASE_X+367,467}},
  {{BASE_X+459,122},{BASE_X+419,467}},
  {{BASE_X+524,122},{BASE_X+469,467}}
};

/* Each coordinate describes the y value of each line of the board. */
#define BASE_Y  132

NXCoord horizontal[] = {
  BASE_Y, BASE_Y+54, BASE_Y+106, BASE_Y+153,
  BASE_Y+196, BASE_Y+237, BASE_Y+277, BASE_Y+312, BASE_Y+345
};

id pieces3D;

void convert_point( NXPoint *p, int *r, int *c );

@implementation Board3D

+ newFrame: (const NXRect *)f
{
  int r, c, i;
  
  self = [super newFrame: f];
  [self allocateGState];
  background = [Bitmap newFromMachO: "3d_board.tiff"];
  [background setFlip: NO];
  pieces3D = [Bitmap newFromMachO: "3d_pieces.tiff"];
  [pieces3D setFlip: NO];
  backBitmap = [Bitmap newSize: BACK_STORE_WIDTH: BACK_STORE_HEIGHT
  		type: NX_UNIQUEBITMAP];
  [backBitmap setFlip: NO];
  for( r = 0; r < 8; r++ )
    for( c = 0; c < 8; c++ )
      square[r][c] = [Square3D new];
  [self setupPieces];
  return self;
}

- setBackgroundBitmap: sender
{
  background = sender;
  [background setFlip: NO];
}

- setupPieces
{
  [self layoutBoard: Stboard color:Stcolor];
}

- layoutBoard: (short *)b color: (short *)c
{
  int sq, row, col;
  
  PSgsave();
  PSrotate ( 10.0 );
  for (sq = 0; sq < 64; sq++){
    row = sq / 8;
    col = sq % 8;
    [self placePiece: b[sq] at: row: col color: c[sq]];
  }
  PSgrestore();
}

squareOrigin( int r, int c, NXCoord *x, NXCoord *y )
{
  NXCoord dx,m,b;
  
  *y = horizontal[r];
  dx = (vertical[c].a.x-vertical[c].b.x);
  m = (vertical[c].a.y - vertical[c].b.y) / dx;
  b = vertical[c].b.y - (m*vertical[c].b.x);
  *x = dx ? (horizontal[r] - b)/m : vertical[c].a.x;
}

squareBounds( int r, int c, NXPoint *p1, NXPoint *p2, NXPoint *p3, NXPoint *p4 )
/*
   (p2)----(p4)
    |        |
    |        |
   (p1)----(p3)
*/
{
  NXCoord dx,m,b;
  
  dx = (vertical[c].a.x-vertical[c].b.x);
  m = (vertical[c].a.y - vertical[c].b.y) / dx;
  b = vertical[c].b.y - (m*vertical[c].b.x);
  p1->y = floor( horizontal[r] );
  p1->x = floor( dx ? (horizontal[r] - b)/m : vertical[c].a.x );

  p2->y = floor( horizontal[r+1] );
  p2->x = floor( dx ? (horizontal[r+1] - b)/m : vertical[c].a.x );
  
  dx = (vertical[c+1].a.x-vertical[c+1].b.x);
  m = (vertical[c+1].a.y - vertical[c+1].b.y) / dx;
  b = vertical[c+1].b.y - (m*vertical[c+1].b.x);
  p3->y = floor( horizontal[r] );
  p3->x = floor( dx ? (horizontal[r] - b)/m : vertical[c+1].a.x );

  p4->y = floor( horizontal[r+1] );
  p4->x = floor( dx ? (horizontal[r+1] - b)/m : vertical[c+1].a.x );
}
  

- slidePieceFrom: (int)r1 : (int)c1 to: (int)r2 : (int)c2
{
  NXPoint p, backP, centerP, endP;
  NXRect backR;
  int controlGState, type, color, increments, dr, dc, i;
  NXRect newLocation, oldLocation;
  NXCoord incX, incY;
  
  [self lockFocus];
  type = [square[r1][c1] type];
  color = [square[r1][c1] colorVal];
  oldLocation = *[square[r1][c1] location];
  if( !type )
    return self;
    
  squareOrigin( r2, c2, &endP.x, &endP.y );
  
  /* Remove piece and then save background */ 
  [square[r1][c1] setType: 0 color: 0];
  [self drawSelf: 0 : 0];

  squareOrigin( r1, c1, &backP.x, &backP.y );
  controlGState = [self gState];
  [backBitmap lockFocus];
  PSgsave();
  PScomposite( backP.x, backP.y, BACK_STORE_WIDTH, BACK_STORE_HEIGHT,
	       controlGState, 
	       0.0, 0.0, NX_COPY);
  PSgrestore();
  [backBitmap unlockFocus];

  [square[r1][c1] setType: type color: color];
  [square[r1][c1] drawInside: 0 inView: self];
  [square[r1][c1] setMoving: YES];
  PSflushgraphics();
  NXPing();

  incX = endP.x - backP.x;
  incY = endP.y - backP.y;
  increments = (int) MAX( ABS(incX), ABS(incY) ) / 7;	// was 5 gcr
  incX = incX / increments;
  incY = incY / increments;
  for( i = 0; i < increments; i++ ){
    /* Restore old background */
    [self lockFocus];
    [backBitmap composite:NX_COPY toPoint: &backP];
    [self unlockFocus];
    
    backP.x += incX;
    backP.y += incY;
    convert_point( &backP, &dr, &dc );

    /* Save new background */
    [backBitmap lockFocus];
//    PSgsave();
    PScomposite( backP.x, backP.y,
		 BACK_STORE_WIDTH, BACK_STORE_HEIGHT,
		 controlGState, 
		 0.0, 0.0, NX_COPY);
//    PSgrestore();
    [backBitmap unlockFocus];
    
    /* Draw piece at new location. */
    [square[r1][c1] setRow: dr];
    newLocation.origin = backP;
    newLocation.size.width = PIECE_WIDTH_3D;
    newLocation.size.height = PIECE_HEIGHT_3D;
    [square[r1][c1] setLocation: &newLocation];
    [square[r1][c1] drawInside: 0 inView: self];

    PSflushgraphics();
    NXPing();
  }
  
  [square[r1][c1] setMoving: NO];

  [self unlockFocus];
}


- placePiece: (int)p at: (int)r : (int)c color: (int)col
{
  NXRect l;
  NXCoord m,b,dx,x;

  [square[r][c] setType: p color: col];
  [square[r][c] setRow: r];
  l.origin.y = horizontal[r];
  dx = (vertical[c].a.x-vertical[c].b.x);
  m = (vertical[c].a.y - vertical[c].b.y) / dx;
  b = vertical[c].b.y - (m*vertical[c].b.x);
  x = dx ? (horizontal[r] - b)/m : vertical[c].a.x;
  l.origin.x = x;

  dx = (vertical[c+1].a.x-vertical[c+1].b.x);
  m = (vertical[c+1].a.y - vertical[c+1].b.y) / dx;
  b = vertical[c+1].b.y - (m*vertical[c+1].b.x);
  x = dx ? (horizontal[r] - b)/m : vertical[c+1].a.x;
  l.size.width = x - l.origin.x;
  l.size.height = 99999;
  [square[r][c] setLocation: &l];
}

- (int)typeAt: (int)r : (int)c
{
  return( (r>=0 && c>=0) ? [square[r][c] type] : 0 );
}
  
- (int)colorAt: (int)r : (int)c
{
  return( (r>=0 && c>=0) ? [square[r][c] colorVal] : 0 );
}
  
- drawRows: (int)r from: (int)c
{
  while( r >= 0 ){
    if( [self typeAt: r : c] && ![square[r][c] moving] )
      [square[r][c] drawInside: 0 inView: self];
    r--;
  }
}
    
- unhighlightSquareAt: (int)r : (int) c
{
  NXPoint p1,p2,p3,p4,to;
  NXRect backR;
  
  squareBounds( r, c, &p1, &p2, &p3, &p4 );
  p1.x = p1.x - 3;
  p1.y = p1.y - 3;
  p2.x = p2.x - 3;
  p2.y = p2.y + 3;
  p3.x = p3.x + 3;
  p3.y = p3.y - 3;
  p4.x = p4.x + 3;
  p4.y = p4.y + 3;
  to.y = p1.y;
  to.x = MIN(p1.x,p2.x);
  backR.origin = to;
  backR.size.width = MAX(p3.x,p4.x) - to.x;
  backR.size.height = p2.y - p1.y;
  
  [self lockFocus];
  
  PSgsave();
  PSsetlinewidth( 3.0 );
  PSsetgray( 1.0 );
  PSnewpath();
  PSmoveto( p1.x, p1.y );
  PSlineto( p2.x, p2.y );
  PSlineto( p4.x, p4.y );
  PSlineto( p3.x, p3.y );
  PSlineto( p1.x, p1.y );
  PSclosepath;
  PSclip();
  [background composite: NX_COPY fromRect: &backR toPoint: &to];
  PSgrestore();
  if( [self typeAt:r :c] || ( r > 0 && [self typeAt:r-1 :c]) )
    [self drawRows: r from: c];
  PSflushgraphics();
  NXPing();
  
  [self unlockFocus];
}

- highlightSquareAt: (int)r : (int) c
{
  struct timeval timeout;
  NXPoint p1,p2,p3,p4;
  short idx;
  float color = 0.0;
  
  squareBounds( r, c, &p1, &p2, &p3, &p4 );
  [self lockFocus];

  PSgsave();
  PSsetlinewidth( 3.0 );
  PSmoveto( p1.x, p1.y );
  PSlineto( p2.x, p2.y );
  PSlineto( p4.x, p4.y );
  PSlineto( p3.x, p3.y );
  PSlineto( p1.x, p1.y );
  PSclosepath();
  /* flash 2 times */
  for ( idx=1; idx <= 3; idx++ ) {
      color = 1.0 - color;
      PSsetgray( color );
      PSgsave(); 
      PSstroke();
      PSgrestore();
      if( [self typeAt:r :c] || ( r > 0 && [self typeAt:r-1 :c]) )
	[self drawRows: r from: c];
      PSflushgraphics();
      NXPing();
      if( ![square[r][c] moving] ){ 
	timeout.tv_sec = 0;
	timeout.tv_usec = 15000;
	select( 0, 0, 0, 0, &timeout );
      }
  }
  PSgrestore();
 /****
  PSgsave();
  PSsetlinewidth( 3.0 );
  PSsetgray( 0.0 );
  PSnewpath();
  PSmoveto( p1.x, p1.y );
  PSlineto( p2.x, p2.y );
  PSlineto( p4.x, p4.y );
  PSlineto( p3.x, p3.y );
  PSlineto( p1.x, p1.y );
  PSclosepath;
  PSstroke();
  PSgrestore();
  if( [self typeAt:r :c] || ( r > 0 && [self typeAt:r-1 :c]) )
    [self drawRows: r from: c];
  PSflushgraphics();
  NXPing();
  if( ![square[r][c] moving] ){ 
    timeout.tv_sec = 0;
    timeout.tv_usec = 150000;
    select( 0, 0, 0, 0, &timeout );
  }

  PSgsave();
  PSsetlinewidth( 3.0 );
  PSsetgray( 1.0 );
  PSnewpath();
  PSmoveto( p1.x, p1.y );
  PSlineto( p2.x, p2.y );
  PSlineto( p4.x, p4.y );
  PSlineto( p3.x, p3.y );
  PSlineto( p1.x, p1.y );
  PSclosepath;
  PSstroke();
  PSgrestore();
  if( [self typeAt:r :c] || ( r > 0 && [self typeAt:r-1 :c]) )
    [self drawRows: r from: c];
  PSflushgraphics();
  NXPing();
  if( ![square[r][c] moving] ){ 
    timeout.tv_sec = 0;
    timeout.tv_usec = 150000;
    select( 0, 0, 0, 0, &timeout );
  }
  ******/
  [self unhighlightSquareAt: r : c];

  [self unlockFocus];
}

- flashSquareAt: (int)r : (int) c
{
  NXPoint p1,p2,p3,p4;
  
  squareBounds( r, c, &p1, &p2, &p3, &p4 );
  [self lockFocus];

  PSgsave();
  PSsetlinewidth( 3.0 );
  PSsetgray( 1.0 );
  PSnewpath();
  PSmoveto( p1.x, p1.y );
  PSlineto( p2.x, p2.y );
  PSlineto( p4.x, p4.y );
  PSlineto( p3.x, p3.y );
  PSlineto( p1.x, p1.y );
  PSclosepath;
  PSstroke();
  PSgrestore();
  if( [self typeAt:r :c] || ( r > 0 && [self typeAt:r-1 :c]) )
    [self drawRows: r from: c];
  [self unlockFocus];
}



- printPSCode: sender
{
  id pi = [NXApp printInfo];
  const NXRect *pr;
  NXCoord lm, rm, tm, bm;
  
  pr = [pi paperRect];
  lm = rm = (pr->size.width - frame.size.width) / 2.0;
  tm = bm = (pr->size.height - frame.size.height) / 2.0;
  [pi setMarginLeft: lm right: rm top: tm bottom: bm];
  
  [self lockFocus];
  NXSizeBitmap( &bounds, &print_size, &print_pwide, &print_phigh,
                &print_bps, &print_ssp, &print_config, &print_mask );
  print_image = (void *)malloc( print_size );
  NXReadBitmap( &bounds, print_pwide, print_phigh, print_bps, print_ssp,
	       print_config, print_mask, print_image, 0, 0, 0, 0 );
  [super printPSCode: self];
  free( print_image );
  [self unlockFocus];
  return self;
}

- drawSelf:(const NXRect *)f :(int)rectCount;
{
  int r, c;
  NXRect cr;
  NXPoint p;
  
  p.x = p.y = 0.0;
  if( NXDrawingStatus == NX_DRAWING ){
    PSgsave();
    
    [background composite: NX_COPY toPoint: &p];
    for( r = 7; r >= 0; r-- ){
      for( c = 7; c >= 0; c-- ){
	[square[r][c] drawSelf: &cr inView: self];
      }
    }
    PSgrestore();
  }
  else{
    NXImageBitmap( &bounds, print_pwide, print_phigh, print_bps, print_ssp,
                   print_config, print_mask, print_image, 0, 0, 0, 0 );
  }
}

- mouseDown: (NXEvent *)event
{
  NXPoint p, pickedP, backP, centerP;
  NXHandler handler;
  NXRect pieceRect, backR;
  int r, c, r2, c2, mask, controlGState, t, col;
  unsigned short mv;
  NXRect newLocation, oldLocation;
  NXCoord x, y;
  int hi_r = -1, hi_c = -1;

  if ( [NXApp bothsides] ) {
      NXBeep();
      return self;
  }

  if( [NXApp finished] ){
    [NXApp finishedAlert];
    handler.code = 0;
    [window addToEventMask: NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
    NX_DURING
      while (event->type != NX_MOUSEUP){
	event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
      }
    NX_HANDLER
      handler = NXLocalHandler;
    NX_ENDHANDLER
    return self;
  }
  
  pickedP = event->location;
  [self convertPoint:&pickedP fromView:nil];
  backP = pickedP;
  
  r = -1;
  c = -1;
  convert_point( &pickedP, &r, &c );
  if( r == -1 || c == -1 )
    return self;

  [self lockFocus];
  t = [square[r][c] type];
  col = [square[r][c] colorVal];
  oldLocation = *[square[r][c] location];
  if( t ){
    [square[r][c] setType: 0 color: 0];
    [self drawSelf: 0 : 0];

    [self flashSquareAt: r : c];
    hi_r = r; hi_c = c;
    
    /* Save background */ 
    squareOrigin( r, c, &x, &y );
    backP.x = x;
    backP.y = y;
    controlGState = [self gState];
    [backBitmap lockFocus];
    PSgsave();
    PScomposite( backP.x, backP.y, BACK_STORE_WIDTH, BACK_STORE_HEIGHT,
		 controlGState, 
		 0.0, 0.0, NX_COPY);
    PSgrestore();
    [backBitmap unlockFocus];

    [square[r][c] setType: t color: col];
    [square[r][c] drawInside: 0 inView: self];
    [square[r][c] setMoving: YES];
    PSflushgraphics();
    NXPing();
    
    pickedP.x = floor( pickedP.x - x );
    pickedP.y = floor( pickedP.y - y );
  }
 
  r2 = 0; c2 = 0;
  handler.code = 0;
  [window addToEventMask: NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
  NX_DURING
    while (event->type != NX_MOUSEUP){
      event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
      if( !t )
        continue;
	
      p = event->location;
      [self convertPoint:&p fromView:nil];

      /* Restore old background */
      [self lockFocus];
      [backBitmap composite:NX_COPY toPoint: &backP];
      [self unlockFocus];
      
      backP.x = p.x - pickedP.x;
      backP.y = p.y - pickedP.y;

      /* Un/highlight square */
      centerP.y = backP.y + PIECE_HEIGHT_3D/4.0;
      centerP.x = backP.x + PIECE_WIDTH_3D/2.0;
      convert_point( &centerP, &r2, &c2);
      if( r2 != hi_r || c2 != hi_c ){
        if( hi_r != -1 && hi_c != -1 )
	  [self unhighlightSquareAt: hi_r : hi_c];
        hi_r = r2; hi_c = c2;
	[self flashSquareAt: r2 : c2];
      }

      /* Save new background */
      [backBitmap lockFocus];
      PSgsave();
      PScomposite( backP.x, backP.y,
		   BACK_STORE_WIDTH, BACK_STORE_HEIGHT,
		   controlGState, 
		   0.0, 0.0, NX_COPY);
      PSgrestore();
      [backBitmap unlockFocus];
      
      /* Draw piece at new location. */
      [square[r][c] setRow: r2];
      newLocation.origin.x = p.x - pickedP.x;
      newLocation.origin.y = p.y - pickedP.y;
      newLocation.size.width = PIECE_WIDTH_3D;
      newLocation.size.height = PIECE_HEIGHT_3D;
      [square[r][c] setLocation: &newLocation];
      [square[r][c] drawInside: 0 inView: self];

      PSflushgraphics();
      NXPing();
    }
  NX_HANDLER
    handler = NXLocalHandler;
  NX_ENDHANDLER

  
  if( t ){
    [square[r][c] setMoving: NO];
    if( r2 != r || c2 != c ){
      if( ![NXApp makeMoveFrom: r : c to: r2 : c2] ){
        [square[r][c] setLocation: &oldLocation];
	[square[r][c] setType: t color: col];
	[square[r][c] setRow: r];
      }
    }
    else{
      [square[r][c] setLocation: &oldLocation];
      [square[r][c] setType: t color: col];
      [square[r][c] setRow: r];
    }
    [self display];
    PSflushgraphics();
    NXPing();
  }
  [self unlockFocus];
  
  if (handler.code) {
    NX_RAISE(handler.code, handler.data1, handler.data2);
  }
}


NXCoord
check_point( struct NXLine *l, NXPoint *p )
{
  NXCoord dx,dy,dx1,dy1;
  
  dx = l->a.x - l->b.x;
  dy = l->a.y - l->b.y;
  dx1 = p->x - l->a.x;
  dy1 = p->y - l->a.y;
  return( dx*dy1 - dy*dx1 );
}

void
convert_point( NXPoint *p, int *r, int *c )
{
  NXCoord dx,dy,dx1,dy1,m;
  int i;
  
  for( i = 0; i < 8; i++ )
    if( p->y >= horizontal[i] && p->y <= horizontal[i+1] ){
      *r = i;
      break;
    }
      
  for( i = 0; i < 8; i++ ){
    NXCoord m1,m2;
    
    m1 = check_point( &vertical[i], p );
    m2 = check_point( &vertical[i+1], p );
    if( m1 > 0 && m2 < 0 ){
      *c = i;
      break;
    }
  }
}  


@end
