#import <appkit/Application.h>
#import <appkit/NXImage.h>
#import <appkit/Window.h>
#import <dpsclient/wraps.h>
#import "Piece.h"
#import "TetMatrix.h"

// The number of pixels to move a block at once when it is dropped.
//static const float ANM_DELTA = 12.0;
static const float ANM_DELTA = 28.0;

extern long random();
extern void srandom(int seed);
extern int getpid();

@implementation Piece

#define PIECE_NULL	(unsigned char *)0;
#define SHAPE_NULL	(struct shape *)0;
const int NUM_ROTATIONS = 4;
const int NUM_SHAPES=7;

#define XMIN_BOUNDS(BOUNDS)	((BOUNDS & 0xc0) >> 6)
#define YMIN_BOUNDS(BOUNDS)	((BOUNDS & 0x30) >> 4)
#define XMAX_BOUNDS(BOUNDS)	((BOUNDS & 0xc) >> 2)
#define YMAX_BOUNDS(BOUNDS)	(BOUNDS & 0x3)

- (void) setShapes
{
	/*  ** 1st shape ** */

	shapes[0].table[0][0] = 0x0;
	shapes[0].table[0][1] = 0xf;	// ####
	shapes[0].table[0][2] = 0x0;
	shapes[0].table[0][3] = 0x0;     //  x,y  x1,y1
	shapes[0].bounds[0] = 0x2e;		// (0, 2, 3, 2) 3*4 + 2 = 14 = 0xe 
	shapes[0].points[0] = 10;			// This piece is worth 10 points

	shapes[0].table[1][0] = 0x4;	//  #
	shapes[0].table[1][1] = 0x4;	//  #
	shapes[0].table[1][2] = 0x4;	//  #
	shapes[0].table[1][3] = 0x4;	//  #
	shapes[0].bounds[1] = 0x47;		// (1, 0, 1, 3)
	shapes[0].points[1] = 16;

	shapes[0].table[2][0] = 0x0;
	shapes[0].table[2][1] = 0xf;	// ####
	shapes[0].table[2][2] = 0x0;
	shapes[0].table[2][3] = 0x0;
	shapes[0].bounds[2] = 0x2e;		// (0, 2, 3, 2)
	shapes[0].points[2] = 10;

	shapes[0].table[3][0] = 0x4;	//  #
	shapes[0].table[3][1] = 0x4;	//  #
	shapes[0].table[3][2] = 0x4;	//  #
	shapes[0].table[3][3] = 0x4;	//  #
	shapes[0].bounds[3] = 0x47;		// (1, 0, 1, 3)
	shapes[0].points[3] = 16;


	/* **  2nd shape ** */

	shapes[1].table[0][0] = 0xc;	// ##
	shapes[1].table[0][1] = 0xc;	// ##
	shapes[1].table[0][2] = 0x0;
	shapes[1].table[0][3] = 0x0;
	shapes[1].bounds[0] = 0x27;		// (0, 2, 1, 3)
	shapes[1].points[0] = 12;

	shapes[1].table[1][0] = 0xc;	// ##
	shapes[1].table[1][1] = 0xc;	// ##
	shapes[1].table[1][2] = 0x0;
	shapes[1].table[1][3] = 0x0;
	shapes[1].bounds[1] = 0x27;		// (0, 2, 1, 3)
	shapes[1].points[1] = 12;

	shapes[1].table[2][0] = 0xc;	// ##
	shapes[1].table[2][1] = 0xc;	// ##
	shapes[1].table[2][2] = 0x0;
	shapes[1].table[2][3] = 0x0;
	shapes[1].bounds[2] = 0x27;		// (0, 2, 1, 3)
	shapes[1].points[2] = 12;

	shapes[1].table[3][0] = 0xc;	// ##
	shapes[1].table[3][1] = 0xc;	// ##
	shapes[1].table[3][2] = 0x0;
	shapes[1].table[3][3] = 0x0;
	shapes[1].bounds[3] = 0x27;		// (0, 2, 1, 3)
	shapes[1].points[3] = 12;


	/* ** 3rd shape ** */

	shapes[2].table[0][0] = 0x4;	//  #
	shapes[2].table[0][1] = 0xe;	// ###
	shapes[2].table[0][2] = 0x0;
	shapes[2].table[0][3] = 0x0;
	shapes[2].bounds[0] = 0x2b;		// (0, 2, 2, 3)
	shapes[2].points[0] = 10;

	shapes[2].table[1][0] = 0x4;	//  #
	shapes[2].table[1][1] = 0x6;	//  ##
	shapes[2].table[1][2] = 0x4;	//  #
	shapes[2].table[1][3] = 0x0;
	shapes[2].bounds[1] = 0x5b;		// (1, 1, 2, 3)
	shapes[2].points[1] = 10;

	shapes[2].table[2][0] = 0x0;
	shapes[2].table[2][1] = 0xe;	// ###
	shapes[2].table[2][2] = 0x4;	//  #
	shapes[2].table[2][3] = 0x0;
	shapes[2].bounds[2] = 0x1a;		// (0, 1, 2, 2)
	shapes[2].points[2] = 12;

	shapes[2].table[3][0] = 0x4;	//  #
	shapes[2].table[3][1] = 0xc;	// ##
	shapes[2].table[3][2] = 0x4;	//  #
	shapes[2].table[3][3] = 0x0;
	shapes[2].bounds[3] = 0x17;		// (0, 1, 1, 3)
	shapes[2].points[3] = 10;


	// 4th shape

	shapes[3].table[0][0] = 0xc;	// ##
	shapes[3].table[0][1] = 0x6;	//  ##
	shapes[3].table[0][2] = 0x0;
	shapes[3].table[0][3] = 0x0;
	shapes[3].bounds[0] = 0x2b;		// (0, 2, 2, 3)
	shapes[3].points[0] = 12;

	shapes[3].table[1][0] = 0x4;	//  #
	shapes[3].table[1][1] = 0xc;	// ##
	shapes[3].table[1][2] = 0x8;	// #
	shapes[3].table[1][3] = 0x0;
	shapes[3].bounds[1] = 0x17;		// (0, 1, 1, 3)
	shapes[3].points[1] = 14;

	shapes[3].table[2][0] = 0xc;	// ##
	shapes[3].table[2][1] = 0x6;	//  ##
	shapes[3].table[2][2] = 0x0;
	shapes[3].table[2][3] = 0x0;
	shapes[3].bounds[2] = 0x2b;		// (0, 2, 2, 3)
	shapes[3].points[2] = 12;

	shapes[3].table[3][0] = 0x4;	//  #
	shapes[3].table[3][1] = 0xc;	// ##
	shapes[3].table[3][2] = 0x8;	// #
	shapes[3].table[3][3] = 0x0;
	shapes[3].bounds[3] = 0x17;		// (0, 1, 1, 3)
	shapes[3].points[3] = 14;


	// 5th shape

	shapes[4].table[0][0] = 0x6;	//  ##
	shapes[4].table[0][1] = 0xc;	// ##
	shapes[4].table[0][2] = 0x0;
	shapes[4].table[0][3] = 0x0;
	shapes[4].bounds[0] = 0x2b;		// (0, 2, 2, 3)
	shapes[4].points[0] = 12;
	
	shapes[4].table[1][0] = 0x8;	// #
	shapes[4].table[1][1] = 0xc;	// ##
	shapes[4].table[1][2] = 0x4;	//  #
	shapes[4].table[1][3] = 0x0;
	shapes[4].bounds[1] = 0x17;		// (0, 1, 1, 3)
	shapes[4].points[1] = 14;
	
	shapes[4].table[2][0] = 0x6;	//  ##
	shapes[4].table[2][1] = 0xc;	// ##
	shapes[4].table[2][2] = 0x0;
	shapes[4].table[2][3] = 0x0;
	shapes[4].bounds[2] = 0x2b;		// (0, 2, 2, 3)
	shapes[4].points[2] = 12;
	
	shapes[4].table[3][0] = 0x8;	// #
	shapes[4].table[3][1] = 0xc;	// ##
	shapes[4].table[3][2] = 0x4;	//  #
	shapes[4].table[3][3] = 0x0;
	shapes[4].bounds[3] = 0x17;		// (0, 1, 1, 3)
	shapes[4].points[3] = 14;


	// 6th shape

	shapes[5].table[0][0] = 0x2;	//   #
	shapes[5].table[0][1] = 0xe;	// ###
	shapes[5].table[0][2] = 0x0;
	shapes[5].table[0][3] = 0x0;
	shapes[5].bounds[0] = 0x2b;		// (0, 2, 2, 3)
	shapes[5].points[0] = 6;

	shapes[5].table[1][0] = 0x8;	// #
	shapes[5].table[1][1] = 0x8;	// #
	shapes[5].table[1][2] = 0xc;	// ##
	shapes[5].table[1][3] = 0x0;
	shapes[5].bounds[1] = 0x17;		// (0, 1, 1, 3)
	shapes[5].points[1] = 7;

	shapes[5].table[2][0] = 0xe;	// ###
	shapes[5].table[2][1] = 0x8;	// #
	shapes[5].table[2][2] = 0x0;
	shapes[5].table[2][3] = 0x0;
	shapes[5].bounds[2] = 0x2b;		// (0, 2, 2, 3)
	shapes[5].points[2] = 6;

	shapes[5].table[3][0] = 0xc;	// ##
	shapes[5].table[3][1] = 0x4;	//  #
	shapes[5].table[3][2] = 0x4;	//  #
	shapes[5].table[3][3] = 0x0;
	shapes[5].bounds[3] = 0x17;		// (0, 1, 1, 3)
	shapes[5].points[3] = 7;


	// 7th shape

	shapes[6].table[0][0] = 0xe;	// ###
	shapes[6].table[0][1] = 0x2;	//   #
	shapes[6].table[0][2] = 0x0;
	shapes[6].table[0][3] = 0x0;
	shapes[6].bounds[0] = 0x2b;		// (0, 2, 2, 3)
	shapes[6].points[0] = 6;

	shapes[6].table[1][0] = 0x4;	//  #
	shapes[6].table[1][1] = 0x4;	//  #
	shapes[6].table[1][2] = 0xc;	// ##
	shapes[6].table[1][3] = 0x0;
	shapes[6].bounds[1] = 0x17;		// (0, 1, 1, 3)
	shapes[6].points[1] = 7;

	shapes[6].table[2][0] = 0x8;	// #
	shapes[6].table[2][1] = 0xe;	// ###
	shapes[6].table[2][2] = 0x0;
	shapes[6].table[2][3] = 0x0;
	shapes[6].bounds[2] = 0x2b;		// (0, 2, 2, 3)
	shapes[6].points[2] = 6;

	shapes[6].table[3][0] = 0xc;	// ##
	shapes[6].table[3][1] = 0x8;	// #
	shapes[6].table[3][2] = 0x8;	// #
	shapes[6].table[3][3] = 0x0;
	shapes[6].bounds[3] = 0x17;		// (0, 1, 1, 3)
	shapes[6].points[3] = 7;
}

