//  SpaceView.m
//
//  This class implements the flying starfield screen saver view.
//
//  Another variation on the space theme, slightly faster and different.


#import "Space2View.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>

#define PI (3.141592653589)

NXSize sizeArray[] = {{1,1},{2,1},{2,2},{3,2},{3,3},{4,3},{4,4}};

@implementation Space2View

extern float randBetween(float a, float b);

//takes theta and distance and stuffs it into x &y for *p
- convertToXY:(STAR *)p
{
	p->draw.origin.x = floor(bounds.size.width / 2 + (p->distance * cos(p-> theta)));
	p->draw.origin.y = floor(bounds.size.height / 2 + (p->distance * sin(p-> theta)));
	return self;
}


- oneStep
{
	int i, count, starsInArray = 0;
	STAR *p;
	
	if (nstars < NSTARS) [self addStar];

	for (i=0; i<nstars; i++)
	{
		p = &stars[i];
		p->distance += p->delta;
		p->delta *= p->ddelta;
		p->theta += 0.012;
		if (p->theta > (2*PI)) p->theta -= (2*PI);

		[self convertToXY:p];

		// only draw the star if it moved > 1 pixel
		if (p->draw.origin.x != p->erase.origin.x || 
			p->draw.origin.y != p->erase.origin.y)
		{
			// add star to the erasure array
			b[starsInArray] = p->erase;

			if (p->distance > p->changepoint[p->changemode])
			{
				(p->changemode)++;
				p->draw.size = sizeArray[p->changemode];
			}

			// clipping is off, so we must not draw outside view.
			// replace stars that go too far...
			if (p->draw.origin.x < 0 ||
				p->draw.origin.y < 0 ||
				p->draw.origin.x + 4 > bounds.size.width ||
				p->draw.origin.y + 4 > bounds.size.height)
			{
				[self replaceStarAt:i];
			}

			w[starsInArray++] = p->draw;
			
			p->erase = p->draw;
		}
	}

	if (starsInArray)
	{
		count = 0;
		while (count < starsInArray)
		{
			// 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 = (starsInArray - count);
			i = (t < STARSPERIT)?t:STARSPERIT;
			
			PSsetgray(NX_BLACK);
			NXRectFillList(&b[count],i);
			
			PSsetgray(NX_WHITE);
			NXRectFillList(&w[count],i);
			
			count += STARSPERIT;
		}
	}

	return self;
}

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	[self allocateGState];		// For faster lock/unlockFocus
	[self setClipping:NO];		// even faster...
	[self setRadius];

	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(1,0,0);
	NXRectFill(&t);	//yucky trick for window depth promotion!
	PSsetgray(NX_BLACK); NXRectFill(&t);
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	if (bounds.size.width == width && bounds.size.height == height) return self;

	[super sizeTo:width :height];

	[self setRadius];
	nstars = 0;
	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;

	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.origin.x < 0 || p->draw.origin.y < 0 ||
			p->draw.origin.x + 4 > bounds.size.width ||
			p->draw.origin.y + 4 > bounds.size.height)
		{
			inBounds = NO;
		}
	} while (!inBounds);

	p->draw.size = sizeArray[0];

	p->delta = (0.3);

//	p->ddelta = randBetween(1.0, 1.1);
	p->ddelta = randBetween(1.0, 1.15);

	t = randBetween(0, (0.42*radius));
	dist = MAX(20,t);
	p->changepoint[0] = p->distance + 5;			// 2nd
	p->changepoint[1] = p->changepoint[0] - 5 + dist + dist;	// 3rd

	p->changepoint[2] = p->changepoint[1] + dist;		// 4th
	p->changepoint[3] = p->changepoint[2] + dist;		// 5th
	p->changepoint[4] = p->changepoint[3] + dist;		// 6th
	p->changepoint[5] = 100000;				// never change to 7th

	p->changemode = 0;
	
	p->erase = p->draw;

	return self;
}

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

@end
