//  SpaceView.m
//
//  This class implements the flying starfield screen saver view.
//
//  You may freely copy, distribute, and reuse the code in this example.
//  NeXT disclaims any warranty of any kind, expressed or  implied, as to its
//  fitness for any particular use.


#import "SpaceView.h"
#import "psfuncts.h"

#import <appkit/Application.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)


		// used in producing timed events
static void AnimateSpace();
static void AnimateSpaceIT();


		// This should return a float between 0 and 1
float frandom()
{
	float val = (random() & 0x7fffffff);
	val /= 0x7fffffff;
	return val;
}
float randBetween(float a, float b)
{
	float val, scale, t;

	if (a > b)
	{	t = a; a = b; b = t;
	}
	
	scale = (b-a);
	val = scale * frandom();
	return (a + val);
}

@implementation SpaceView

		//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, count, starsInArray = 0;
	STAR *p;
	NXPoint *t;
	
	if (nstars < NSTARS) [self addStar];

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

		[self convertToXY:p];

		// only draw the star if it moved > 1 pixel
		if (p->draw->x != p->erase->x || 
			p->draw->y != p->erase->y)
		{
			BOOL mustErase = NO;
			// add star to the erasure array
			b[starsInArray] = *p->erase;
			bc[starsInArray] = 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 < 0 ||
				p->draw->y < 0 ||
				p->draw->x + 7 > bounds.size.width ||
				p->draw->y + 7 > bounds.size.height)
			{
				[self replaceStarAt:i];
				mustErase = YES;
			}

			w[starsInArray] = *p->draw;
			wc[starsInArray] = p->c;
			
			if (mustErase || [self allowStars:p]) starsInArray++;
		
			t = p->draw; p->draw = p->erase; p->erase = t;
		}
	}

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

		count = 0;
		while (count < starsInArray)
		{	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 = (starsInArray - 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;
			
			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;
		}
	}

	return self;
}

		// returns yes if the star is outside the avoidance rectangle
		// this is really fast and loose but it works acceptibly well
		// ps I could just use NXIntersectsRect() but I want to avoid
		// trap overhead.  Call me paranoid...
- (BOOL) allowStars:(const STAR *)p
{
				// just return if voidRect not set
	if ((!voidRect.size.width) ||
		p->draw->x < voidRect.origin.x ||
		p->draw->y < voidRect.origin.y ||
		p->draw->x+7 > voidRect.origin.x+voidRect.size.width ||
		p->draw->y+7 > voidRect.origin.y+voidRect.size.height ||

		p->erase->x < voidRect.origin. x ||
		p->erase->y < voidRect.origin. y ||
		p->erase->x+7 > voidRect.origin.x+voidRect.size.width ||
		p->erase->y+7 > voidRect.origin.y+voidRect.size.height) return YES;

	return NO;
}

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

	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);

	PSselectfont("StarFont", 1.0);

	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];

	if (oldSize.width != bounds.size.width ||
			oldSize.height != bounds.size.height)
	{
		oldSize.width = bounds.size.width;
		oldSize.height = bounds.size.height;
		[self setRadius];
		nstars = 0;
		[self display];
	}
	
	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 < 0 || p->draw->y < 0 ||
			p->draw->x + 7 > bounds.size.width ||
			p->draw->y + 7 > bounds.size.height)
		{
			inBounds = NO;
		}
	} while (!inBounds);

	p->delta = (0.1);

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



	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;
}

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

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

- setVoidRect:(const NXRect *)r
{
	voidRect = *r;
	return self;
}

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

- (BOOL)useBufferedWindow
{	return NO;
}

- inspector:sender
{
	[self display];

    return self;
}

- (BOOL)ignoreMouseMovement
{	return NO;
}

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

- inspectorInstalled
{	return self;	// just a prototype
}
//*****************************************************************************
//
// 		circular clipping path 
//
//*****************************************************************************

