
#import <appkit/NXImage.h>
#import "robotView.h"
#import "robotsController.h"

#define RAND_X (random() % gridwidth)
#define RAND_Y (random() % gridheight)
#define RAND_OF(x) (random() % x)

@implementation robotView

- squareAtX:(int)i              Y:(int)j
{

    if                  ((i < 0) || (i >= gridwidth) || (j < 0) || (j >= gridheight))
    {
	                    return nil;
    }
    return mySquares[gridwidth * j + i];
}

- (BOOL)robotWithIDisFast:(int)idno
{
    int                 i;
    aRobot             *tmpBot;

    if (idno == NOROBOT)
	return NO;
    for (i = 0; i < [robotStorage count]; i++)
    {
	tmpBot = [robotStorage elementAt:i];
	if (tmpBot->idnumber == idno)
	{
	    if (tmpBot->speed == FAST)
	    {
		return YES;
	    }
	    else
	    {
		return NO;
	    }
	}
    }
    return NO;
}

- (BOOL)isSafeX:(int)x Y:(int)y
{
    int                 i, j, k;
    aSquare            *testSquare = [self squareAtX:x Y:y];

    if ((testSquare == nil) ||
	([testSquare hasRobotID] != NOROBOT) ||
	[testSquare hasScrap])
    {
	return NO;
    }

    for (i = -1; i <= 1; i++)
    {
	for (j = -1; j <= 1; j++)
	{
	    if (nil == (testSquare = [self squareAtX:x + i Y:y + j]))
		continue;
	    switch (abs (i) + abs (j))
	    {
	    case 0:
		{
		/* This is the center square... We've already checked it. */
		    break;
		}
	    case 1:
		{
		/* This is NSEW.  Check the three next to it, too. */
		    if ([testSquare hasScrap])
			break;
		    if ([testSquare hasRobotID] != NOROBOT)
			return NO;
		    for (k = -1; k <= 1; k++)
		    {
			if (nil == (testSquare =
				[self squareAtX:(x + ((i != 0) ? 2 * i : k))
				 Y :(y + ((j != 0) ? 2 * j : k))]))
			{
			    continue;
			}
			if ([self robotWithIDisFast:[testSquare hasRobotID]])
			{
			    return NO;
			}
		    }
		    break;
		}
	    case 2:
		{
		    if ([testSquare hasScrap])
			break;
		    if ([testSquare hasRobotID] != NOROBOT)
			return NO;
		    if (nil == (testSquare = [self squareAtX:x + i * 2 Y:y + j * 2]))
			continue;
		    if ([self robotWithIDisFast:[testSquare hasRobotID]])
			return NO;
		    break;
		}
	    } /* End switch. */
	} /* End for i */
    } /* End for j */
    return YES;
}

- drawGrid
{
    NXRect              myRect;

    NXSetRect (&myRect, 0, 0,
	       gridwidth * GRIDSIZE + 2, gridheight * GRIDSIZE + 2);
    PSsetgray (NX_BLACK);
    NXEraseRect (&myRect);
    NXFrameRect (&myRect);

/*
   NXSetRect (&myRect, 0, 0, GRIDSIZE, GRIDSIZE); 
  
PSsetgray (NX_LTGRAY); NXRectFill (&bounds); PSsetgray (NX_BLACK); for (y =
   0; y < gridheight; y++) { myRect.origin.y = y * GRIDSIZE; for (x = 0; x <
   gridwidth; x++) { myRect.origin.x = x * GRIDSIZE; NXFrameRect (&myRect); }
   } 
*/
    return self;
}

- drawScrapAtX:(int)x Y:(int)y
{
    NXRect              scrapRect;

    NXSetRect (&scrapRect, x * GRIDSIZE + 1, y * GRIDSIZE + 1,
	       GRIDSIZE, GRIDSIZE);
    [self lockFocus];

/*
   PSsetgray (NX_BLACK); NXRectFill (&scrapRect); 
*/
    [scrapImage composite:NX_SOVER toPoint:&scrapRect.origin];
    [self unlockFocus];

    return self;
}

- advanceLevel
{
    int                 i, j;

    NXRunAlertPanel ("Level Cleared",
		     "Level %d: Crushed %d robots into %d heaps.",
		     "Cool", NULL, NULL, level, initialRobots, scrapheaps);

    for (i = 0; i < gridwidth; i++)
    {
	for (j = 0; j < gridheight; j++)
	{
	    [[self squareAtX:i Y:j] init];
	}
    }
    valuePerRobot += level * (5 + (initialRobots / scrapheaps));
    initialRobots += level * 3 + RAND_OF (level * 5);
    /* This next line will be modified to %70, or so I guess. */
    initialRobots = MIN (initialRobots, gridwidth * gridheight - 1);
    teleports += MIN (level, 3);
    level++;
    return self;
}

