/* Generated by Interface Builder */

#import <gamekit/gamekit.h>
#import <daymisckit/daymisckit.h>
#import <stdio.h>
#import <string.h>
#import <objc/objc-runtime.h>

@implementation GameBrain

- init		// designated initializer sets up game variables
{		// to sensible values.
	[super init];
    level = 0;
    paused = NO;
    ranOnce = NO;
    aborting = NO;
	printLevel = NO;
	initDone = NO;
    return self;
}

/* methods to get at important variables */
- highScoreController { return highScoreController; }
- oneUpView { return oneUpView; }
- scoreKeeper { return scoreKeeper; }
- scorePlayer { return scorePlayer; }
- soundPlayer { return soundPlayer; }
- mainStrings { return strings; }
- gameScreen { return gameScreen; }
- gameWindow { return gameWindow; }
- preferencesBrain { return preferencesBrain; }
- infoController { return infoController; }
- gameInfo { return gameInfo; }

- (BOOL)aborting { return aborting; }
- makeGameInfo { return [[GameInfo alloc] init]; }
- (int)startLevel { return [preferencesBrain startLevel]; }
- (int)tableNum { return tableNum; }
- (int)level { return level; }
- (int)speed { return [preferencesBrain speed]; }
- (int)paused { return paused; }
- (BOOL)playerCheated { return playerCheated; }
- gameOver:sender		// end the game, take high scores, etc.
{				// calls gameOver method (this is IB wrapper)
    [self gameOver];
    return self;
}

- pauseGame:sender		// toggle pause status of the game
{
    const char *title = [pauseMenuCell title];
    
    if (!strcmp(title,[strings valueForStringKey:"Pause"])) {
        [self pause];
    } else {
        [self unpause];
    } 
    return self;
}

- (int)pause				// pause game  
{
    if ([gameScreen gameState]==GAMEOVER) return NO; // no pausing if over
	[gameTimer pauseTiming:self];
#ifdef NOISYDEBUG
	fprintf(stderr, "Pause timer, elapsed time is %s\n",
			[gameTimer stringValue]);
#endif
    [pauseMenuCell setTitle:[strings valueForStringKey:"Unpause"]];
    [gameScreen pause:self];
	if (!paused)
		[gameWindow setTitle:[strings valueForStringKey:"PausedTitle"]];
    paused = YES;
    return YES;
}

- unpause			// unpause game  
{
    if ([gameScreen gameState]==GAMEOVER) [pauseMenuCell setEnabled:NO];
    [pauseMenuCell setTitle:[strings valueForStringKey:"Pause"]];
    [gameScreen unpause:self];
	[gameWindow setTitle:[strings valueForStringKey:"GameName"]];
    paused = NO;
#ifdef NOISYDEBUG
	fprintf(stderr, "Unpause game timer\n");
#endif
	[gameTimer continueTiming:self];
    return self;
}

- startNewGame:sender		// starts a new game
{
	if (![self askAbortGame:GK_ABORT]) return nil;
	if ([gameScreen gameState] != GAMEOVER) {
		aborting = YES;
		[self gameOver];
	}
	[scoreKeeper resetScore];
	[scoreKeeper updateTopScoreText];
	[scoreKeeper updateScoreText];
	level = 0;
	playerCheated = NO;
    ranOnce = YES;
    [pauseMenuCell setEnabled:YES];
	[self layerWindows];	
	level = [preferencesBrain startLevel] - 1;  // so all level init is done
	[preferencesBrain startingGame];
	[oneUpView setNumUp:[gameInfo numOneUps]];
    [self nextLevel];   // by the nextLevel method.
    if (paused) [self unpause];
    [gameWindow makeKeyAndOrderFront:self];
	// use sender's tag to determine which HS table to use. (Expert, Beg., etc)
	tableNum = 0; // should be the prefs default here *****
	if ([sender respondsTo:@selector(tag)]) {
		int theTag = [sender tag];
		if (theTag > 0) tableNum = theTag;
	}
	//[highScoreController table:tableNum]; // get applicable high score table
	currentSlot = [self buildNewSlot];
#ifdef NOISYDEBUG
	fprintf(stderr, "Starting time is:  %s\n",
			[[currentSlot startTime] stringValue]);
#endif
	[gameTimer startTiming:self];
    [gameScreen restartGame];
#ifdef GK_USE_MUSICKIT
	if ([preferencesBrain music] && ([gameScreen gameState] != GAMEOVER))
		[scorePlayer play:self];
#endif
	aborting = NO;
    return self;
}

