
// Copyright (c) 1997 by Don Yacktman
// All rights reserved.
// Permission to use this code in your application, whether
// commercial, shareware, or freeware, is granted.
// This notice may not be removed or altered.


#import "Spewer.h"
#import "FlyingWindow.h"
#import "Animator.h"
#import <libc.h>

@implementation Spewer:NSObject

- init
{
	[super init];
    // seed the random number generator with the current time.
    // This call should really only be done once, before the random
	// number generator is ever used.  Since this object is typically
	// only instantiated once so that's not a problem.
	srandom(time(0));
    // clear this out, since we've not been started yet.
	initiator = nil;
    // get an animator instance to help us run the animation.
	animator = [[Animator allocWithZone:[self zone]] initChronon:0.05
			adaptation:0.0 target:self action:@selector(move:)
			autoStart:NO eventMask:0];
    // set up an extra list used for swapping during animation
	_extraList = [[NSMutableArray allocWithZone:[self zone]] init];
	// set up the list of extra, idle windows
    idleWindows = [[NSMutableArray allocWithZone:[self zone]] init];
    // set up the list of active windows
    windowList = [[NSMutableArray allocWithZone:[self zone]] init];
    // it is not currently OK to create ne windows.
	createOK = NO;
	return self;
}

- start:sender
{
    // start the animation.  It is OK to instantiate new windows.
	createOK = YES;
    // the sender is the one who started us off, so cache that info
	initiator = sender;
    // get a new window so that we have at least one window to animate
	[self addWindow];
    // start the animation
	[animator startEntry];
    return self;
}

- (void)stop:(id)sender
{
    // we need to stop the animation, but need all the windows to
	// fall off the screen first.  So we tell ourself that the
	// initiator is gone and disallow creation of new windows.
	createOK = NO;
	initiator = nil;
}

- (BOOL)windowShouldClose:(id)sender
{
    // if the window is closing, then we automatically stop the
	// animation.  Make sure we are the winow's delegate (hooked
	// up as such in IB) or else we won't get this message!
	[self stop:self];
    return YES;
}

- (void)dealloc
{ // clean out any windows, remove the timed antry and free everything
	int i;
	// stop the animation and get rid of the animator
    [animator stopEntry];
    [animator release];
    animator = nil;
    // get rid of all our windows--get them off the screen
    for (i=0; i<[windowList count]; i++) {
        [[windowList objectAtIndex:i] orderOut:self];
    }
    // free the active window list and its contents
    [windowList release];
    windowList = nil;
	// free the idle window list and its contents
    [idleWindows release];
    idleWindows = nil;
	[super dealloc];
	return;
}

- addWindow
{	// put up a new window on the screen
	NSPoint theCenterPoint;
	NSRect senderFrame;
    id newWindow = nil;

    // if we're not allowed to start any new windows, then return immediately
	if (!createOK) return nil;
    // find the point where the new window should appear on the screen.
    // find out the frame of the control which sent the -start: message
	senderFrame = [initiator frame];
    // Get the center point of the initiator's frame
	theCenterPoint.x = NSMinX(senderFrame) + NSWidth(senderFrame) / 2;
	theCenterPoint.y = NSMinY(senderFrame) + NSHeight(senderFrame) / 2;
    // Convert the point from view to screen coordinates
	theCenterPoint = [[initiator window] convertBaseToScreen:theCenterPoint];
	// recycle a window if there are any idle ones; otherwise make a new one
    // get an idle window
    newWindow = [idleWindows lastObject];
	// if we got one, then take it out of the list of idle windows
    if (newWindow) [idleWindows removeLastObject];
    // if there weeren't any idle windows, then create a new one and set it
    // to appear at the point calculated above.  If we found an idle window,
    // then get it to set itself up as the same point.
	if (!newWindow) newWindow = [[FlyingWindow allocWithZone:[self zone]]
			initAt:&theCenterPoint];
	else [newWindow reInitAt:&theCenterPoint];
    // add the new window to the list of active windows.
	[windowList addObject:newWindow];
	return self;
}

- move:sender
{
    // get a new active list
	int i; id newList = _extraList;

	// add any new windows spewing about if necessary...
    // if we don't have too many windows out already, then check
	// against a random number to decide if we should add a new
	// window.  The larger "FREQUENCY" is, the longer the average
	// interval will be between new windows coming onto the screen.
	if (([windowList count] < MAX_SPEWS) && !(random() % FREQUENCY)) {
		[self addWindow];
	}

	// move all the active windows
	for (i=0; i<[windowList count]; i++) [[windowList objectAtIndex:i] move];

	// remove any windows that left the screen
	for (i=0; i<[windowList count]; i++) {
        id theWindow = [windowList objectAtIndex:i];
        // if the window is on the screen, transfer it to a new active list
		if ([theWindow onScreen]) [newList addObject:theWindow];
        // windows that left the screen are taken out of the animation and moved to the idle list
		else {
			[theWindow orderOut:self];
			[idleWindows addObject:theWindow]; // put in recycler
		}
	}
    // make sure no stray windows were left behind.
    [windowList removeAllObjects];
    // get rid of the old active list and set up the new one.
	_extraList = windowList;
	windowList = newList;
    // if there are no more windows in the active list, and it is no longer
	// OK to create new ones, then the animation has ended, so we'll stop
	// the timer.
	if (([windowList count] < 1) && !(createOK))
		// stop TE after all windows gone
		[animator stopEntry];
	return self;
}

@end