
#import "BoinkViewPart.h"
#import "SpaceView.h"
#import "Thinker.h"
#import "BoinkWraps.h"
#import <appkit/NXImage.h>
#import <math.h>
#import <libc.h>
#import <dpsclient/wraps.h>

@implementation BoinkView

// assumed interval in milliseconds
#define ASSUMED_INTERVAL 35

#define WIDTH 100
#define HEIGHT 100
#define GAP 4
#define COUNT 10
#define ACCEL (-2)
#define REBOUND (-1.3)

// This screen height value is not critical, though it will be used
// to determine how high the ball can go
#define SCREEN_HEIGHT 832
#define LAUNCH_SPEED (sqrt(fabs(2*ACCEL*(SCREEN_HEIGHT - HEIGHT))))
#define REAL_LAUNCH_SPEEd (sqrt(fabs(2*accel*(viewHeight - HEIGHT))))

#define MIN_X_SPEED (3)
#define MAX_X_SPEED (6)
#define ABS_MAX_X_SPEED (6)
#define MAX_Y_SPEED (LAUNCH_SPEED + 20)

#define BUFFER_WIDTH (WIDTH + ABS_MAX_X_SPEED + 1)
#define BUFFER_HEIGHT (HEIGHT + MAX_Y_SPEED + 1)



/* move the ball to its new bounce position */
- oneStep
{
	NXRect black = {0,0,0,0};
	NXRect ballRect;
	BRECT new;
	float scaledTime, calcYpos;
	
	then = now;
	now = currentTimeInMs();

	/* calculate new ball x position */
	xpos += [self timeCorrectedXSpeed];

	if (xpos < 0)				/* ball hit left edge */
	{	xspeed = -xspeed;
		if (viewWidth > WIDTH)
		{	spinDir = -spinDir;
		}
		xpos = 0;
	}
	else if (xpos > (viewWidth - WIDTH))		/* ball hit right edge */
	{	if (viewWidth > WIDTH)
		{
			xspeed = -[self getRandomXspeed];
			[self checkXspeed:&xspeed];
			xpos = (viewWidth - WIDTH);
		}
		else
		{	xspeed = xpos = 0;
		}
	}


	scaledTime = ((float)(now - then) / ASSUMED_INTERVAL);
	if (scaledTime > 1) scaledTime = 1;
	
	// calculate new ball vertical position
	calcYpos = ypos + (scaledTime*yspeed) + ((accel * scaledTime * scaledTime)/2);

	// change vertical ball speed to simulate gravity
	yspeed += (accel * scaledTime);
	
	if (calcYpos < (ypos - MAX_Y_SPEED)) calcYpos = ypos - MAX_Y_SPEED;
	else if (calcYpos > (ypos + MAX_Y_SPEED)) calcYpos = ypos + MAX_Y_SPEED;
	
	ypos = calcYpos;
	
	if (yspeed < -MAX_Y_SPEED) yspeed = -MAX_Y_SPEED;
	

	if (ypos <= 0)				/* ball hit bottom of window */
	{
		ypos = 0;
		
		if (viewHeight > HEIGHT)
		{
			if (reboundMode == DECREASING)
			{
				yspeed = lastLaunchSpeed = lastLaunchSpeed + rebound;
			}
			else
			{
				yspeed = lastLaunchSpeed = lastLaunchSpeed - (2*rebound);
			}

			if (yspeed <= 0)
			{
				yspeed = 0;
				reboundMode = INCREASING;	/* bounce height increases every bounce */
			}
			else if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED - (3*accel);
		}
		else yspeed = 0;
	
	}
	else if (ypos >= (viewHeight - HEIGHT))	/* ball hit top of window */
	{	if (viewHeight > HEIGHT)
		{
			yspeed = accel;
			ypos = (viewHeight - HEIGHT);
			reboundMode = DECREASING;	/* bounce height decreases every bounce */
			spinDir = -spinDir;
		}
		else
		{	yspeed = ypos = 0;
		}
	}


	/* rotate the ball by selecting a new ball image to blit */
	/* we have an image of the ball in 10 different stages of rotation */

	[self incrementBallNumber];
	
	new.l = floor(xpos);
	new.b = floor(ypos);
	new.r = new.l + WIDTH;
	new.t = new.b + HEIGHT;
	
	ballRect.origin.x = (WIDTH+GAP) * ballNum;
	ballRect.origin.y = 0;
	ballRect.size.width = WIDTH;
	ballRect.size.height = HEIGHT;
	
	redrawTo.x = MIN(new.l, old.l);
	redrawTo.y = MIN(new.b, old.b);

	redraw.origin.x = 0;
	redraw.origin.y = 0;
	redraw.size.width = (MAX(new.r, old.r)) - redrawTo.x + 1;
	redraw.size.height = (MAX(new.t, old.t)) - redrawTo.y + 1;
	
	black.size= redraw.size;

	[self updateGrid];

	[buffer lockFocus];
	PSsetgray(0);
	NXRectFill(&black);
	
	ballTo.x = new.l - redrawTo.x;
	ballTo.y = new.b - redrawTo.y;

	[self drawLinesInBuffer];
	
	[balls composite:NX_SOVER fromRect:&ballRect toPoint:&ballTo];
	[buffer unlockFocus];
	
	
	// Now bring it onto the screen
	
	[buffer composite:NX_COPY fromRect:&redraw toPoint:&redrawTo];

	old = new;

	return self;
}