BOOL resize(NXSize *aSize)
{
	BOOL resized = NO;

	if (aSize->height > 32) {
		aSize->height = 32;
		resized = YES;
	}

	if (aSize->width > 32) {
		aSize->width = 32;
		resized = YES;
	}
	return resized;
}

- findBlockImages
{
	int i;
	char buffer[20];
	id image;
	char *blockName;
	BOOL isGameColor = [NXApp isGameColor];

	if (isGameColor) {
		blockName = "ColorBlock";
	} else {
		blockName = "MonoBlock";
	}
	for (i=0; i<4; i++) {
		sprintf(buffer, "%s%d", blockName, i+1);
		image = [NXImage findImageNamed:buffer];
		if (image == NULL) {
			fprintf(stderr, "Couldn't find block named %s\n", buffer);
			exit(0);
		}
		blockImage[i] = image;
	}
	// Assuming all the blocks are the same size.

	[blockImage[0] getSize:&blockSize];
	if (resize(&blockSize)) {

#ifdef DEBUG
		printf("resizing Block Image\n");
#endif
		[bitmap setScalable:YES];
		[bitmap setSize:&blockSize];
	} 

	return self;
}

- init
{
	[super init];

	[self setShapes];

	curCol = curRow = 0;
	currentPiece = PIECE_NULL;
	currentShape = SHAPE_NULL;
	currentRotation = shapenum = 0;

	invalidRect.origin.x = invalidRect.origin.y =
	  invalidRect.size.width = invalidRect.size.height = 0.0;

	// Initialized the animation Rect
	{
		NXSize tmp;
		[self findBlockImages];

		// Limit size of block image
		[blockImage[0] getSize:&tmp];
		resize(&tmp);

		tmp.height *= 4;
		tmp.width *= 4;

		anmShape = [[NXImage alloc] initSize:&tmp];
		[anmShape lockFocus];
		PSsetalpha(0.0);				  // make this block transparent
		PSrectfill(0., 0., tmp.width, tmp.height);
		PSsetalpha(1.0);
	}
	anmBackground = nil;
	viewVarsInited = NO;
	dropPoints = 0;
	srandom(getpid());	// Initialize random number generator

#ifdef DEBUG
	printf("Piece object initialized\n");
#endif

	return self;
}

