/*
 * BoardView.h
 * Description:
 * 	a view that knows how to draw a game board for a two player game 
 *	with a grid board like checkers, chess, reversi or attaxx
 * History:
 *	14-Feb-93 Erik Kay - created
 */

#import "BoardView.h"

// the timed entry function for doing board animation when a move has happened
void animateMove (DPSTimedEntry te, double now, void *data)
{
    [(id)data animateMove];
}

// the timed entry function for doing board animation when some sort of change
// has happened, such as a piece capture
void animateChange (DPSTimedEntry te, double now, void *data)
{
    [(id)data animateChange];
}

@implementation BoardView

- initFrame:(const NXRect *)rect
{
    [super initFrame:rect];
    scalable = NO;
    squareSize = 52;
    pieceSize = squareSize - 4;
    highlightedRow = highlightedCol = selectedRow = selectedCol = -1;
    numAnimationFrames = 3;
    abortGame = NO;
    rows = cols = 0;
    curFrame = 0;
    direction = 1;
    return self;
}

// how many frames of animation for moves and changes
- (int)numAnimationFrames
{
    return numAnimationFrames;
}

// the various drawing methods for squares that are in various states

- drawSelectedSquare:(NXRect *)rect at:(int)col :(int)row
{
    NXDrawGrayBezel(rect,(NXRect *)0);
    return self;
}

- drawEmptySquare:(NXRect *)rect at:(int)col :(int)row
{
    NXDrawGroove(rect,(NXRect *)0);
    return self;
}

- drawHighlightedSquare:(NXRect *)rect at:(int)col :(int)row
{
    PSsetgray(0.333);
    NXRectFill(rect);
    return self;
}

- drawObstruction:(NXRect *)rect at:(int)col :(int)row
{
    PSsetgray(0.0);
    NXRectFill(rect);
    return self;
}

- drawPlayerOne:(NXRect *)rect at:(int)col :(int)row
{
    int width;
    width = NX_WIDTH(rect);
    PSsetgray(1.0);
    PSarc(NX_MIDX(rect),NX_MIDY(rect),(width-8)/2,
	0,360);
    PSfill();
    return self;
}

- drawPlayerTwo:(NXRect *)rect at:(int)col :(int)row
{
    int width;
    width = NX_WIDTH(rect);
    PSsetgray(0.0);
    PSarc(NX_MIDX(rect),NX_MIDY(rect),(width-8)/2,
	0,360);
    PSfill();
    return self;
}


// the feeder method to look at a square and decide which method should be
// called to draw it, and to then do the drawing
- drawSquare:(int)c :(int)r
{
    NXRect rect;

    NXSetRect(&rect,c * squareSize, r * squareSize, 
		squareSize, squareSize);

    // first, draw the square background (selected, highlighted, empty)
    if (c == selectedCol && r == selectedRow)
	[self drawSelectedSquare:&rect at:c :r];
    else if (c == highlightedCol && r == highlightedRow)
	[self drawHighlightedSquare:&rect at:c :r];
    else if ([board pieceAt:c :r] != SQUARE_ERROR)
	[self drawEmptySquare:&rect at:c :r];

    // now draw what's on top of the square(blocked, empty, player 1, player 2)
    switch ([board pieceAt:c :r]) {
	case SQUARE_BLOCKED:
	    [self drawObstruction:&rect at:c :r];
	    break;
	case SQUARE_ONE:
	    [self drawPlayerOne:&rect at:c :r];
	    break;
	case SQUARE_TWO:
	    [self drawPlayerTwo:&rect at:c :r];
	    break;
	case SQUARE_EMPTY:
	default:
	    break;
    }
    return self;
}

// compare the current board to the copy of the last board state we have
// only draw the squares that have changed
- drawSelf:(const NXRect *)rects :(int)count
{
    int r, c;
    
    if (!boardCopy) {
	PSsetgray(0.667);
	NXRectFillList(rects,count);
    }
    for (c = 0; c < cols; c++)
    	for (r = 0; r < rows; r++) {
	    if (!boardCopy || 
	    	([board pieceAt:c :r] != [boardCopy pieceAt:c :r]) ||
		((c == selectedCol) && (r == selectedRow)) ||
		((c == highlightedCol) && (r == highlightedRow)))
		    [self drawSquare:c :r];
	}
    if (boardCopy)
	[boardCopy free];
    boardCopy = [board trueCopy];
    return self;
}

// clear the boardCopy, then do a display.  This forces a complete redraw of
// the board
- redrawBoard
{
    if (boardCopy) {
	[boardCopy free];
	boardCopy = nil;
    }
    [self display];
    return self;
}

// get the mouseDown, figure out which square it's associated with, then
// pass the message on to the game delegate
- mouseDown:(NXEvent *)ev
{
    int col, row;
    [self convertPoint:&ev->location fromView:[[self superview] superview]];
	
    /* convert from view coords to grid coords */
    col = ev->location.x / squareSize;
    row = ev->location.y / squareSize;
    [game squareHit:col :row];
    return self;
}

// deselect the old selected square, and select the new one
// set both of the locations to be a SQUARE_ERROR to force an update
// of both of those squares in the display
- selectSquare:(int)col :(int)row
{
    [boardCopy setPiece:SQUARE_ERROR at:selectedCol :selectedRow];
    selectedCol = col;
    selectedRow = row;
    [boardCopy setPiece:SQUARE_ERROR at:selectedCol :selectedRow];
    return self;
}

