//  NewSpaceView.m
//
//  This class implements the flying starfield screen saver view.
//  This is mostly the code from SpaceView in BackSpace
//
// Hacked by R.S. Brown for added functionality


#import "NewSpaceView.h"
#import "Thinker.h"
#import "psfuncts.h"
#import "spaceFuncts.h"



#import <dpsclient/wraps.h>
#import <appkit/NXImage.h>
#import <objc/zone.h>
#import <mach/mach.h>
#import <c.h>
#import <libc.h>
#import <math.h>
#import "StarShipView.h"

#define PI (3.141592653589)
#define SCREENMOVE 1000      //how often screen moves
#define LIGHTMOVE 10		//how often lights move

@implementation NewSpaceView

+ initialize
{
    if ( self == [NewSpaceView class] )
	{
		// load PostScript procedures SpaceView needs
		// must be careful these are loaded into the correct context
		loadPSProcedures();
    }
    return self;
}

//takes theta and distance and stuffs it into x &y for *p
- convertToXY:(STAR *)p
{

	p->draw->x = floor(bounds.size.width / 2 + (p->distance * cos(p-> theta)));
	p->draw->y = floor(bounds.size.height / 2 + (p->distance * sin(p-> theta)));
	
	return self;
}

- oneStep
{
	int i, stopTest, count;
	int starsInBArray = 0;
	int starsInWArray = 0;
	STAR *p;
	NXPoint *t;
	float adjustedStarSpeed;
	STAR test;
	NXPoint testP;



		if (nstars < NSTARS && !hidden) [self addStar];

		stopTest = 0;
		for (i=0; i<nstars; i++)
		{
			p = &stars[i];
			p->distance += (p->delta);
			if(!stopping)
				adjustedStarSpeed = starSpeed;
			else
				adjustedStarSpeed = 1.0;
			p->delta = p->delta * p->ddelta * stopDelta * adjustedStarSpeed;
			if(p->delta < .01){ // this must be less than starting p>delta
				stopTest++;
				p->delta = 0.0;
			}


			[self convertToXY:p];

			// only draw the star if it moved > 1 pixel or stopped
			if (p->draw->x != p->erase->x || 
				p->draw->y != p->erase->y || stopped){
				BOOL mustErase = NO;
				// add star to the erasure array
				b[starsInBArray] = *p->erase;
				bc[starsInBArray] = p->c;

				if (p->distance > p->changepoint[p->changemode]){
					(p->c)++;	// increment character for next star size
					(p->changemode)++;
				}

				// clipping is off, so we must not draw outside view.
				// replace stars that go too far...
				if (p->draw->x < smallScreenRect.origin.x+3 ||
					p->draw->y < smallScreenRect.origin.y+3 ||
					p->draw->x+7 > smallScreenRect.size.width +
					smallScreenRect.origin.x - 3 ||
					p->draw->y+7 > smallScreenRect.size.height +
					smallScreenRect.origin.y - 3){
					
					[self replaceStarAt:i];
					//printf("replaced star at %d\n",i);
					mustErase = YES;
				}

				w[starsInWArray] = *p->draw;
				wc[starsInWArray] = p->c;

				if(mustErase){
					testP.x = b[starsInBArray].x;
					testP.y = b[starsInBArray].y;
					test.erase = &testP;
					if ([self allowBStars:&test])starsInBArray++;
				}
				else if ([self allowBStars:p])starsInBArray++;
				if ([self allowWStars:p]) starsInWArray++;

					
					
				t = p->draw; p->draw = p->erase; p->erase = t;
			}
		}

		bc[starsInBArray] = wc[starsInWArray] = 0;	//null terminate string
		if (starsInBArray){
			for (i=0; i<(starsInBArray-1); i++){
				bOffsets[i].x = b[i+1].x - b[i].x;
				bOffsets[i].y = b[i+1].y - b[i].y;
			}
			bOffsets[i].x = bOffsets[i].y  = 0;

			count = 0;
			while (count < starsInBArray){
				char tc;
				int j;
				// You get the best performance if you put out all the stars
				// at once.  This causes noticable flicker, so I put out 
				// 100 of the stars per iteration.  This gives reasonable speed
				// and flicker is hardly noticable.  Besides, stars
				// _should_ flicker a little...
		
				int t = (starsInBArray - count);
				i = (t < STARSPERIT)?t:STARSPERIT;
				j = i + count;
			
				PSsetgray(NX_BLACK);
				tc = bc[j]; bc[j] = 0;
				PSWXYShow(b[count].x, b[count].y, &bc[count], 
				(float *)(&bOffsets[count].x), i*2);
				bc[j] = tc;
				count += STARSPERIT;

			}
		}

		if (starsInWArray){
			for (i=0; i<(starsInWArray-1); i++){
				wOffsets[i].x = w[i+1].x - w[i].x;
				wOffsets[i].y = w[i+1].y - w[i].y;
			}
			wOffsets[i].x = wOffsets[i].y  = 0;

			count = 0;
			while (count < starsInWArray){
				char tc;
				int j;
				// You get the best performance if you put out all the stars
				// at once.  This causes noticable flicker, so I put out 
				// 100 of the stars per iteration.  This gives reasonable speed
				// and flicker is hardly noticable.  Besides, stars
				// _should_ flicker a little...
		
				int t = (starsInWArray - count);
				i = (t < STARSPERIT)?t:STARSPERIT;
				j = i + count;
			

				PSsetgray(NX_WHITE);
				tc = wc[j]; wc[j] = 0;
				PSWXYShow(w[count].x, w[count].y, &wc[count], 
				 (float *)(&wOffsets[count].x), i*2);
				wc[j] = tc;
				count += STARSPERIT;
			}
		}		
		if((stopTest >= (nstars - 1)) && stopping && !stopped){
			stopped = YES;
			[bodyController starsStopped];
		}
	screenChangeTime++;
	if(screenChangeTime > SCREENMOVE){
		[self drawScreen];
	    [starShip viewScreenResized:&smallScreenRect];
	}

	lightChangeTime++;
	if(lightChangeTime > LIGHTMOVE){
	    lightChangeTime = 0;
	    [self drawScreenLights];

	}
	return self;
}