/*
 *  Draw the Piece.
 *  The focus must be locked by on calling object's view before invoking this method.
 */
- draw:sender
{
	unsigned char mask;
	int xc, yc;
	int ycmax;
	NXPoint dPt;

	ycmax = MAX_SHAPE_SIZE -
	  YMIN_BOUNDS(currentShape->bounds[currentRotation]);
	for (yc = MAX_SHAPE_SIZE -
		  YMAX_BOUNDS(currentShape->bounds[currentRotation]) - 1;
		  yc < ycmax; yc++) {
			  mask = 1 << (MAX_SHAPE_SIZE - 1);
			  for (xc = 0; xc < MAX_SHAPE_SIZE; xc++) {
				  if (currentPiece[yc] & mask) {
					  // Call the sender's parent object 
					  [sender point:&dPt for:curRow + MAX_SHAPE_SIZE - yc - 1 :curCol + xc];
					  [bitmap composite:NX_SOVER toPoint:&dPt];
				  }
				  mask >>= 1;
			  }
		  }
	return self;
}


/*
 *  Determine what the next piece to drop will be.
 *
 */
- newPiece
{
	int bitmapNum;

#ifdef DEBUG
	printf("Generating a new piece\n");
#endif

	shapenum = random() % NUM_SHAPES;
//	shapenum = 0;
	currentShape = &shapes[shapenum];
	currentRotation = random() % NUM_ROTATIONS;
	currentPiece = currentShape->table[currentRotation];

	bitmapNum = random() % NUM_BITMAPS;

	bitmap = blockImage[bitmapNum];

	if (bitmap == NULL) {
		printf("Block %d not found\n", bitmapNum);
	}
	return self;
}

