/*
    The animation guts from a freely distributable X program:
	xsperm.c
	Drew Olbrich, Febrary 1991
	Note --  This code originally served as a demonstration
	of how to do animation under X.  The "guts" of the program
	which draws the sperm are consequently located in one huge
	chunk in the update_display() routine, and can be easily
	cut out.

    The animation function wrapped in a NeXTstep View subclass by Ali Ozer, May 91
	Very minor changes so this thing works as a screen saver module by sam streeper,
	August 91
    The "oneStep" method computes new locations.
*/

#import "SpermViewPart.h"
#import "Thinker.h"
#import <math.h>
#import <libc.h>
#import <dpsclient/wraps.h>		// For PS and DPS function prototypes
#import <appkit/color.h>		// For color stuff
#import <appkit/nextstd.h>		// For MIN MAX
#import <appkit/Button.h>		// intValue, etc
#import <appkit/Application.h>  // For NX_BASETHRESHOLD and peek event
#import <appkit/Window.h>

#define COUNT 200

#define VEC_DOT(x, y) (x[0]*y[0] + x[1]*y[1])
#define VEC_LEN(x) (sqrt(x[0]*x[0] + x[1]*x[1]))

#define VEC_SET(x, a, b) x[0] = a, x[1] = b
#define VEC_COPY(y, x) y[0] = x[0], y[1] = x[1]
#define VEC_NEG(x) x[0] = -x[0], x[1] = -x[1]
#define VEC_ADD(z, x, y) z[0] = x[0] + y[0], z[1] = x[1] + y[1]
#define VEC_SUB(z, x, y) z[0] = x[0] - y[0], z[1] = x[1] - y[1]
#define VEC_MULT(x, a) x[0] *= a, x[1] *= a
#define VEC_DIV(x, a) x[0] /= a, x[1] /= a
#define VEC_ADDS(z, x, a, y) z[0] = x[0] + (a)*y[0], z[1] = x[1] + (a)*y[1]
#define VEC_NORM(x) { double l = VEC_LEN(x); VEC_DIV(x, l); }

#define MINRAD 0.1
#define RADSTEP 2.0
#define MAXRAD (MINRAD * RADSTEP * RADSTEP * RADSTEP * RADSTEP * RADSTEP * RADSTEP)
#define INITRAD (MINRAD * RADSTEP * RADSTEP * RADSTEP)

// RANDINT(n) returns an integer 0..n-1
// RANDFLOAT(f) returns a float [0..f] (inclusive on both ends)

#define RANDINT(n) (random() % (n))
#define RANDFLOAT(f) (((f) * (float)(random() & 0x0ffff)) / (float)0x0ffff)

@implementation SpermView

- initFrame:(const NXRect *)rect
{
    id retVal = [super initFrame:rect];

	[self allocateGState];		// For faster lock/unlockFocus

    dir = 1.0;
    rad = INITRAD;
    count = 200;
    useColors = NO;
    dontErase = NO;
    color = NX_COLORWHITE;
	alreadyInitialized = NO;
	randCount1 = 100;
	randCount2 = 200;
	
    uPath = newUserPath();

    return retVal;
}

- (void)initializeLine:(int)i
{
    double angle = RANDFLOAT(10.0) + 5.0;
    prevX[i][0] = x[i][0] = (double) (RANDINT((int)NX_WIDTH(&bounds)));
    prevX[i][1] = x[i][1] = (double) (RANDINT((int)NX_HEIGHT(&bounds)));
    v[i][0] = RANDFLOAT(2.0) - 1.0;
    v[i][1] = RANDFLOAT(2.0) - 1.0;	
    sine[i] = sin(angle*M_PI/180.0);
    cosine[i] = cos(angle*M_PI/180.0);
    vel[i] = RANDFLOAT(4.0) + 4.0;
    VEC_NORM(v[i]);
}

- (void)getFocusFromEvent:(NXEvent *)event
{
    NXPoint loc = event->location;
    [self convertPoint:&loc fromView:nil];
    mouse[0] = loc.x;
    mouse[1] = loc.y;
}

- (BOOL)acceptsFirstMouse
{	return YES;
}

- mouseDown:(NXEvent *)event
{
    [self getFocusFromEvent:event];
    return self;
}

- effectOne
{
    VECTOR y;
    int i;

    dir *= -1.0;
    for (i = 0; i < COUNT; i++) {
	VEC_COPY(y, v[i]);
	if (dir == -1.0) {
	    v[i][0] = y[1];
	    v[i][1] = -y[0];
	} else {
	    v[i][0] = -y[1];
	    v[i][1] = y[0];
	}
    }
    return self;
}

- effectTwo
{
    int i;
    for (i = 0; i < COUNT; i++) {
	v[i][0] = -v[i][0];
    }
    return self;
}

- effectThree
{
    int i;
    for (i = 0; i < COUNT; i++) {
	v[i][1] = -v[i][1];
    }
    return self;
}

- effectFour
{
    [self effectTwo];
    [self effectThree];
    return self;
}

- effectFive
{
    [self effectOne];
    [self effectFour];
    return self;
}

- effectSix
{
    rad = MIN(rad * RADSTEP, MAXRAD);
    return self;
}