// make the actual image 8 pixels bigger in each direction
// then make the avoidance rect -8 pixels to width and height of image
//for example 
// if the image you want to avoid is 5 pixels square
// make image 21 X 21 with a black background and
//center the actual image. make the avoidance rect
//0,0,13,13
//The reason for this is that the stars are a max of
//7 X 7 pixels and drawn from lower left corner

- (BOOL) allowWStars:(const STAR *)p
{
// avoidance rectangles are set by message from body objects

int ii;
NXRect avoid;
BOOL allow;

	allow = YES;
	// return yes if point is outside of all avoidance rectangles
	for(ii=0;ii < [avoidStorage count];ii++){
			avoid = ((AvoidStruct *)[avoidStorage elementAt:ii])->avoid;
			
			// just return if voidRect not set
			if ((!avoid.size.width) ||
				p->draw->x < avoid.origin.x ||
				p->draw->y < avoid.origin.y ||
				p->draw->x > avoid.origin.x+avoid.size.width ||
				p->draw->y > avoid.origin.y+avoid.size.height){
				
			}
			else
				allow = NO;	
	}	
	return allow;
}


//check the ones that need erasing
//returns yes if outside avoidance rect
- (BOOL) allowBStars:(const STAR *)p
{
int ii;
NXRect avoid;
BOOL allow;

	allow = YES;
	// return yes if point is outside of all avoidance rectangles
	for(ii=0;ii < [avoidStorage count];ii++){
			avoid = ((AvoidStruct *)[avoidStorage elementAt:ii])->avoid;
			
			// just return if voidRect not set
			if ((!avoid.size.width) ||
				p->erase->x < avoid.origin.x ||
				p->erase->y < avoid.origin.y ||
				p->erase->x > avoid.origin.x+avoid.size.width ||
				p->erase->y > avoid.origin.y+avoid.size.height){
				
			}
			else
				allow = NO;	
	}	
	return allow;
}


- initFrame:(const NXRect *)frameRect
{

	[super initFrame:frameRect];
	// these were in original code - I don't know if they do anything
	
	//[self allocateGState];		// For faster lock/unlockFocus
	//[self setClipping:NO];		// even faster...

	loadPSProcedures();
	loadSpaceProcedures();
	PSWDefineFont("StarFont");
	PSselectfont("StarFont", 1.0);

	// not really needed
	// for completeness - view gets resized right away by sizeTo method
	[self setSmallScreenRect:frameRect];
	[self setRadius];

	screenChangeTime = 0;
	screenResizeLimit = 0;
	screenResizeDir = 1;
	lightChangeTime = 0;
	lightLimit = 16;
	stopDelta = 1.0;
	stopped = NO;
	stopping = NO;
	hidden = NO;
	

	lightInc = floor(smallScreenRect.size.width/60);
	lights.l_x_pos = smallScreenRect.size.width/2 - 4 +
 	smallScreenRect.origin.x;
	lights.r_x_pos = smallScreenRect.size.width/2 + 4 +
	smallScreenRect.origin.x;
	lights.y_pos = floor(smallScreenRect.origin.y/2);

	[self setStarsStopped]; 	//set stars stopped at the beginning

	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	// this drawself doesn't really draw the view at all.
	// in fact it just promotes the window to screen depth...

	NXRect t = {0,0,1,1};

	PSsetrgbcolor(.333,.222,.111);
	NXRectFill(&t);	//yucky trick for window depth promotion!
	PSsetgray(NX_BLACK); NXRectFill(&t);
	PSselectfont("StarFont", 1.0);

	return self;
}