/*
 *  Needed so other objects can query to set the Window and View sizes.
 */
- getBlockSize:(NXSize *) size
{
	*size = blockSize;
	return self;
}

/*
 *  So other objects can query Piece.m about images
 */
- getBlockImage:(int) blockNum
{
	return blockImage[blockNum];
}

- (struct pieceInfo *)pieceInfo
{
	static struct pieceInfo info;

	info.bitmap = bitmap;
	info.shape = shapenum;
	info.rotation = currentRotation;
	return &info;
}


- setPiece:(struct pieceInfo *)info
{
	bitmap = info->bitmap;
	[bitmap getSize:&blockSize];
	resize(&blockSize);
	shapenum = info->shape;
	currentShape = &shapes[shapenum];
	currentRotation = info->rotation;
	currentPiece = currentShape->table[currentRotation];
	return self;
}

/*
 *  Get the next piece.
 *
 */
- (BOOL)reset:sender piece:(struct pieceInfo *)info
{
	BOOL legalMove;
	unsigned char shapeBounds;
	int xmin, xmax;
	int ymin;
	NXRect bounds;

#ifdef DEBUG
	printf("reseting piece\n");
#endif

	// info will contain the piece from NextMatrix if Show Next is selected
	// Otherwise, we'll generate a new piece. 

	if (info) {
		[self setPiece:info];
	} else {
		[self newPiece];
	}
	shapeBounds = currentShape->bounds[currentRotation];
	xmin = XMIN_BOUNDS(shapeBounds);
	xmax = XMAX_BOUNDS(shapeBounds);
	ymin = YMIN_BOUNDS(shapeBounds);
	curCol = (TETRIS_COLUMNS - (xmax - xmin + 1)) / 2;
	curRow = TETRIS_ROWS - ymin - 1;

	if (!viewVarsInited) {
		[sender getBounds:&bounds];
		[sender getIntercell:&intercell];

		// Create a new animation background
		anmBackground = [[NXImage alloc] initSize:&bounds.size];
		viewVarsInited = YES;
	}
	if ((legalMove = [self legalMove:sender :curRow :curCol rotation:currentRotation])) {
		[self getInvalidRect:&invalidRect for:sender];
		[sender display:&invalidRect :1];
	}
	dropPoints = 0;
	return legalMove;
}


