#import "WorldSpacePart.h"
#import "SpaceView.h"
#import "Thinker.h"
#import <appkit/NXImage.h>
#import <math.h>
#import <libc.h>
#import <dpsclient/wraps.h>
#import <objc/List.h>
#import <defaults.h>
#import <appkit/Panel.h>

@implementation WorldSpaceView

#define ASSUMED_INTERVAL 35

#define WIDTH 100
#define HEIGHT 100
#define COUNT 36
#define ACCEL (-2)
#define REBOUND (-1.3)

// This screen height value is not critical, though it will be used
// to determine how high the ball can go
#define SCREEN_HEIGHT 832
#define LAUNCH_SPEED (sqrt(fabs(2*ACCEL*(SCREEN_HEIGHT - HEIGHT))))
#define REAL_LAUNCH_SPEEd (sqrt(fabs(2*accel*(viewHeight - HEIGHT))))

#define MIN_X_SPEED (3)
#define MAX_X_SPEED (6)
#define ABS_MAX_X_SPEED (6)
#define MAX_Y_SPEED (LAUNCH_SPEED + 20)

#define BUFFER_WIDTH (WIDTH + ABS_MAX_X_SPEED + 1)
#define BUFFER_HEIGHT (HEIGHT + MAX_Y_SPEED + 1)

- oneStep
{
		NXRect black = {0,0,0,0};
		BRECT new;
		float scaledTime, calcYpos;

		then = now;
		now = currentTimeInMs();


    /* calculate new ball x position */
    xpos += [self timeCorrectedXSpeed];

    if (xpos < 0)               /* ball hit left edge */
    {   xspeed = -xspeed;
        if (viewWidth > WIDTH)
        {   spinDir = -spinDir;
		}
        xpos = 0;
	}
    else if (xpos > (viewWidth - WIDTH))        /* ball hit right edge */
    {   if (viewWidth > WIDTH)
        {
            xspeed = -[self getRandomXspeed];
            [self checkXspeed:&xspeed];
            xpos = (viewWidth - WIDTH);
		}
        else
        {   xspeed = xpos = 0;
		}
	}

    scaledTime = ((float)(now - then) / ASSUMED_INTERVAL);
    if (scaledTime > 1) scaledTime = 1;

    // calculate new ball vertical position
    calcYpos = ypos + (scaledTime*yspeed) + ((accel * scaledTime * scaledTime)/2);

    // change vertical ball speed to simulate gravity
    yspeed += (accel * scaledTime);

    if (calcYpos < (ypos - MAX_Y_SPEED)) calcYpos = ypos - MAX_Y_SPEED;
    else if (calcYpos > (ypos + MAX_Y_SPEED)) calcYpos = ypos + MAX_Y_SPEED;

    ypos = calcYpos;

    if (yspeed < -MAX_Y_SPEED) yspeed = -MAX_Y_SPEED;


    if (ypos <= 0)              /* ball hit bottom of window */
    {
        ypos = 0;

        if (viewHeight > HEIGHT) {
            if (reboundMode == DECREASING) {
                yspeed = lastLaunchSpeed = lastLaunchSpeed + rebound;
			}
            else {
                yspeed = lastLaunchSpeed = lastLaunchSpeed - (2*rebound);
			}

            if (yspeed <= 0) {
                yspeed = 0;
                reboundMode = INCREASING;   /* bounce height increases every b */
			}
            else if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED - (3*accel);
		}
        else yspeed = 0;

	}	

    else if (ypos >= (viewHeight - HEIGHT)) /* ball hit top of window */
    {   if (viewHeight > HEIGHT)
        {
            yspeed = accel;
            ypos = (viewHeight - HEIGHT);
            reboundMode = DECREASING;   /* bounce height decreases every bounce */
            spinDir = -spinDir;
		}
        else
        {   yspeed = ypos = 0;
		}
	}


    /* rotate the ball by selecting a new ball image to blit */
    /* we have an image of the ball in 10 different stages of rotation */

    [self incrementBallNumber];

    new.l = floor(xpos);
    new.b = floor(ypos);
    new.r = new.l + WIDTH;
    new.t = new.b + HEIGHT;

    redrawTo.x = MIN(new.l, old.l);
    redrawTo.y = MIN(new.b, old.b);

    redraw.origin.x = 0;
    redraw.origin.y = 0;
    redraw.size.width = (MAX(new.r, old.r)) - redrawTo.x + 1;
    redraw.size.height = (MAX(new.t, old.t)) - redrawTo.y + 1;

	black.size = redraw.size;

	[buffer lockFocus];
	PSsetgray(0);
	NXRectFill(&black);

    ballTo.x = new.l - redrawTo.x;
    ballTo.y = new.b - redrawTo.y;

	[[imageList objectAt:ballNum] composite:NX_SOVER toPoint:&ballTo];
	[buffer unlockFocus];

	[buffer composite:NX_COPY fromRect:&redraw toPoint:&redrawTo];

	avoid.origin = redrawTo;
	[mySpaceView setVoidRect:&avoid];
	[mySpaceView oneStep];

    old = new;

    return self;

}