- 	setSmallScreenRect:(const NXRect *)newRect;
{
	//sets variable to bounds of the small window in the viewscreen rect

	smallScreenRect.size.width = newRect->size.width;
	smallScreenRect.size.height = newRect->size.height;
	smallScreenRect.origin.x = newRect->origin.x;
	smallScreenRect.origin.y = newRect->origin.y;

	return self;
}
- sizeTo:(NXCoord)width :(NXCoord)height
{
NXSize delta;

	[super sizeTo:width :height];

	delta.width = floor(width * .15);
	delta.height = floor(height * .15);

	smallScreenRect.size.width = width - delta.width;
	smallScreenRect.size.height = height - delta.height;
	smallScreenRect.origin.x = (delta.width / 2);
	smallScreenRect.origin.y = (delta.height / 2);

	screenChangeTime = SCREENMOVE;  //put them on screen
	lightChangeTime = LIGHTMOVE;
	lightLimit = 16;

	lightInc = floor(smallScreenRect.size.width/60);
	lights.l_x_pos = smallScreenRect.size.width/2 - 4 +
 	smallScreenRect.origin.x;
	lights.r_x_pos = smallScreenRect.size.width/2 + 4 +
	smallScreenRect.origin.x;
	lights.y_pos = floor(smallScreenRect.origin.y/2);

	if (oldSize.width != bounds.size.width ||
			oldSize.height != bounds.size.height)
	{
		oldSize.width = bounds.size.width;
		oldSize.height = bounds.size.height;

		nstars = 0;
		[self setRadius];
		[self display];
		[starShip viewScreenResized:&smallScreenRect];
		
	}
	
	return self;
}

// only call addStar if there is room in the stars array!
- addStar
{
	[self replaceStarAt:nstars++];
	return self;
}

- replaceStarAt:(int)index
{
	float dist, t;
	int tries = 0;
	STAR *p = &stars[index];
	BOOL inBounds;

	p->draw = &p->r1;
	p->erase = &p->r2;
	

		do {
			p->theta = randBetween(0,(2*PI));

		if (tries++ < 3) p->distance = randBetween(1, radius);
		else p->distance = randBetween(1, p->distance);

		inBounds = YES;
		[self convertToXY:p];

		if (p->draw->x < smallScreenRect.origin.x+3 ||
			p->draw->y < smallScreenRect.origin.y+3 ||
			p->draw->x + 7 > smallScreenRect.size.width +
			smallScreenRect.origin.x - 3 ||
			p->draw->y + 7 > smallScreenRect.size.height +
			smallScreenRect.origin.y - 3)
		{
			inBounds = NO;
		}
	} while (!inBounds);
		//take out
	if(stopping){
		p->delta = 0.0;
		p->ddelta = 1.0;
	}
	else{
		p->delta = .2;
		//p->ddelta = randBetween(1.0, 1.1);
		p->ddelta = randBetween(1.0, 1.05);

	}

	t = randBetween(0, (0.42*radius));

	dist = MAX(20,t);
	p->changepoint[0] = p->distance + 5;			// to b
	p->changepoint[1] = p->changepoint[0] - 5 + dist + dist;	// to c
	p->changepoint[2] = p->changepoint[1] + dist;	// to d
	p->changepoint[3] = p->changepoint[2] + dist;	// to e
	p->changepoint[4] = p->changepoint[3] + dist;	// to f
	p->changepoint[5] = 100000;						// never change to g

	p->changemode = 0;
	
	if ((++toggle) & 1) p->c = 'a';
	else p->c = 'g';

	p->r2 = p->r1;

	return self;
}

- drawScreen
{
	screenChangeTime = 0;

	PSscreenBorder(smallScreenRect.origin.x,
        smallScreenRect.origin.y,
        smallScreenRect.size.width + smallScreenRect.origin.x,
        smallScreenRect.size.height + smallScreenRect.origin.y,
	smallScreenRect.origin.y-2.5,5.0,0.0);

	if(screenResizeDir == 1){
	    smallScreenRect.size.width++;
	    smallScreenRect.size.height++;
	    smallScreenRect.origin.x--;
	    smallScreenRect.origin.y--;
	}
	else{
	    smallScreenRect.size.width--;
	    smallScreenRect.size.height--;
	    smallScreenRect.origin.x++;
	    smallScreenRect.origin.y++;
	}    

	PSscreenBorder(smallScreenRect.origin.x,
        smallScreenRect.origin.y,
        smallScreenRect.size.width + smallScreenRect.origin.x,
        smallScreenRect.size.height + smallScreenRect.origin.y,
	smallScreenRect.origin.y-2.5,5.0,1.0);
	
	screenResizeLimit++;
	if(screenResizeLimit > 7){
	    if(screenResizeDir)
		 screenResizeDir = 0;
	    else
		  screenResizeDir = 1;    //reverse the values
	    screenResizeLimit = 0;
	}
	
	return self;	        
}