- newScrap
{
    scrapheaps++;
    return self;
}

- teleportSafely:(BOOL)isSafe
{
    int                 newX, newY;

    do
    {
	newX = RAND_X;
	newY = RAND_Y;
    }
    while ([[self squareAtX:newX Y:newY] hasScrap] ||
	   ([[self squareAtX:newX Y:newY] hasRobotID] != NOROBOT));

    if (isSafe)
    {
	int                 i, newX, newY;

	for (i = 0; i < gridwidth * gridheight * 10; i++)
	{
	    newX = RAND_X;
	    newY = RAND_Y;
	    if ([self isSafeX:newX Y:newY] &&
		![[self squareAtX:playerX Y:playerY] hasScrap])
	    {
		playerX = newX;
		playerY = newY;

		teleports--;
		[myDelegate showTeleports:teleports];
		return self;
	    }

	}
	{
	    int                 x, y;

	    teleports -= 4;
	    [myDelegate showTeleports:teleports];
	    NXRunAlertPanel ("Nowhere To Run!!",
			     "You NUKE all robots within 2 squares.",
			     "Bye, 4 teleports", NULL, NULL);
	    [self lockFocus];
	    PSsetgray (BACKGROUND);
	    for (x = playerX - 2; x <= playerX + 2; x++)
		for (y = playerY - 2; y <= playerY + 2; y++)
		{
		    aSquare            *nukeSquare = [self squareAtX:x Y:y];

		    if (!nukeSquare)
			continue;
		    if ([nukeSquare hasRobotID] != NOROBOT)
		    {
			NXRect              roboRect;

			NXSetRect (&roboRect, x * GRIDSIZE + 1,
				   y * GRIDSIZE + 1, GRIDSIZE, GRIDSIZE);
			NXRectFill (&roboRect);
			[self deleteRobotID:[nukeSquare hasRobotID]];
		    }

		}
	    [self unlockFocus];
	}
	return self;
    } /* End isSafe */
    else
    {
	playerX = newX;
	playerY = newY;
    }
    return self;
}

/* Clear any leftovers from last level.  Init all the robots. */
- initLevel
{
    NXRect              myRect;
    int                 x, y;

    [self lockFocus];
    NXSetRect (&myRect, 0, 0,
	       gridwidth * GRIDSIZE + 2, gridheight * GRIDSIZE + 2);
    PSsetgray (NX_LTGRAY);
    NXRectFill (&bounds);
    [self drawGrid];
    [self unlockFocus];

/* We'll teleport the player later */
    playerX = gridwidth / 2;
    playerY = gridheight / 2;
    scrapheaps = 0;
    [myDelegate showRobots:initialRobots];
    [myDelegate showHeaps:scrapheaps];
    [myDelegate showLevel:level];
    [myDelegate showScore:score];
    [myDelegate showTeleports:teleports];
    [myDelegate showValue:valuePerRobot];
/* Place all the robots. */
    while ([robotStorage count] < initialRobots)
    {
	aRobot              newRobot;

	x = RAND_X;
	y = RAND_Y;
	if ([[self squareAtX:x Y:y] hasRobotID] == NOROBOT)
	{
	    id                  tmpId;

	    newRobot.x = x;
	    newRobot.y = y;
	    newRobot.idnumber = [robotStorage count];
	    newRobot.speed = 1 + (RAND_OF (10) > 6);
	    tmpId = [self squareAtX:x Y:y];
	    [tmpId addRobot:&newRobot];
	    [robotStorage addElement:&newRobot];
	}
    }
    [self teleportSafely:NO];
    return self;
}