- (BOOL)legalMove:sender :(int)row :(int)column rotation:(int)rotation
{
	unsigned char mask;
	int xc, yc;
	int ycmax;
	int ex, ey;
	unsigned char *piece;

	piece = currentShape->table[rotation];
	ycmax = MAX_SHAPE_SIZE - YMIN_BOUNDS(currentShape->bounds[rotation]);
	for (yc = MAX_SHAPE_SIZE -
		  YMAX_BOUNDS(currentShape->bounds[rotation]) - 1; yc < ycmax; yc++) {
			  mask = 1 << (MAX_SHAPE_SIZE - 1);
			  for (xc = 0; xc < MAX_SHAPE_SIZE; xc++) {
				  if (piece[yc] & mask) {
					  ey = row + MAX_SHAPE_SIZE - yc - 1;
					  ex = column + xc;
					  if (ex < 0 || ex >= TETRIS_COLUMNS || ey < 0
							|| [sender bitmapAt:ey :ex])
						 return NO;
				  }
				  mask >>= 1;
			  }
		  }
	return YES;
}


- left:sender
{
	if ([self legalMove:sender :curRow :(curCol - 1) rotation:currentRotation]) {
		curCol--;
		[sender display:&invalidRect :1];
		[self getInvalidRect:&invalidRect for:sender];
	}
	return self;
}

- right:sender
{
	if ([self legalMove:sender :curRow :(curCol + 1) rotation:currentRotation]) {
		curCol++;
		[sender display:&invalidRect :1];
		[self getInvalidRect:&invalidRect for:sender];
	}
	return self;
}

- getInvalidRect:(NXRect *)aRect for:sender
{
	 unsigned char bounds;
	 NXRect unionRect;

	 bounds = currentShape->bounds[currentRotation];
	 [sender getRect:&unionRect for:curRow + YMIN_BOUNDS(bounds) :curCol + XMIN_BOUNDS(bounds)];
	 [sender getRect: aRect for:curRow + YMAX_BOUNDS(bounds) :curCol + XMAX_BOUNDS(bounds)];
	 NXUnionRect(&unionRect, aRect);
	  
	 return self;
}

- point:(NXPoint *)thePoint for:(int)row :(int)column 
{
	unsigned char bounds = currentShape->bounds[currentRotation];
	int realx = column - curCol - XMIN_BOUNDS(bounds);
	int realy = row - curRow - YMIN_BOUNDS(bounds);
	
	thePoint->x = realx * (intercell.width + blockSize.width);
	thePoint->y = realy * (intercell.height + blockSize.height);
	return self;
}

/*
 *  Called when the player his the spacebar to drop the piece. 
 */
- compositeShape:sender
{
	if (anmShape) {		  // already exists
		//		[self->anmShape resize:self->invalidRect.size.width :self->invalidRect.size.height];
		//self->anmShape = [NXImage newSize:&self->invalidRect.size];
	} else {							  // create new image
		//self->anmShape = [NXImage newSize:&self->invalidRect.size];
		//.width
		//		 :self->invalidRect.size.height type:NX_UNIQUEALPHABITMAP];
		//        [self->anmShape setFlip:NO];
	}
	[anmShape lockFocus];
	PSsetalpha(0.0);				  // Fill the window with alpha
	PSrectfill(0.0, 0.0, self->invalidRect.size.width, self->invalidRect.size.height);
	PSsetalpha(1.0);				  // Draw image w/o alpha
	[self draw:self];
	[anmShape unlockFocus];

	return self;
}


- compositeBackground: sender
{
	NXRect unionRect;

	[self getInvalidRect:&unionRect for:sender];
	NXUnionRect(&self->invalidRect, &unionRect);
	[anmBackground lockFocus];
	[sender setPieceVisible:NO];
	[sender drawSelf:&unionRect :1];
	[sender setPieceVisible:YES];
	[anmBackground unlockFocus];
	return self;
}