- clipToFrame:(const NXRect *)frameRect
{
float x, y, cradius;

    				// Center the circle and pick an appropriate radius
    x = frameRect->origin.x + frameRect->size.width/2.0;
    y = frameRect->origin.y + frameRect->size.height/2.0;
    cradius = frameRect->size.height/2.0;

    				// Create a circular clipping path
    PSnewpath();
    PSarc(x, y, cradius, 0.0, 360.0);
    PSclosepath();
    PSclip();

    return self;
}


@end



		// this class is only used in the inspector, it animates
		// when it draws itself.

@implementation StaticSpaceView

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	int i;
	
	if (!rects || !rectCount) return self;
	
	PSselectfont("StarFont", 1.0);

	PSsetgray(NX_BLACK);
	NXRectFill(rects);
	
	for (i=0; i<20; i++)
	{
		[self oneStep];
		[[self window] flushWindow];
		NXPing();
	}

	return self;
}

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	[super setClipping:YES];

	while (nstars < NSTARS) [self addStar];

	if(!aniSpaceTag)  
		aniSpaceTag = DPSAddTimedEntry(			// register function Animate
			0.08, 								// to be called every period of 		
			(DPSTimedEntryProc)AnimateSpace, 	// arg0
			(id)self, 
			NX_BASETHRESHOLD);

	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];

	nstars = 0;
	while (nstars < NSTARS) [self addStar];	
	return self;
}
//************************************************************************
//
// 		remove the timed entry when we exit
//
//************************************************************************
 
- free
{
	if (aniSpaceTag)  
		DPSRemoveTimedEntry (aniSpaceTag);
	aniSpaceTag = 0;
		
    return [super free];
}
@end

//************************************************************************
//
//		This fucntion is registered by DPSaddtimedentry.
//		It is subsequently called every period t as registered  
//		in arg 0 of DPSaddtimedentry.
//
//************************************************************************

static void AnimateSpace(DPSTimedEntry time_tag, double now, id self)
{
	[self display];
}




@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


#import "InactivityTimer.h"
#import <appkit/Application.h>


@implementation StaticSpaceITimerView

//************************************************************************
//
//		sent to us after nib objects are unarchived and init'd 
//
//************************************************************************

- awakeFromNib
{
	[secTillTimeoutField setIntValue:10];

	return self;
}

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	[super setClipping:YES];

	while (nstars < NSTARS2) [self addStar];

	if(!aniSpaceITimerTag)  
		aniSpaceITimerTag = DPSAddTimedEntry(	// register function Animate
			0.08, 								// to be called every period of 		
			(DPSTimedEntryProc)AnimateSpaceIT, 	// arg0
			(id)self, 
			NX_BASETHRESHOLD);

	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	int i;
	
	if (!rects || !rectCount) return self;
	
	PSselectfont("StarFont", 1.0);

	PSsetgray(NX_BLACK);
	NXRectFill(rects);
	
	for (i=0; i<20; i++)
	{
		[self oneStep];
		[[self window] flushWindow];
		NXPing();
	}

	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];

	nstars = 0;
	while (nstars < NSTARS2) [self addStar];	
	return self;
}
//************************************************************************
//
// 		remove the timed entry when we exit
//
//************************************************************************
 
- removeTimedEntry
{
	if (aniSpaceITimerTag)  
		DPSRemoveTimedEntry (aniSpaceITimerTag);
	aniSpaceITimerTag = 0;
		
	return self;
}

//************************************************************************
//
// 		countdown to inactivity timeout
//
//************************************************************************
 
- countdown
{
	time(&ltime);						// Get time and place in time_t

	if(ltime > preltime)
		{
		if((seconds = [secTillTimeoutField intValue]) == 0)
			[[window delegate] continue:self];
		else
			[secTillTimeoutField setIntValue:(seconds - 1)];
		[secTillTimeoutField display];
		preltime = ltime;
		}
		
    return self;
}


@end

//************************************************************************
//
//		This fucntion is registered by DPSaddtimedentry.
//		It is subsequently called every period t as registered  
//		in arg 0 of DPSaddtimedentry.
//
//************************************************************************

static void AnimateSpaceIT(DPSTimedEntry time_tag, double now, id self)
{
	[self countdown];
	[self display];
}