- unpauseGame:sender		// unpause the game 
{
    return [self pauseGame:sender]; // handles toggle of menuCell
}

- nextLevel		// move to next level-- called when level is
				// completed and to start game
{
    level++;
    [levelText setIntValue:level];
	[gameScreen setUpScreen];
    return self;
}

- nextLevel:sender	// this is can be called from the interface; it considers
// the user to have cheated since they haven't completed the level normally.
{
	playerCheated = YES;
	return [self nextLevel];
}

- buildNewSlot
{	// build a high score slot at the start of a game.
	id newSlot;
	if (gameInfo) {	// use class in GameInfo...must be linked in already!
		newSlot = [[objc_lookUpClass([[gameInfo slotType] stringValue])
				alloc] init];
	} else newSlot = [[HighScoreSlot alloc] init];
	gameTimer = [[DAYStopwatch alloc] init];
	[newSlot setElapsedTime:gameTimer];
	[newSlot setStartTime:[[DAYTime alloc] initWithCurrentTime]];
	[newSlot setPlayerName:[preferencesBrain defaultPlayerName]];
	[newSlot setUserName:[NXApp userLoginName]];
	[newSlot setMachineName:[NXApp realHostName]];
	[newSlot setStartLevel:[preferencesBrain startLevel]];
	[newSlot setEndLevel:[preferencesBrain startLevel]];
	
	return newSlot;
}

- currentHighScoreSlot
{
	// update the slot and return it.
	[currentSlot setEndLevel:level];
	if ([gameScreen gameState] != GAMEOVER)
		[currentSlot setEndTime:[[DAYTime alloc] initWithCurrentTime]];
	[currentSlot setFinalScore:[scoreKeeper currentScore]];
	[gameTimer calcElapsedTime:self];
#ifdef NOISYDEBUG
	fprintf(stderr, "Current elapsed time is:  %s\n", [gameTimer stringValue]);
#endif
	return currentSlot;
}

- gameOver			// tidy up game, allow high score name entry
				
{
	[gameTimer pauseTiming:self]; // stop the timer now that the game is over
	//	implies a -calcElapsedTime: message, so we don't send it from here
	[currentSlot setEndTime:[[DAYTime alloc] initWithCurrentTime]];
#ifdef NOISYDEBUG
	fprintf(stderr, "Ending elapsed time is:  %s\n", [gameTimer stringValue]);
	fprintf(stderr, "Ending time is:  %s  (%s elapsed)\n",
			[[currentSlot endTime] stringValue],
			[[currentSlot elapsedTime] stringValue]);
#endif
	gameTimer = nil;
	// remove demo mode title...
	if ([gameScreen demoMode])
			[gameWindow setTitle:[strings valueForStringKey:"GameName"]];
	// if not demo, it's possible to get a high score entry...
	else if ([gameScreen gameState] != GAMEOVER)
		[highScoreController putInHighScores:[self currentHighScoreSlot]];
    [pauseMenuCell setEnabled:NO];
#ifdef GK_USE_MUSICKIT
	[scorePlayer stop:self];
#endif
	currentSlot = nil;
	[gameScreen gameOver]; // change gameView's state to GAMEOVER
	if (paused) [self unpause];
	return self;
}

- printGame:sender		// print game screen w/score, level, etc.
{
    char titleString[80];
    
	if (printLevel) sprintf(titleString,
			[strings valueForStringKey:"PrintTitleLevel"], level,
    		[scoreKeeper currentScore], [highScoreController highestScore]);
	else sprintf(titleString,[strings valueForStringKey:"PrintTitle"],
    		[scoreKeeper currentScore], [highScoreController highestScore]);
	[self pause]; // bringing up the print panel will do this anyway,
	// indirectly, via the delegate methods, but that would screw up the
	// window title bar we want to put up here:  (score, highest score)
    [gameWindow setTitle:titleString];
    [gameWindow smartPrintPSCode:self];
	if (paused)
		[gameWindow setTitle:[strings valueForStringKey:"PausedTitle"]];
	else [gameWindow setTitle:[strings valueForStringKey:"GameName"]];
    return self;
}

//  Application DELEGATE methods.  Special things to do on startup, unhide,
//  hide, and so on.

- buildAlert
{
	if (!alert) alert = NXGetAlertPanel([strings valueForStringKey:"GameName"],
		[strings valueForStringKey:"LoadingMessage"], NULL, NULL, NULL);
	return self;
}

- appWillInit:sender		// after init, but before 1st event.
{
	[self buildAlert];
	[alert makeKeyAndOrderFront:self];
	[loadingText setStringValue:[strings valueForStringKey:"LoadInit"]];
	NXPing();
	return self;
}