- effectSeven
{
    rad = MAX(rad / RADSTEP, MINRAD);
    return self;
}

- doEffectNumber:(int)val
{
    switch (val) {
	case 0: [self effectOne]; break;
	case 1: [self effectTwo]; break;
	case 2: [self effectThree]; break;
	case 3: [self effectFour]; break;
	case 4: [self effectFive]; break;
	case 5: [self effectSix]; break;
	case 6: [self effectSeven]; break;
	default: break;
    }
    return self;
}

- oneStep
{
    int i, cnt;
    	POINT lLeft, uRight;
	NXRect eraseRect;

	uRight[0] = lLeft[0] = x[0][0];
	uRight[1] = lLeft[1] = x[0][1];

	for (i = 0; i < COUNT; i++) {
	    VECTOR w, y;
	    POINT p;
	    double r;

	    if (!dontErase) {
		for (cnt = 0; cnt < 2; cnt++) {
		    if (prevX[i][cnt] < lLeft[cnt]) lLeft[cnt] = prevX[i][cnt];
		    else if (prevX[i][cnt] > uRight[cnt]) uRight[cnt] = prevX[i][cnt];
		    if (x[i][cnt] < lLeft[cnt]) lLeft[cnt] = x[i][cnt];
		    else if (x[i][cnt] > uRight[cnt]) uRight[cnt] = x[i][cnt];
		}
	    }

	    prevX[i][0] = x[i][0];	/* old location */
	    prevX[i][1] = x[i][1];
    
	    VEC_SUB(w, x[i], mouse);
	    VEC_NORM(w);
	    VEC_COPY(y, w);
	    w[0] = y[0]*cosine[i] - dir*y[1]*sine[i];
	    w[1] = y[1]*cosine[i] + dir*y[0]*sine[i];
	    VEC_ADDS(p, mouse, rad*(160.0 - vel[i]*20.0), w);
    
	    VEC_SUB(w, p, x[i]);
	    r = VEC_LEN(w);
	    VEC_DIV(w, r);
    
	    VEC_ADDS(v[i], v[i], 1.0, w);
    
	    VEC_NORM(v[i]);
	    VEC_MULT(v[i], vel[i]);
    
	    VEC_ADD(x[i], x[i], v[i]);

	}
	if (!dontErase) {
	    NXSetRect (&eraseRect, lLeft[0], lLeft[1], uRight[0]-lLeft[0], uRight[1]-lLeft[1]);
	    NXInsetRect (&eraseRect, -1.0-lineWidth, -1.0-lineWidth);
		PSsetgray(0);
		NXRectFill(&eraseRect);
	}
	[self drawPath];

	if (--randCount1 < 0)
	{
		randCount1 = RANDINT(700);
		mouse[0] = randBetween(0,bounds.size.width);
		mouse[1] = randBetween(0,bounds.size.height);
	}
	if (--randCount2 < 0)
	{
		randCount2 = RANDINT(600);
		[self doEffectNumber:(randCount2 % 7)];
	}
	return self;
}

// Modify "orig" by upto plus or minus "by" keeping it in the specified range...

static float randMod(float orig, float by, float min, float max)
{
    orig = orig + RANDFLOAT(by * 2.0) - by;
    return (orig < min) ? min : ((orig > max) ? max : orig);
}

- drawPath
{
    int cnt;

    PSsetlinewidth (lineWidth);
    if (useColors) {
	color = NXConvertRGBToColor(randMod(NXRedComponent(color), 0.05, 0.0, 1.0), randMod(NXGreenComponent(color), 0.05, 0.0, 1.0), randMod(NXBlueComponent(color), 0.05, 0.0, 1.0));
    }
    NXSetColor (color);

    beginUserPath(uPath, NO);
    for (cnt = 0; cnt < COUNT; cnt++) {
	UPmoveto(uPath, (float)prevX[cnt][0], (float)prevX[cnt][1]);
	UPlineto(uPath, (float)x[cnt][0], (float)x[cnt][1]);
    }
    closePath(uPath);
    endUserPath(uPath, dps_ustroke);
    sendUserPath(uPath);

    return self;
}
    
- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	
	PSsetgray(NX_BLACK);
	NXRectFill(rects);
    [self drawPath];
	return self;
}

- newWindow
{
    mouse[0] = randBetween(0,bounds.size.width);
    mouse[1] = randBetween(0,bounds.size.height);

	return self;
}

- free
{
    freeUserPath(uPath);
    return [super free];
}

- setNumLines:(int)val
{
    int	   i;
    int    oldCount = count;

    // set the number of lines
    count = MIN(MAXCOUNT, MAX(val, 1));

    // initialize velocities & such
    for (i = oldCount; i < count; i++) {
	[self initializeLine:i];
    }

    [self display];

    return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	
	if (!alreadyInitialized)
	{	int i;
		mouse[0] = NX_MIDX(&bounds);
		mouse[1] = NX_MIDY(&bounds);

		for (i = 0; i < COUNT; i++) {
			[self initializeLine:i];
		}
		alreadyInitialized = YES;
	}

	[self newWindow];
	return self;
}

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

- (BOOL) useBufferedWindow;
{	return YES;
}

@end
