
/* Generated by Interface Builder */

#import "GameView.h"
#import "Animator.h"			// timer controller
#import "GameBrain.h"			// Partner in crime
#import "PreferencesBrain.h"	// for user's preferences
#import <appkit/color.h>		// Colors
#import <appkit/Application.h>	// event stuff, misc.
#import <appkit/Speaker.h>		// for dragging
#import <appkit/Listener.h>		// for dragging
#import <appkit/NXImage.h>		// for tiff rendering
#import <appkit/OpenPanel.h>	// for setting backgrounds
#import <appkit/publicWraps.h>	// used to set up dragging (get windownum)
#import <appkit/nextstd.h>		// for NX_FREE, etc.
#import <dpsclient/psops.h>		// PSsetgray()
#import <dpsclient/wraps.h>		// PScompositerect()
#import <libc.h>				// for random(), etc.
#import <math.h>				// for floor(), etc.
#import <stdio.h>				// for standard C file operations


@implementation GameView

- initFrame:(const NXRect *)frm    // designated initializer for a view
{
    char *slashPos;
    
    [super initFrame:frm];
 
// seed random number generator

    srandom(time(0));

// some defaults

    demoMode = NO;
    grayBorder = YES;
    
// get .app wrapper pathname and then load .snd files

    appPath = (char *)malloc(256);
    strcpy(appPath, NXArgv[0]);
    if (slashPos = strrchr(appPath, '/')) {
    	sprintf(slashPos+1, "\0");
    } else {
        strcpy(appPath, "./");
    }
    
// initialize game variables
    state = GAMEOVER;
    demoWait = 0;
    doingBorder = NO;
    grayBorder = YES;
    
	backColor = NXConvertRGBToColor(0.333, 0.333, 0.333); // dark gray default
	backIsColor = YES; // default bg is dark gray...
	[self allocateGState];
    return self;
}

- loadPix
{
	const char *tmpStr;
	char *okStr; float r, g, b;
	
// set background
	okStr = malloc(8);
	tmpStr = NXGetDefaultValue([NXApp appName], "BackColor");
	if (tmpStr) {
		backIsColor = NO;
		sscanf(tmpStr, "%s %f %f %f", &okStr[0], &r, &g, &b);
		backColor = NXConvertRGBToColor(r, g, b); 
		if (okStr[0] == 'Y') {
			backIsColor = YES;
		}
	}
	free(okStr);
	
    if (!backIsColor)
		[self setBackgroundFile:
				NXGetDefaultValue([NXApp appName], "BackGround")
			andRemember:NO];
    
	return self;
}

- animate:sender
{		// method to start the animation - called by appDidInit

    // set up Animator: (autoUpdate)  this handles moving viruses and
    //   makes sure the columns fall when they should.  The animator is
    //   an object that handles timed entries "smartly".
    animator = [[Animator alloc] initChronon:[self speedTime]
    		adaptation:0.0   target:self
    		action:@selector(autoUpdate:) autoStart:YES eventMask:0];
    
    return self;
}

- (BOOL)demoMode			// if last game was demo
{ return demoMode; }

- (float)speedTime		// returns time between animation frames
{  return GRANULARITY;	// return delay between clock ticks
}			// 0.05 is about as fast as should be used.

- (int)gameState	// tell caller our state... if in demo, game is over
{ if (demoMode) return GAMEOVER; return state; }

- autoUpdate:sender	// called by timed entry to update screen periodically
{			// ALL animation is controlled from here!!!
    cycles++;
	// you would do updates to various screen objects and
	// then do a [self updateSelf:&bounds :1];
    return self;
}

- updateSelf:(NXRect *)rects :(int)rectCount	// redraws the screen.
{		// it redraws only what has changed since last redraw.
	NXPing();
	return self;
}

- mouseDown:(NXEvent *)event		// handle mouseDown events.
{
    [controller pauseGame:self];	// pause/unpause game
    return self;
}

- (BOOL)acceptsFirstMouse		// let us grab activating mousedowns
{	// 1st click is to become 1st responder, NOT pause/unpause
    return NO;
}

- setKey:(int)keyIndex val:(char)keyVal		// change key we respond to
{
    keys[keyIndex] = keyVal;
    return self;
}

- pause:sender				// set pause status.  
{
    paused = YES;
    return self;
}

- unpause:sender			// remove paused status
{
    paused = NO;
    return self;
}
	
- (BOOL)isPaused			// tell caller is we're paused
{ return paused; }