- appDidInit:sender		// after init, but before 1st event.
{
	NXEvent *theEvent = (NXEvent *)malloc(sizeof(NXEvent));
	
	if (!gameInfo) gameInfo = [self makeGameInfo];
	[pauseMenuCell setEnabled:NO];
	if (alert != loadingPanel) [alert orderOut:self];
	// methods to load the stuff that takes a while.
	[loadingPanel makeKeyAndOrderFront:self];

	// automatically make a bunch of the needed connections if they
	// haven't already been made in the .nib file and inform the
	// main objects that the app is up and running.
	if (!gameWindow) gameWindow = [gameScreen window];
	if (![gameWindow delegate]) [gameWindow setDelegate:self];
	[infoController appDidInit:sender];

	[loadingText setStringValue:[strings valueForStringKey:"LoadPrefs"]];
	NXPing();
	[preferencesBrain appDidInit:sender];
	[[levelText window] setFrameUsingName:"Stats"];
	[[levelText window] setFrameAutosaveName:"Stats"];
	[gameWindow setFrameUsingName:"Game"];
	[gameWindow setFrameAutosaveName:"Game"];

	[loadingText setStringValue:[strings valueForStringKey:"LoadSound"]];
	NXPing();
	[soundPlayer appDidInit:sender];

	[loadingText setStringValue:[strings valueForStringKey:"LoadImages"]];
	NXPing();
	[gameScreen appDidInit:sender];

#ifdef GK_USE_MUSICKIT
	[loadingText setStringValue:[strings valueForStringKey:"LoadScore"]];
	NXPing();
	[scorePlayer appDidInit:sender];
#endif

	[loadingText setStringValue:[strings valueForStringKey:"LoadHighs"]];
	NXPing();
	[highScoreController appDidInit:sender];
    [levelText setIntValue:1];
	[scoreKeeper appDidInit:sender];
	if (oneUpView) [scoreKeeper addDelegate:oneUpView];

	[loadingPanel orderOut:self];
    [[gameScreen animate:self] update];		// start up animation
	if ((alert != loadingPanel) && alert) NXFreeAlertPanel(alert);
	
	// the app-defined event will be the first one, used to bring up the
	// various panels that may turn up during the start up sequence.
	// (i.e. welcome, readme, shareware alert.)
	theEvent->type = NX_APPDEFINED;
	theEvent->data.compound.subtype = GK_STARTUP;
	DPSPostEvent(theEvent, YES);	// post as the next event to be serviced.
    return self;
}

- startUp	// handle the startup app-defined event
{
	BOOL firstTimeRun = [preferencesBrain firstTimeCheck];
	if ([infoController notRegistered]) { // shareware alert always...
		NXRunAlertPanel(
				[strings valueForStringKey:"SharewareAlert"],
				[strings valueForStringKey:"SharewareMessage"],
				[strings valueForStringKey:"Understand"], NULL, NULL);
	}
	if (!firstTimeRun && [preferencesBrain autoStart])
		[self startNewGame:self];
	[self layerWindows];
	if (firstTimeRun) { // welcome and readme
		char *str = malloc(128);
		sprintf(str, [strings valueForStringKey:"Welcome1"],
				[infoController versionString]);
		NXRunAlertPanel([strings valueForStringKey:"Welcome"],
				str, [strings valueForStringKey:"LetsPlay"],
				NULL, NULL);
		free(str);
		[infoController readme:self];
	}
	initDone = YES;
	return self;
}

- applicationDefined:(NXEvent *)theEvent	// initial startup readme panels
{ // a subclass could add other event types, but don't forget to call super!
	// You'd probably want to structure your code more or less like what
	// you see here (switch), if you have several event types declared.
	switch (theEvent->data.compound.subtype) { // switch allows me to
		// easily add more event types if I need to do so.
		case GK_STARTUP : { [self startUp]; break; }
		default : { break; }
	}
	return self;
}

- appDidBecomeActive:sender
{	// why the "initDone" ivar?
	// don't want to order the window up until we've got return values from
	// the alert panels that turn up when the game starts.  Since this message
	// is sent after the alert comes up but before the user replies (this
	// message is sent when the event loop becomes active, including launch)
	// we need to NOT order the gamewindow, etc. out until the start up
	// sequence is complete.  This is a hack that removes an annoying bug
	// that occured with panels popping up and making a general mess of the
	// screen...since in 2.x this message was _NOT_ sent on the initial
	// launch's activation of the event loop.  The 3.x behavior is more
	// correct, I suppose, but it was annoying to track down why this was
	// happening.  Anyway, this works around the problem just fine.
	if (initDone) [self layerWindows];
    return self;
}