/* calculate a vertical launch speed which will get the ball almost to	*/
/* the top of the window before gravity pulls it back down. Little bit	*/
/* of physics lesson here...											*/

- newSpeed
{
	lastLaunchSpeed = yspeed = REAL_LAUNCH_SPEEd;
	if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED;
	xpos = 0;
	ypos = 0;

	if (viewWidth <= WIDTH) xspeed = 0;
	else xspeed = [self getRandomXspeed];

	[self checkXspeed:&xspeed];
	rebound = REBOUND;
	return self;
}


- initFrame:(NXRect *)frameRect
{
	NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT };

	[super initFrame:frameRect];
	[self allocateGState];		// For faster lock/unlockFocus
	[self setClipping:NO];		// even faster...

	accel = ACCEL;
	spinDir = 1;

	//in this case, I only need one buffer for several Views
	if (!(buffer = [NXImage findImageNamed:"boinkBuffer"]))
	{
		buffer = [[NXImage alloc] initSize:&black.size];
		[buffer setName:"boinkBuffer"];
	}
	
	if ([buffer lockFocus])
	{
		PSsetgray(0);
		NXRectFill(&black);
		[buffer unlockFocus];
	}

	balls = [NXImage findImageNamed:"balls"];

	[self newViewSize];

	return self;
}

- setAccel:(float)val
{
	accel = val;
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self newViewSize];
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	
	//PSsetgray(0);
	//NXRectFill(rects);
	
	NXRectClip(rects);
	[self drawGrid];

	return self;
}

- newViewSize
{
	int i;
	//this is called every time View size changes
	NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT };

	then = now = currentTimeInMs();

	if (oldSize.width == bounds.size.width &&
			oldSize.height == bounds.size.height)
		return self;
	else
	{
		oldSize.width = bounds.size.width;
		oldSize.height = bounds.size.height;
	}
	
	old.l = old.r = old.b = old.t = ballTo.x = ballTo.y = 0;

	viewWidth = bounds.size.width;
	viewHeight = bounds.size.height;
	if (viewHeight > SCREEN_HEIGHT) viewHeight = SCREEN_HEIGHT;
	
	nvert = viewWidth/130;
	if (nvert > NVERT) nvert = NVERT;
	nhoriz = viewHeight/130;
	if (nhoriz > NHORIZ) nhoriz = NHORIZ;
	
	if (viewWidth < WIDTH) nvert = 0;
	if (viewHeight < HEIGHT) nhoriz= 0;
	vcount = hcount = 0;
	
	for (i=0; i<nvert; i++)
	{
		vertLines[i].hue = i * 0.17;
		while (vertLines[i].hue > 1) vertLines[i].hue -= 1;
		vertLines[i].pos = floor(i * (viewWidth/nvert));
	}
	
	for (i=0; i<nhoriz; i++)
	{
		horizLines[i].hue = i * 0.17 + 0.1;
		while (horizLines[i].hue > 1) horizLines[i].hue -= 1;
		horizLines[i].pos = i * floor((viewHeight/nhoriz)) + 1;
	}
	
	if ([buffer lockFocus])
	{
		PSsetgray(0);
		NXRectFill(&black);
		[buffer unlockFocus];
	}

	[self newSpeed];
	return self;
}

- incrementBallNumber
{
	if (now > nextRotationTime)
	{
		ballNum += spinDir;

		if (ballNum >= COUNT) ballNum = 0;
		else if (ballNum < 0) ballNum = COUNT-1;
		nextRotationTime = now + 24;
	}

	return self;
}

- (float) getRandomXspeed
{
	return randBetween(MIN_X_SPEED, MAX_X_SPEED);
}