/*
 *  Move the piece rapidly down the screen after the user hits the spacebar.
 *
 */
- animateDrop:sender
{
	NXRect updRect;
	
	[self getInvalidRect:&updRect for:sender];

	[sender lockFocus];

	// Erase piece by compositing the background over it.
	[anmBackground composite:NX_SOVER fromRect: &invalidRect
       toPoint:&invalidRect.origin];
	
	while (invalidRect.origin.y - ANM_DELTA > updRect.origin.y) {
		invalidRect.origin.y -= ANM_DELTA;

		// Show the piece dropping one increment
    	[anmShape composite:NX_SOVER toPoint:&invalidRect.origin];
    	[[sender window] flushWindow];

		// Erase the piece by showing the background
		[anmBackground composite:NX_SOVER fromRect:&invalidRect
	        toPoint:&invalidRect.origin];
	}
	[sender unlockFocus];
	
	[anmShape lockFocus];
	[anmShape getSize:&updRect.size];
	PScompositerect(0.0, 0.0, updRect.size.width, updRect.size.height, NX_CLEAR);
	[anmShape unlockFocus];
	return self;
}

/*
 * Return the amount of points that we get for this piece.
 */
- (int)points
{
	return currentShape->points[currentRotation] + dropPoints;
}

/*
 * Called when the user hits the space bar.
 *
 */
- drop:sender
{
	int ymin;
	int yc;

	ymin = YMIN_BOUNDS(currentShape->bounds[currentRotation]);

	for (yc = curRow - 1; yc >= -1 - ymin; yc--) {
		if (![self legalMove:sender :yc :curCol rotation:currentRotation])
		  break;
	}
	dropPoints += curRow - yc + 1;	  // You get extra points for dropping it
	curRow = yc + 1;

	[self compositeShape: sender];
	[self compositeBackground: sender];
	[self animateDrop: sender];

	[sender display:&invalidRect : 1];
	[self getInvalidRect:&invalidRect for:sender];
	return self;
}

/*
 * Move the piece down.
 */
- (BOOL)down:sender
{
	BOOL canGoDown;

	if ((canGoDown = [self legalMove:sender :curRow-1 :curCol rotation:currentRotation])){
		curRow--;
		[sender display:&invalidRect :1];
		[self getInvalidRect:&invalidRect for:sender];
	}
	return canGoDown;
}

- turn:sender
{    
	int newRotation = (currentRotation + 3) % NUM_ROTATIONS;
	
	if ([self legalMove:sender :curRow :curCol rotation:newRotation]) {
		currentRotation = newRotation;
		currentPiece = currentShape->table[newRotation];
		[sender display:&invalidRect :1];
		[self getInvalidRect:&invalidRect for:sender];
	}
	return self;
}

/*
 * Called when the piece has reached the bottom and can no longer be moved.
 *
 * For each block that composes the image, put the block's image id in the
 * proper iconMatrix[row][column] position.  Thus to redraw the screen,
 * we need only to composite the images from the iconMatrix.
 */
- stick:sender
{
	unsigned char mask;
	int col, theRow;
	int rowMax;

	rowMax = MAX_SHAPE_SIZE - YMIN_BOUNDS(currentShape->bounds[currentRotation]);
	for (theRow = MAX_SHAPE_SIZE -
		  YMAX_BOUNDS(currentShape->bounds[currentRotation]) - 1;
		  theRow < rowMax; theRow++) {
			  mask = 1 << (MAX_SHAPE_SIZE - 1);
			  for (col = 0; col < MAX_SHAPE_SIZE; col++) {
				  // If there's a block in  
				  if (currentPiece[theRow] & mask)
					 // Set the (row, column) of the matrix equal to Block's image.
					 [sender setBitmap:bitmap at:curRow + MAX_SHAPE_SIZE - theRow - 1
					                            :curCol + col];
				  mask >>= 1;
			  }
		  }
	return self;
}

- (int)getCurRow
{
	return curRow;
}

- free
{
	if (anmShape) [anmShape free];
	if (anmBackground) [anmBackground free];
	return [super free];
}

@end