- appDidHide:sender
{
	[self pause];
    return self;	// pause game on Command-h
}

- appDidResignActive:sender
{
    if ([gameScreen demoMode]) return self;
	[self pause];
    return self;	// pause game on app deactivate
}

- layerWindows
{
	[gameWindow makeKeyAndOrderFront:self];
	[gameWindow makeFirstResponder:gameScreen];
	if ([preferencesBrain autoUnPause]) [self unpause];
	// make sure the windows are layered properly
	// need to check delegate to be sure it responds...
	// Note:  NeXT has said, for portability, that we shouldn't depend on
	// 	a method to return zero if sent to a nil object unless that method
	//	returns an id, in which case nil will be "returned".  Thus, the
	//	first half of the condition below has been put in to deal with
	//	when there's no delegate.  It's not really needed on Intel or Moto
	//	hardware, but I'm keeping later ports in mind here, too...
	if (![[levelText window] delegate] || // if no delegate, assume window up
			[[[levelText window] delegate] windowUp])
		[[levelText window] orderFront:self];
	[gameWindow orderFront:self];
	return self;
}

- appDidUnhide:sender
{
	return [self layerWindows];
}

- appWillTerminate:sender		// update DEFAULTS here 
{
    [preferencesBrain writeDefaults:self];
    return self;
}					

- (BOOL)askAbortGame:(int)why
{
	if (([gameScreen gameState] != GAMEOVER) && (![gameScreen demoMode]) &&
			([preferencesBrain alert])) {
		BOOL flag = (why == GK_EXIT);
		// Verify that player wants to leave game
		[self pause];
        if (NXRunAlertPanel(([highScoreController
				slotIsEligible:[self currentHighScoreSlot]] ?
					[strings valueForStringKey:"HaveScore"] : NULL),
				(flag ? [strings valueForStringKey:"ReallyQuit"] :
					[strings valueForStringKey:"ThrowAway"]),
				(flag ? [strings valueForStringKey:"Yes2"] :
					[strings valueForStringKey:"Yes1"]),
				(flag ? [strings valueForStringKey:"No2"] :
					[strings valueForStringKey:"No1"]),
				NULL) != NX_ALERTDEFAULT) {
		[self unpause];	// unpause if we're aborting the game abort. :->
	    return NO;
	}	}
	return YES;
}

- abortGame:sender
{
	if ([self askAbortGame:GK_ABORT]) {
		aborting = YES;
		[self gameOver];
	}
	return self;
}

- quit:sender
{
	if (![self askAbortGame:GK_EXIT]) return self;
	if ([infoController notRegistered]) {
		if (ranOnce) {
			NXRunAlertPanel([strings valueForStringKey:"Goodbye"],
				[strings valueForStringKey:"Enjoyed"],
				[strings valueForStringKey:"NoForget"], NULL, NULL);
		} else {
			NXRunAlertPanel([strings valueForStringKey:"Goodbye"],
				[strings valueForStringKey:"TryMe"],
				[strings valueForStringKey:"TryLater"], NULL, NULL);
		}
	}
	[highScoreController closeServers]; // make sure server knows that we left.
	return [NXApp terminate:sender];
}

- windowDidResginMain:sender	// do pause if window loses main status
{
    [self pause];
	return self;	// pause game
}

- windowDidResignKey:sender		// do pause if window loses key status
{
    [self pause];
	return self;	// pause game
}

- windowDidBecomeKey:sender		// do unpause if window gains key status 
{
    [gameWindow makeFirstResponder:gameScreen];
    if ([preferencesBrain autoUnPause]) [self unpause];	// unpause on unhide*/
	return self;
}

- windowDidDeminiaturize:sender		// clean off crap left by the new 
{									// border animation in 3.0.  yuck!
    [gameScreen update];
	[[infoController niftyView] update];
	return self;
}

- windowDidMove:sender		// move status with game window 
{
    NXRect gameFrame, statsFrame;
	
	[gameWindow getFrame:&gameFrame];
	[[levelText window] getFrame:&statsFrame];
    [[levelText window] moveTo:(NX_X(&gameFrame) - NX_WIDTH(&statsFrame) + 1)
		:NX_Y(&gameFrame) + NX_HEIGHT(&gameFrame) - NX_HEIGHT(&statsFrame)];
	return self;
}


@end