- moveRobotsOfSpeed:(int)robotSpeed
{
    int                 i;
    aRobot             *tmpRobot;

/*
   Delete the record of all the robots that are going to move this round from
   the squares that they are currently in. 
*/
    for (i = 0; i < [robotStorage count]; i++)
    {
	tmpRobot = [robotStorage elementAt:i];
	if (tmpRobot->speed >= robotSpeed)
	{
	    [tmpRobot->occupiedSquare removeRobot:tmpRobot];
	}
    }

/* Move all the robots (but don't place them yet). */
    [self lockFocus];
    for (i = 0; i < [robotStorage count]; i++)
    {
	tmpRobot = [robotStorage elementAt:i];

    /* If we're handling all robots... */
	if ((tmpRobot->speed >= robotSpeed) && (robotSpeed == NORMAL))
	{
	    NXRect              robotRect;

	/* Erase the robot. */
	    NXSetRect (&robotRect, tmpRobot->x * GRIDSIZE + 1,
		       tmpRobot->y * GRIDSIZE + 1, GRIDSIZE, GRIDSIZE);

	    PSsetgray (BACKGROUND);
	    NXRectFill (&robotRect);
	}

	if (tmpRobot->speed < robotSpeed)	/* If this one is slow. */
	    continue;		/* We just continue the loop. */

	if (playerX > tmpRobot->x)
	    tmpRobot->x++;
	if (playerX < tmpRobot->x)
	    tmpRobot->x--;
	if (playerY > tmpRobot->y)
	    tmpRobot->y++;
	if (playerY < tmpRobot->y)
	    tmpRobot->y--;
	[[self squareAtX:tmpRobot->x Y:tmpRobot->y] addRobot:tmpRobot];
    }
    [self unlockFocus];

    if (robotSpeed == NORMAL)
	[self moveRobotsOfSpeed:FAST];

    return self;
}

- doMove:(int)movetype whileSafe:(BOOL)repeat
{
    int                 deltaX = 0, deltaY = 0, i = 0;
    NXRect              playerRect;

/* Erase the player. */
    NXSetRect (&playerRect, playerX * GRIDSIZE + 1, playerY * GRIDSIZE + 1,
	       GRIDSIZE, GRIDSIZE);

    [self lockFocus];
    PSsetgray (BACKGROUND);
    NXRectFill (&playerRect);
    [self unlockFocus];

    switch (movetype)
    {
    case DOWNLEFT:
	{
	    if ((playerX > 0) && (playerY > 0))
	    {
		deltaX--;
		deltaY--;
	    }
	    break;
	}
    case DOWN:
	{
	    if (playerY > 0)
	    {
		deltaY--;
	    }
	    break;
	}
    case DOWNRIGHT:
	{
	    if ((playerX < (gridwidth - 1)) && (playerY > 0))
	    {
		deltaX++;
		deltaY--;
	    }
	    break;
	}
    case LEFT:
	{
	    if (playerX > 0)
	    {
		deltaX--;
	    }
	    break;
	}
    case WAIT:
	{
	    break;
	}
    case RIGHT:
	{
	    if (playerX < (gridwidth - 1))
	    {
		deltaX++;
	    }
	    break;
	}
    case UPLEFT:
	{
	    if ((playerX > 0) && (playerY < (gridheight - 1)))
	    {
		deltaX--;
		deltaY++;
	    }
	    break;
	}
    case UP:
	{
	    if (playerY < (gridheight - 1))
	    {
		deltaY++;
	    }
	    break;
	}
    case UPRIGHT:
	{
	    if ((playerX < (gridwidth - 1)) && (playerY < (gridheight - 1)))
	    {
		deltaX++;
		deltaY++;
	    }
	    break;
	}
    case TELEPORT:
	{
	    [self teleportSafely:(repeat && (teleports > 0))];
	    repeat = NO;
	    break;
	}
    case SCREWDRIVER:
	{
	    int                 x, y;

	    if (teleports > 0)
	    {
		teleports--;
		[myDelegate showTeleports:teleports];
	    }
	    else
	    {
		return self;
	    }
	    for (x = playerX - 1; x <= playerX + 1; x++)
	    {
		for (y = playerY - 1; y <= playerY + 1; y++)
		{
		    aSquare            *frySquare = [self squareAtX:x Y:y];

		    if ((frySquare != nil) &&
			([frySquare hasRobotID] != NOROBOT))
		    {
			NXRect              roboRect;

			[self lockFocus];
			PSsetgray (BACKGROUND);
			NXSetRect (&roboRect, x * GRIDSIZE + 1,
				   y * GRIDSIZE + 1, GRIDSIZE, GRIDSIZE);
			NXRectFill (&roboRect);
			[self unlockFocus];
			[self deleteRobotID:[frySquare hasRobotID]];
		    }
		}
	    }
	    break;
	}
    default:
	{
	    printf ("ooops\n");
	}
    }
    if ([[self squareAtX:(playerX + deltaX) Y :(playerY + deltaY)] hasScrap])
    {
	if ((playerX + deltaX * 2 >= 0) &&
	    (playerY + deltaY * 2 >= 0) &&
	    (playerX + deltaX * 2 <= gridwidth - 1) &&
	    (playerY + deltaY * 2 <= gridheight - 1) &&
	[[self squareAtX:(playerX + deltaX) Y :(playerY + deltaY)] moveScrap:
	 [self squareAtX:(playerX + deltaX * 2) Y :(playerY + deltaY * 2)]])
	{
	    NXRect              oldScrapRect;

	    NXSetRect (&oldScrapRect, (playerX + deltaX) * GRIDSIZE + 1,
		       (playerY + deltaY) * GRIDSIZE + 1,
		       GRIDSIZE, GRIDSIZE);

	    [self drawScrapAtX:(playerX + deltaX * 2)
	     Y :(playerY + deltaY * 2)];
	/* Erase the old scrap. */
	    [self lockFocus];
	    PSsetgray (BACKGROUND);
	    NXRectFill (&oldScrapRect);
	    [self unlockFocus];

	    playerX += deltaX;
	    playerY += deltaY;
	    [self moveRobotsOfSpeed:NORMAL];
	}
    }
    else
    {
	playerX += deltaX;
	playerY += deltaY;
	[self moveRobotsOfSpeed:NORMAL];
    }

    while ([robotStorage count] && i < [robotStorage count])
    {
	aRobot             *tmpRobot = [robotStorage elementAt:i];

	if (tmpRobot->speed == DEAD)
	{
	    [robotStorage removeElementAt:i];
	}
	else
	{
	    i++;
	}
    }

    if (([[self squareAtX:playerX Y:playerY] hasRobotID] != NOROBOT) ||
	([[self squareAtX:playerX Y:playerY] hasScrap]))
    {
	playerstate = WIPED;
	[self display];
	return self;
    }

    if ([robotStorage count] == 0)
    {
	repeat = NO;
	[self advanceLevel];
	[self initLevel];
    }
    else
    {
	[myDelegate showRobots:[robotStorage count]];
	[myDelegate showHeaps:scrapheaps];
	[myDelegate showScore:score];
    }
    [self display];

    if (repeat)
    {
	if ([self isSafeX:playerX + deltaX Y:playerY + deltaY])
	    return[self doMove:movetype whileSafe:repeat];
    }
    return self;
}