- keyDown:(NXEvent *)myevent		// handle keyDown events.
{
	if (myevent->data.key.charSet == NX_ASCIISET &&
		(myevent->flags&(NX_CONTROLMASK|NX_ALTERNATEMASK|NX_COMMANDMASK)) == 0)
	{	
		if (myevent->data.key.charCode == 'p') { // allow pause
			if (paused) [controller unpause];
			else [controller pause];
		} else if (paused) {	// any keyDown unpauses game (except 'p').
			[controller unpause];
		} else if (myevent->data.key.charCode == 'n') { // allow new game
			if (([self gameState] != GAMEOVER) && ([preferences alert])) {
				// if game in progress, alert user about this
				if (NXRunAlertPanel(NULL,
						"Do you want to throw away the current game?",
						"You betcha!", "Um, no.", NULL) != NX_ALERTDEFAULT) {
					return self;  // allow "graceful escape"
				}
			}
			if ([self gameState] != GAMEOVER) {
				[controller gameOver];  // Force Game Over.
				state = GAMEOVER;
			}
			[controller startNewGame:self]; // re-start the game
		}
	} else return [super keyDown:myevent];
    return self;
}

- (BOOL)acceptsFirstResponder		// to grab keyboard events
{ return YES; }

- getPreferences
{    
    soundEffects = [preferences effects];
    music = [preferences music];
    return self;
}


- soundOn				// turn on sound effects
{
    soundEffects = YES;
    return self;
}

- soundOff				// turn off sound effects
{
    soundEffects = NO;
    return self;
}

- musicOn				// turn on music
{
    music = YES;
    return self;
}

- musicOff				// turn off music
{
    music = NO;
    return self;
}

- free					// get rid of support objects
{
    [animator free];
	[self unregisterWindow];
    return [super free];
}

- setUpScreen
{	    
    if ((demoMode)||(doingBorder)) [self display];
	
    return self;
}

- changeBorder:(BOOL)borderOn
{ // move view about in the window & do sizing
	// NOT USED in PacMan...it's best if you don't use it, in fact.
    NXRect wFrame;
	
	if (borderOn == grayBorder) return self;
	
	if (borderOn) {
		[window getFrame:&wFrame];
		NXInsetRect(&wFrame, -BEZELSIZE, -BEZELSIZE);
		[self moveBy:BEZELSIZE :BEZELSIZE];
		[window placeWindowAndDisplay:&wFrame];
	} else {
		[window getFrame:&wFrame];
		NXInsetRect(&wFrame, BEZELSIZE, BEZELSIZE);
		[self moveBy:-BEZELSIZE :-BEZELSIZE];
		[window placeWindowAndDisplay:&wFrame];
	}
	grayBorder = borderOn;
	return self;
}

- restartGame
{
    return self;
}


//// Background handling methods stolen from BreakApp:
//

- setBackgroundFile:(const char *)fileName andRemember:(BOOL)remember
// This methods allows changing the file used to paint the background of the
// playing field. Set fileName to NULL to revert to the default. Set
// remember to YES if you wish the write the value out in the defaults.
{
	[backGround free];

	backGround = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
	if (fileName) {
		[backGround useFromFile:fileName];
		if (remember) {
			NXWriteDefault ([NXApp appName], "BackGround", fileName);
		}
	} else {
		[backGround useFromSection:"BackGround.eps"];
		if (remember) {
			NXRemoveDefault ([NXApp appName], "BackGround");
		}
	}
	backIsColor = NO;
	[self writeColor];
	[backGround setBackgroundColor:NX_COLORWHITE];
	[backGround setScalable:YES];
	[self display];

	return self;   
}


// The following two methods allow changing the background image from
// menu items or buttons.

- changeBackground:sender
{
    const char *const types[] = {"tiff", "eps", NULL};  

    if ([[OpenPanel new] runModalForTypes:types]) {
	[self setBackgroundFile:[[OpenPanel new] filename] andRemember:YES];
	[self display];
    }

    return self;
}

- revertBackground:sender
{
    [self setBackgroundFile:NULL andRemember:YES];
    [self display];
    return self;
}

- back1:sender
{
    char *tempStr;
    
    tempStr = malloc(256);
    sprintf(tempStr, "%sBack1.tiff", appPath);
    [self setBackgroundFile:tempStr andRemember:YES];
    free(tempStr);
    [self display];
    return self;
}