/* calculate a vertical launch speed which will get the ball almost to  */
/* the top of the window before gravity pulls it back down. Little bit  */
/* of physics lesson here...                                            */

- newSpeed
{
    lastLaunchSpeed = yspeed = REAL_LAUNCH_SPEEd;
    if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED;
    xpos = 0;
    ypos = 0;

    if (viewWidth <= WIDTH) xspeed = 0;
    else xspeed = [self getRandomXspeed];

    [self checkXspeed:&xspeed];
    rebound = REBOUND;
    return self;
}

static BOOL noAnimFile = 0;

- initFrame:(NXRect *)frameRect
{
    char appDirectory[1024], *lastSlash;
    const char *animSpeed, *animFile, *animName;
    char animFrame[1024];
    int i, f;
    id local_image;

    NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT };

    [super initFrame:frameRect];
    [self allocateGState];      // For faster lock/unlockFocus
    [self setClipping:NO];      // even faster...

    accel = ACCEL;
    spinDir = 1;

    mySpaceView = [[SpaceView alloc] initFrame:frameRect];
	avoid.size.width = avoid.size.height = 100;

    //in this case, I only need one buffer for several Views
    if (!(buffer = [NXImage findImageNamed:"boinkBuffer"]))
    {
        buffer = [[NXImage alloc] initSize:&black.size];
        [buffer setName:"boinkBuffer"];
	}

    if ([buffer lockFocus])
    {
        PSsetgray(0);
        NXRectFill(&black);
        [buffer unlockFocus];
	}


// Get some of the defaults
    animSpeed = NXGetDefaultValue([NXApp appName], "animSpeed");
    if (animSpeed == NULL) framesPerSecond = 18;
    else framesPerSecond = atoi(animSpeed);

// Find the animation file
    animFile = NXGetDefaultValue([NXApp appName], "animFile");
    if (animFile == NULL) {
    strcpy(appDirectory, NXArgv[0]);
    lastSlash = rindex(appDirectory, '/');
    strcpy((lastSlash?lastSlash+1:appDirectory), "Globe.anim");
    animFile = appDirectory;
	}
    animName = basename(animFile, ".anim");

	imageList = [[List alloc] init];

// Construct the imageList
    /* construct the image list */
    for (i=0; ; i++) {
        sprintf(animFrame, "%s/%s.%d.tiff", animFile, animName, i+1);
    if ((f=open(animFrame, O_RDONLY)) < 0) break;
        close(f);
//    printf("%s\n", animFrame);
    local_image = [[NXImage alloc] initFromFile:animFrame];
    if (local_image == NULL) break; // never null, even if no file
        [imageList addObject:local_image];
	}
    numberOfFrames = i;
    ballNum = 0;

    if (numberOfFrames == 0) {
        if (!noAnimFile)
        NXRunAlertPanel([NXApp appName], "Could not open %s",
                NULL, NULL, NULL, animFile);
    noAnimFile = 1;
    return NULL;
	}

// May something else that I forgot to do?

    [self newViewSize];

    return self;
}

- setAccel:(float)val
{
    accel = val;
    return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
    [super sizeTo:width :height];
	[mySpaceView sizeTo:width :height];
    [self newViewSize];
    return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    if (!rects || !rectCount) return self;

    //PSsetgray(0);
    //NXRectFill(rects);

    NXRectClip(rects);

    return self;
}

- newViewSize
{

    //this is called every time View size changes
//    NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT };

    then = now = currentTimeInMs();

    if (oldSize.width == bounds.size.width &&
            oldSize.height == bounds.size.height)
        return self;
    else
    {
        oldSize.width = bounds.size.width;
        oldSize.height = bounds.size.height;
	}

    old.l = old.r = old.b = old.t = ballTo.x = ballTo.y = 0;

    viewWidth = bounds.size.width;
    viewHeight = bounds.size.height;
    if (viewHeight > SCREEN_HEIGHT) viewHeight = SCREEN_HEIGHT;

	[self newSpeed];
	return self;
}

- incrementBallNumber
{
    if (now > nextRotationTime)
    {
        ballNum += spinDir;

        if (ballNum >= COUNT) ballNum = 0;
        else if (ballNum < 0) ballNum = COUNT-1;
        nextRotationTime = now + 1000/framesPerSecond;
	}

    return self;
}

- (float) getRandomXspeed
{
    return randBetween(MIN_X_SPEED, MAX_X_SPEED);
}

- (float) timeCorrectedXSpeed
{
    float ret = xspeed * ((float)(now - then) / ASSUMED_INTERVAL);
    [self checkXspeed:&ret];
    return ret;
}

- checkXspeed:(float *)speed
{
    if (*speed > MAX_X_SPEED) *speed = MAX_X_SPEED;
    else if (*speed < -MAX_X_SPEED) *speed = -MAX_X_SPEED;
    return self;
}

- (const char *)windowTitle
{
    return "The Earth As A Basketball";
}

- didLockFocus
{
		[mySpaceView didLockFocus];
		return self;
}
@end