- (float) timeCorrectedXSpeed
{
	float ret = xspeed * ((float)(now - then) / ASSUMED_INTERVAL);
	[self checkXspeed:&ret];
	return ret;
}

- checkXspeed:(float *)speed
{
	if (*speed > MAX_X_SPEED) *speed = MAX_X_SPEED;
	else if (*speed < -MAX_X_SPEED) *speed = -MAX_X_SPEED;
	return self;
}

- (const char *)windowTitle
{
	return "Boink!";
}


- drawGrid
{
	int i;
	float *fp;
		
	for (i=0; i<nvert; i++)
	{
		fp = &vertLines[i].pos;
		colorLine(*fp, 0, *fp, viewHeight, vertLines[i].hue, 1);
	}
	
	for (i=0; i<nhoriz; i++)
	{
		fp = &horizLines[i].pos;
		colorLine(0, *fp, viewWidth, *fp, horizLines[i].hue, 1);
	}
	
	return self;
}

- updateGrid
{
	NXRect avoid;
	float oldPos;
	float *fp;
	
	if (!nvert && !nhoriz) return self;

	if (now < nextLineDrawTime) return self;

	nextLineDrawTime = now + 3300;
	
	avoid.origin = redrawTo;
	avoid.size = redraw.size;

	if (++toggle & 1)
	{
		//advance vertical line
		
		if (!nvert) return self;
		
		fp = &vertLines[vcount].pos;
		oldPos = *fp;
		*fp += 1;
		if (*fp > viewWidth) *fp = 0;
		vertLines[vcount].hue += 0.005;
		if (vertLines[vcount].hue > 1) vertLines[vcount].hue -= 1;
		

		verticalLineWithAvoidance(*fp, 0, *fp, viewHeight, vertLines[vcount].hue, 1, &avoid);
		verticalLineWithAvoidance(oldPos, 0, oldPos, viewHeight, 0, 0, &avoid);

		if (++vcount >= nvert) vcount = 0;
	}
	else
	{
		//advance horiz line
		
		if (!nhoriz) return self;
		
		fp = &horizLines[hcount].pos;
		oldPos = *fp;
		*fp += 1;
		if (*fp > viewHeight) *fp = 0;
		horizLines[hcount].hue += 0.005;
		if (horizLines[hcount].hue > 1) horizLines[hcount].hue -= 1;
		

		horizLineWithAvoidance(0, *fp, viewWidth, *fp, horizLines[hcount].hue, 1, &avoid);
		horizLineWithAvoidance(0, oldPos, viewWidth, oldPos, 0, 0, &avoid);

		if (++hcount >= nhoriz) hcount = 0;
	}
	
	return self;
}

void horizLineWithAvoidance(float x1, float y1, float x2,float y2,
			float hue,float brightness, const NXRect *r)
{
	if (y1 <= r->origin.y || y1 >= r->origin.y+r->size.height)
		colorLine(x1, y1, x2, y2, hue, brightness);
	else
	{
		colorLine(x1, y1, r->origin.x, y2, hue, brightness);
		colorLine(r->origin.x+r->size.width, y1, x2, y2, hue, brightness);
	}
}

void verticalLineWithAvoidance(float x1, float y1, float x2,float y2,
			float hue,float brightness, const NXRect *r)
{
	if (x1 <= r->origin.x || x1 >= r->origin.x+r->size.width)
		colorLine(x1, y1, x2, y2, hue, brightness);
	else
	{
		colorLine(x1, y1, x2, r->origin.y, hue, brightness);
		colorLine(x1, r->origin.y+r->size.height, x2, y2, hue, brightness);
	}
}

- drawLinesInBuffer
{
	NXRect avoid;
	int i;
		
	avoid.origin = redrawTo;
	avoid.size = redraw.size;

	for (i=0; i<nvert; i++)
	{
		float x = vertLines[i].pos;

		if (x >= avoid.origin.x && x <= avoid.origin.x + avoid.size.width)
		{
			colorLine(x-redrawTo.x, 0, x-redrawTo.x, avoid.size.height, vertLines[i].hue, 1);
		}
	}
	
	for (i=0; i<nhoriz; i++)
	{
		float y = horizLines[i].pos;

		if (y >= avoid.origin.y && y <= avoid.origin.y + avoid.size.height)
		{
			colorLine(0, y-redrawTo.y, avoid.size.width, y-redrawTo.y, horizLines[i].hue, 1);
		}
	}
	
	return self;
}



@end