- drawScreenLights
{
float red,green,blue;

        PSscreenLights(lights.l_x_pos,lights.r_x_pos,
	lights.y_pos,0.0,0.0,0.0);
	
	lights.l_x_pos -= lightInc;
	lights.r_x_pos += lightInc;

	lightLimit++;
	if(lightLimit > 15){
	    lightLimit = 0;

	lights.l_x_pos = smallScreenRect.size.width/2 - 4 +
 	smallScreenRect.origin.x;
	lights.r_x_pos = smallScreenRect.size.width/2 + 4 +
	smallScreenRect.origin.x;
	lights.y_pos = smallScreenRect.origin.y/2;
	}
	red = randBetween(0.1,1.0);  //make sure never black
	green = randBetween(0.0,1.0);
	blue = randBetween(0.0,1.0);

	PSscreenLights(lights.l_x_pos,lights.r_x_pos,
	lights.y_pos,red,green,blue);
        return self;	        
}

//this causes stars to slow down and stop - continue being displayed
- stopStars
{
	// needs to be < 1 so that the stars stop eventually
	// this will control rate of stoppage
	stopDelta = 0.90;
	stopping = YES;
	return self;

}
// causes stars to disappear altogether - 
//only works if stars are stopped
//send a setStarsStopped message first
- hideStars
{
	[self setStarsStopped];
	PSsetgray(0);
	NXRectFill(&smallScreenRect);
	nstars = 0;
	hidden = YES;
	return self;
}



- startStars
{
STAR *p;
int ii;
	hidden = NO;
	for (ii=0; ii < NSTARS; ii++){
		p = &stars[ii];
		p->delta = 0.2;
	}
	stopDelta = 1.0;
	stopping = NO;
	stopped = NO;
	return self;
}

- (BOOL)isStopped
{
	return stopped;
}
- (BOOL)isStopping
{
	return stopping;
}

- setRadius
{
	float x = smallScreenRect.size.width;
	float y = smallScreenRect.size.height;
	radius = (sqrt(x*x + y*y))/2;
	return self;
}

- (const char *)windowTitle
{
	return "The Final Frontier";
}

- setAvoidRect:(Storage *)storage	
{
	avoidStorage = storage;
	return self;
}

- didLockFocus
{
	PSselectfont("StarFont", 1.0);
	return self;
}

- (BOOL)useBufferedWindow
{	return NO;
}

- inspector:sender
{
    return [sender spaceInspector];
}

- (BOOL)ignoreMouseMovement
{	return NO;
}

- inspectorWillBeRemoved
{	return self;	// just a prototype
}

- inspectorInstalled
{	return self;	// just a prototype
}

- setStarShipOutlet:(id)outlet
{
	starShip = outlet;
	return self;
}
- setBodyControllerOutlet:(id)outlet
{
	bodyController = outlet;
	return self;
}
- setStarSpeed:sender
{
	starSpeed = [sender floatValue];
	return self; 	
}

//this sets stars instantly stopped
- setStarsStopped
{
STAR *p;
int ii;
	nstars = 0;
	for(ii=0;ii < NSTARS;ii++){
		[self addStar];
		p = &stars[ii];
		p->delta = 0.0;
	}
	
	stopping = YES;
	stopped = YES;
	return self;
}

@end


@implementation View(nonretainedFillMethod)

// I add this method as a category of View to be sure that all
// my views implement it.  I really want to use nonretained windows
// but they are drawn via drawSelf at all kinds of goofy times.  It
// seems like the kit kind of throws up its hands when it doesn't have
// a buffer to draw from, so you get a lot more drawSelfs than you need,
// and sometimes you don't get them when you really want them.  I know
// when I need the background filled in black, so I factor that out of
// my drawSelf and then drawself only draws things that are already on
// screen so you don't see it happen.  I will only call this method on
// a nonretained (and full screen) window.

- fillBoundsWithBlack
{
	if ([self canDraw])
	{
		[self lockFocus];
		PSsetgray(NX_BLACK);
		NXRectFill(&bounds);
		[self unlockFocus];
	}
	return self;
}

@end