- back2:sender
{
    char *tempStr;
    
    tempStr = malloc(256);
    sprintf(tempStr, "%sBack2.tiff", appPath);
    [self setBackgroundFile:tempStr andRemember:YES];
    free(tempStr);
    [self display];
    return self;
}

- back3:sender
{
    char *tempStr;
    
    tempStr = malloc(256);
    sprintf(tempStr, "%sBack3.tiff", appPath);
    [self setBackgroundFile:tempStr andRemember:YES];
    free(tempStr);
    [self display];
    return self;
}

- drawBackground:(NXRect *)rect
// drawBackground: just draws the specified piece of the background by
// compositing from the background image.
{
    NXRect tmpRect = *rect;

	if (backIsColor) {
		NXSetColor(backColor);
		NXRectFill(rect);
		return self;
	}
	
	NX_X(&tmpRect) = floor(NX_X(&tmpRect));
	NX_Y(&tmpRect) = floor(NX_Y(&tmpRect));
	if (NXDrawingStatus == NX_DRAWING) {
		PSsetgray (NX_WHITE);
		PScompositerect (NX_X(&tmpRect), NX_Y(&tmpRect),
			NX_WIDTH(&tmpRect), NX_HEIGHT(&tmpRect), NX_SOVER);
	}
	[backGround composite:NX_SOVER fromRect:&tmpRect toPoint:&tmpRect.origin];
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height 
{
    [super sizeTo:width :height];
    [backGround setSize:&bounds.size];
    return self;
}


// deal with drag and drop colors:
- acceptColor:(NXColor)color atPoint:(NXPoint *)aPoint
{
	backIsColor = YES;
	backColor = color;
	[[self writeColor] update];
	return self;
}

- writeColor
{
	char def[256];
	char bic[16] = "YES";
	float r, g, b;
	
	if (!backIsColor) sprintf(bic, "NO");
	
	NXConvertColorToRGB(backColor, &r, &g, &b);
	
	sprintf(def, "%s %f %f %f", bic, r, g, b);
	NXWriteDefault ([NXApp appName], "BackColor", def);
	return self;
}


// The drag and drop .tiff/.eps is borrowed and modified from the
// Adobe example NX_ImportAdv which demonstrates tiff/eps handling.
// I've taken the code chunk for drag and drop and modified it to
// work for setting backgrounds.


/*
 * Registers the document window with the Workspace Manager so that when the
 * user picks up an icon in the Workspace and drags it over our document window
 * and lets go, iconEntered:... and iconReleasedAt::ok: messages will be
 * sent to the DrawDocument from the Workspace Manager.  Allows the user to
 * drag PostScript and TIFF files into the document.
 */
- registerWindow
{

	return self;
}

/*  Undoes what registerWindow does.  */
- unregisterWindow
{

	return self;
}

/*
 * Called whenever an icon is dragged from the Workspace over the document
 * window.  At this point, all that is done is to salt away the list of files
 * represented by the icon.  All the real work is done in iconReleasedAt::ok:.
 */
- (int)iconEntered:(int)windowNum at:(double)x :(double)y
    iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
    iconWidth:(double)iconWidth iconHeight:(double)iconHeight
    pathList:(char *)pathList
{
	if (!iconPathList || strcmp(iconPathList, pathList))
	{ // if new list of files, copy it in for us to look at later
		NX_FREE(iconPathList);
		NX_MALLOC(iconPathList, char, strlen(pathList)+1);
		strcpy(iconPathList, pathList);
	}
	return 0;
}

/*
 * Goes through the list of files associated with the icon dragged
 * from the Workspace and checks if any of them are PostScript or TIFF.
 * If any are, then the GraphicView is asked to load those in as objects.
 * Very important: an NX_DURING handler is required around all the processing
 * of this method since an uncaught raised error will cause this method not
 * to return and thus hang the Workspace Manager for a while.
 */
- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
{
	volatile int		foundOne = NO;
	char				*file, *tab, *extension;

	NX_DURING
		file = iconPathList;
		tab = strchr(file, '\t');
		if (tab)
			*tab = '\0';
		extension = strrchr(file, '.');
		if (extension) {
			if (!strcmp(extension, ".eps") || !strcmp(extension, ".tiff")) {
				[self setBackgroundFile:file andRemember:YES];
				foundOne = YES;
			}
		}
		if (foundOne) {
			[NXApp activateSelf:YES];
			[window makeKeyAndOrderFront:self];
		}
	NX_HANDLER
	NX_ENDHANDLER

	*flag = foundOne;

	return 0;
}

@end