// set up the move animation timed entry
- doMoveAnimation:(move *)mv
{
    if (abortGame) {
    	if (moveEntry) {
	    DPSRemoveTimedEntry(moveEntry);
	    moveEntry = NULL;
	}
	abortGame = NO;
    }
    currentMove = *mv;
    board->currentMove = *mv;
    [self selectSquare:mv->from.col :mv->from.row];
    moveEntry = DPSAddTimedEntry(0.1,animateMove,
    				(void *)self,NX_BASETHRESHOLD);
    [self display];
    return self;
}

// what gets called by the move animation timed entry
// this decides the state of things, and then actually calls the doAnimateMove
- animateMove
{
    if (abortGame) {
    	curFrame = 0;
	DPSRemoveTimedEntry(moveEntry);
	abortGame = NO;
	return self;
    }
    [self doAnimateMove];
    curFrame++;
    if (curFrame >= numAnimationFrames) {
    	curFrame = 0;
	DPSRemoveTimedEntry(moveEntry);
	[self finishMoveAnimation];
    }
    return self;
}

// actually do the animation for a move
- doAnimateMove
{
    if (highlightedCol > -1) {
	[boardCopy setPiece:SQUARE_ERROR at:highlightedCol :highlightedRow];
    	highlightedCol = highlightedRow = -1;
    } else {
	highlightedCol = currentMove.to.col;
	highlightedRow = currentMove.to.row;
	[boardCopy setPiece:SQUARE_ERROR at:highlightedCol :highlightedRow];
    }
    [self display];
    return self;
}

// move animation is finished!  clean up and tell the delegate
- finishMoveAnimation
{
    moveEntry = NULL;
    highlightedRow = highlightedCol = -1;
    [self selectSquare:-1 :-1];
    [self display];
    [game moveAnimationFinished:&currentMove];
    return self;
}

// set up the change animation timed entry
// by default, this does nothing
- doChangeAnimation:(Board *)orig
{
    [self finishChangeAnimation];
    return self;
}

// what gets called by the move animation timed entry
// this decides the state of things, and then actually calls the doAnimateMove
- animateChange
{
    if (abortGame) {
    	curFrame = 0;
	DPSRemoveTimedEntry(changeEntry);
	abortGame = NO;
	return self;
    }
    [self doAnimateChange];
    if (direction == 1) {
	curFrame++;
	if (curFrame >= numAnimationFrames) {
	    direction = -1;
	}
    } else {
	curFrame--;
	if (curFrame < 0) {
	    curFrame = 0;
	    direction = 1;
	    DPSRemoveTimedEntry(changeEntry);
	    [self finishChangeAnimation];
	}
    }
    return self;
}

// the actual change animation.  it does nothing by default
- doAnimateChange
{
    return self;
}

// change animation is finished!  clean up and tell the delegate
- finishChangeAnimation
{
    changeEntry = NULL;
    [game changeAnimationFinished];
    return self;
}

// set the game for this board (our delegate!)
- setGame:g
{
    game = g;
    return self;
}

// the board we're using
- (Board *)board
{
    return board;
}

// when the board is changed, we need to re-organize a bit
// (i.e. resize window if necessary, redraw things, etc)
- setBoard:(Board *)b
{
    int r,c;
    NXRect newfrm, f, contentR;
    NXSize newSize;

    board = b;
    r = [b rows];
    c = [b cols];
    highlightedRow = highlightedCol = selectedRow = selectedCol = -1;
    rows = r; cols = c;
    if (!scalable) { // if not scalable, then resize the view/window
	NXSetRect(&newfrm, NX_X(&frame), NX_Y(&frame),
		squareSize * cols, squareSize * rows);
	[[window contentView] getFrame:&contentR];
	newSize.width = (NX_WIDTH(&newfrm) + 
			    (NX_WIDTH(&contentR) - NX_WIDTH(&frame)));
	newSize.height = (NX_HEIGHT(&newfrm) + 
			    (NX_HEIGHT(&contentR) - NX_HEIGHT(&frame)));
	[window sizeWindow:newSize.width :newSize.height];
	[self setFrame:&newfrm];
	[[[self window] contentView] getFrame:&f];
    } else {
	if (NX_WIDTH(&frame) > NX_HEIGHT(&frame))
	    squareSize = NX_HEIGHT(&frame) / rows;
	else
	    squareSize = NX_WIDTH(&frame) / cols;
	pieceSize = squareSize - 4;
    }
    //! this is kind of weird! I'm not sure why the origin is so messed up
    //! here.  It seems to be coming out as (1,9) rather than (0,0)
    NX_X(&f) = 0;
    NX_Y(&f) = 0;
    [[window contentView] lockFocus];
    PSsetgray(0.667);
    NXRectFill(&f);
    [[window contentView] unlockFocus];
    [window flushWindow];
    [boardCopy free];
    boardCopy = nil;
    [self display];
    return self;
}

- (int)selectedRow
{
    return selectedRow;
}

- (int)selectedCol
{
    return selectedCol;
}

- resetBoardView
{
    abortGame = YES;
    return self;
}

- gameOver
{
    return self;
}

- setScalable:(BOOL)f
{
    scalable = f;
    return self;
}

@end