- keyDown:(NXEvent *)theEvent
{
    if (theEvent->type != NX_KEYDOWN)
	return[super keyDown:theEvent];
    switch (theEvent->data.key.charCode)
    {
    case '1':
	{
	    [self doMove:DOWNLEFT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '2':
	{
	    [self doMove:DOWN
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '3':
	{
	    [self doMove:DOWNRIGHT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '4':
	{
	    [self doMove:LEFT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '5':
	{
	    [self doMove:WAIT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '6':
	{
	    [self doMove:RIGHT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '7':
	{
	    [self doMove:UPLEFT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '8':
	{
	    [self doMove:UP
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case '9':
	{
	    [self doMove:UPRIGHT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case 't':
	{
	    [self doMove:TELEPORT
	     whileSafe:(0 != (theEvent->flags & NX_COMMANDMASK))];
	    break;
	}
    case 'a':
	{
	    [self doMove:SCREWDRIVER whileSafe:NO];
	    break;
	}
    default:
	{
	    return[super keyDown:theEvent];
	}
    }
    return self;
}

- init
{
    int                 j;
    NXSize              imagesize;

    imagesize.width = imagesize.height = GRIDSIZE;

    [super init];
    srandom (time (0));
    level = 1;
    score = 0;
    teleports = 1;
    playerstate = HEALTHY;
    initialRobots = 10;
    valuePerRobot = 10;

    if (mySquares)
    {
	for (j = 0; j < gridheight * gridwidth; j++)
	{
	    [mySquares[j] free];
	}
	free (mySquares);
    }
/* Subtract 2 for the border. */
    gridheight = (int)(bounds.size.height - 2) / GRIDSIZE;
    gridwidth = (int)(bounds.size.width - 2) / GRIDSIZE;

    mySquares = (id *)malloc (gridheight * gridwidth * sizeof (id));
    for (j = 0; j < gridheight * gridwidth; j++)
    {
	mySquares[j] = [[aSquare alloc] init];
	[mySquares[j] setDelegate:self];
    }

    [robotStorage initCount:0 elementSize:sizeof (aRobot)
     description :"{iiic@}"];
    [self initLevel];
    slowRobotImage = [[NXImage alloc] initFromSection:"slowrobot"];
    fastRobotImage = [[NXImage alloc] initFromSection:"fastrobot"];
    scrapImage = [[NXImage alloc] initFromSection:"scrap"];
    personImage = [[NXImage alloc] initFromSection:"person"];
    wipedImage = [[NXImage alloc] initFromSection:"wiped"];
    [[self window] makeKeyAndOrderFront:self];
    [[self window] makeFirstResponder:self];
    [self display];
    return self;
}

- drawRobot:(aRobot *) theRobot
{
    NXPoint             roboPoint;

    roboPoint.x = theRobot->x * GRIDSIZE + 1;
    roboPoint.y = theRobot->y * GRIDSIZE + 1;
    switch (theRobot->speed)
    {
    case FAST:
	{
	    [fastRobotImage composite:NX_SOVER toPoint:&roboPoint];
	    break;
	}
    case NORMAL:
	{
	    [slowRobotImage composite:NX_SOVER toPoint:&roboPoint];
	    break;
	}
    default:
	{
	    return self;
	}
    }

/*
   NXRect              roboRect; 
  
switch (theRobot->speed) { case FAST: { PSsetrgbcolor (1.0, 0.0, 0.0); break;
   } case NORMAL: { PSsetrgbcolor (0.0, 1.0, 0.0); break; } default: { return
   self; } } NXSetRect (&roboRect, theRobot->x * GRIDSIZE + 1, theRobot->y *
   GRIDSIZE + 1, GRIDSIZE, GRIDSIZE); NXRectFill (&roboRect); 
*/
    return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    int                 i;
    aRobot             *tmpRobot;
    NXRect              playerRect;

    //[self drawGrid];
    for (i = 0; i < [robotStorage count]; i++)
    {
	tmpRobot = [robotStorage elementAt:i];
	[self drawRobot:tmpRobot];
    }

/* Draw the player. */
    switch (playerstate)
    {
    case WIPED:
	{
	    NXSetRect (&playerRect, (playerX - 1) * GRIDSIZE + 1,
		       (playerY - 1) * GRIDSIZE + 1, GRIDSIZE, GRIDSIZE);
	    [wipedImage composite:NX_SOVER toPoint:&(playerRect.origin)];

	    [myDelegate playerDead:self];
	    [[self window] makeFirstResponder:[self window]];
	    playerstate = UNSTARTED;
	    break;
	}
    case HEALTHY:
	{
	    NXSetRect (&playerRect, playerX * GRIDSIZE + 1,
		       playerY * GRIDSIZE + 1, GRIDSIZE, GRIDSIZE);
	    [personImage composite:NX_SOVER toPoint:&(playerRect.origin)];
	    break;
	}
    }
    return self;
}

- deleteRobot:(aRobot *) deadRobot
{
    int                 i = 0;
    aRobot             *tmpRobot;

    do
    {
	tmpRobot = [robotStorage elementAt:i];
    }
    while ((tmpRobot->idnumber != deadRobot->idnumber) &&
	   (++i < [robotStorage count]));

    if (i < [robotStorage count])
    {
	score += valuePerRobot;
	if (tmpRobot->speed == FAST)
	{
	    score += valuePerRobot / 2;
	}
	tmpRobot->speed = DEAD;
    }

    return self;
}

- deleteRobotID:(int)deadRobotID
{
    int                 i;
    aRobot             *tmpRobot;

    if (deadRobotID == NOROBOT)
    {
	return self;
    }
    for (i = 0; i < [robotStorage count]; i++)
    {
	tmpRobot = [robotStorage elementAt:i];
	if (tmpRobot->idnumber == deadRobotID)
	{
	    [tmpRobot->occupiedSquare removeRobot:tmpRobot];
	    [self deleteRobot:tmpRobot];
	    return self;
	}
    }
    return self;
}

- scrapRobotID:(int)deadRobotID
{
    int                 i;
    aRobot             *tmpRobot;

    for (i = 0; i < [robotStorage count]; i++)
    {
	tmpRobot = [robotStorage elementAt:i];
	if (tmpRobot->idnumber == deadRobotID)
	{
	    [self drawScrapAtX:tmpRobot->x Y:tmpRobot->y];
	    [self deleteRobot:tmpRobot];
	    return self;
	}
    }

/*
   Trouble if we get here!! 
*/
    i = i / (i - i);

    return self;
}

- (int)playerState
{
    return playerstate;
}

@end
