// (C) Copyright 1992 Jim Patterson
#import <appkit/appkit.h>
#import <objc/objc.h>
#import <appkit/ClipView.h>
#import <dpsclient/event.h>
#import <soundkit/soundkit.h>
#import "ArcadeApp.h"
#import "Animator.h"
#import "LessonPlan.h"
#import "GameView.h"
#import "PerformanceEvaluator.h"

// Time between timer interrupts to scroll screen vertically
#define TA_CYCLE_TIME 0.1

// Number of points to scroll vertically each cycle (must be even!)
#define TA_DEFAULT_CYCLE_DELTA 5
#define TA_ENDGAME_CYCLE_DELTA 15

@implementation ArcadeApp

+ new
{
	self = [super new];
	startedWithFile = NO;
	// other stuff here when necessary
	return self;		
}

- appDidInit:sender
{
	NXRect clipViewFrame;
	int i;
	id gameFont;
	
	// initialize Animator instance
	timer = [Animator	newChronon:TA_CYCLE_TIME
					adaptation:0.0
					target:self
					action:@selector(cycleOnce:)
					autoStart:NO
					eventMask:NX_KEYDOWNMASK ];
	
	// further initialize LessonPlan instance
	[lessonPlan postInitialize];
	if (!startedWithFile) {
		for (i=0; i < 128; i++) [lessonPlan deselectKey:i];
		[lessonPlan selectKeysBetween:'a' :'z'];	// select upper & lower case letters
		[lessonPlan selectKey:TA_KEY_LEFTSHIFT];
		[lessonPlan selectKey:TA_KEY_RIGHTSHIFT];
		[[lessonPlan lessonNumMatrix]  selectCellAt:2:0];
		[lessonPlan setLessonNum: 3];
		}
	[lessonPlan flushKeyboard];
					
	// setup sound objects
	explosionSound = [Sound newFromMachO:"explosion.snd"];
	keyHitSound = [Sound newFromMachO:"keyHit.snd"];
			
	// further initialize view hierarchy
	NXSetRect(&clipViewFrame,
			TA_CLIPVIEW_X,TA_CLIPVIEW_Y,
			TA_CLIPVIEW_W,TA_CLIPVIEW_H);	
	clipView = [ClipView newFrame:&clipViewFrame];
	[clipView setBackgroundGray:NX_LTGRAY];
	[[gameWindow contentView]  addSubview:clipView];
	[gameWindow makeFirstResponder:self];
	[[gameWindow contentView] lockFocus];
	
	strcpy(personalFileName,"untitled");
	return self;
}

- appDidHide:sender
{
   [self stopGame:self];
   return self;
}
	
- setGameWindow:anObject { gameWindow = anObject; return self; }
- setGameView:anObject { gameView = anObject; return self; }
- setLessonPlan:anObject { lessonPlan = anObject; return self; }
- setPerformanceEvaluator:anObject {performanceEvaluator = anObject; return self; }
- setSpeedSlider:anObject { speedSlider = anObject; return self; }
- setPauseButton:anObject {pauseButton = anObject; return self; }
- setInstructionsScrollView:anObject {instructionsScrollView = anObject; return self; }
- lessonPlan {return lessonPlan;}
- performanceEvaluator {return performanceEvaluator; }
- gameWindow {return gameWindow; }
- speedChanged:sender
{
	int t = [sender tag];
	if (t == 0) 	cycleDelta = [sender intValue];	// sender was the slider
	else {
		cycleDelta +=t;		// else it was a speedup/slowdown key
		if (cycleDelta > 20) cycleDelta = 20;
			else if (cycleDelta <1) cycleDelta = 1;
		[speedSlider setIntValue:cycleDelta];
		}
	return self;
}

- startNewGame:sender
{
	NXRect gameViewFrame;
	NXPoint aPoint;

	if ([pauseButton state]) {		// unpause if we were paused
			 [timer startEntry];
			 [pauseButton setState:0];
			 }	
	// Don't start a new game if we are already playing
	if ([timer isTicking]) return self;

	// turn off pause button if it is on
	[pauseButton setState:0];
	// give user a get-ready message
	aPoint.x = 0; aPoint.y = TA_GAMEVIEW_H - TA_CLIPVIEW_H;
	[clipView rawScroll:&aPoint];
		
	// create a new GameView
	[gameWindow disableDisplay];
	[self getReadyPanel];
	[gameWindow reenableDisplay];
	gameView = [GameView standardNewFrame];
	[gameWindow disableDisplay];
	[clipView setDocView:gameView];
	aPoint.x = 0;
	aPoint.y = -TA_CLIPVIEW_H;
	[clipView rawScroll:&aPoint];	// orient  gameView within clipView
	[gameWindow reenableDisplay];
	[gameView display];
	[clipView setBackgroundGray:NX_LTGRAY];
	
	// reorient from the beginning
	gameProgress = 0;
	gamePhase = 0;
	cycleDelta = [speedSlider intValue];
	currentLowestLetter = [gameView lowestLetter];	// figure out which letter we are looking for
	
	[timer startEntry];
	return self;
}

- stopGame:sender
{
	NXPoint aPoint;

	if (([timer isTicking]) || ([pauseButton state])) {
		[timer stopEntry];
		[pauseButton setState:0];
		aPoint.x = 0; aPoint.y = TA_GAMEVIEW_H - TA_CLIPVIEW_H;
		[clipView rawScroll:&aPoint];
		[performanceEvaluator tellGameCompleted:NO]; 
		[performanceEvaluator summarize];
		[performanceEvaluator sortKeyInfo];
		[performanceEvaluator flush];
		}
	return self;
}

- playerLoses:sender
{
	NXPoint aPoint;
	[explosionSound play];
	[timer stopEntry];
	aPoint.x = 0; aPoint.y = TA_GAMEVIEW_H - TA_CLIPVIEW_H;
	[clipView rawScroll:&aPoint];
	[performanceEvaluator tellGameCompleted:NO]; 
	[performanceEvaluator summarize];
	[performanceEvaluator sortKeyInfo];
	[performanceEvaluator flush];
	sleep(3); 	
	return self;
}

- playerWins:sender
{
	NXPoint aPoint;
	[timer stopEntry];
	aPoint.x = 0; aPoint.y = TA_GAMEVIEW_H - TA_CLIPVIEW_H;
	[clipView rawScroll:&aPoint];
	[performanceEvaluator tellGameCompleted:YES]; 
	[performanceEvaluator summarize];
	[performanceEvaluator sortKeyInfo];
	[performanceEvaluator flush];
	sleep(3); 	
	return self;
}

- cycleOnce:sender
{
	NXPoint aPoint;
	NXRect aRect;
	int dangerLevel;

	if (gamePhase == 2)
		gameProgress += TA_ENDGAME_CYCLE_DELTA;
	else gameProgress += cycleDelta;
	aPoint.x = 0; aPoint.y = gameProgress - TA_CLIPVIEW_H;
	[clipView rawScroll:&aPoint];	// set's clip view's bounds.origin to aPoint
	switch (gamePhase) {
		case 0:	// starting phase...waiting for signal to change background to black
			if (gameProgress  >= TA_GAMEVIEW_TOBLACK_H) { 
				[clipView setBackgroundGray:NX_BLACK];
				gamePhase = 1;
				}
			break;
		case 1:	// playing phase...waiting for signal to change background to gray
			if (gameProgress  >= TA_GAMEVIEW_SPEEDUP_H) {
					[clipView setBackgroundGray:NX_LTGRAY];
					gamePhase = 2;
					}
			break;
		case 2:	// winding down phase...waiting for signal to stop scrolling
			if (gameProgress >= TA_GAMEVIEW_STOP_H)
				[self playerWins:self];
			break;
		}
	dangerLevel = TA_GAMEVIEW_Y + TA_GAMEVIEW_HEADER_H + TA_CLIPVIEW_H +
		 	[gameView lowestNonemptyRow]*TA_LINE_HEIGHT  - gameProgress;
	if (dangerLevel < 0)
		if (gameProgress + dangerLevel < 2750)			 
			[self playerLoses:self];
	return self;
}

- pauseGame:sender
{
	if ([sender state])  [timer stopEntry];
	else	 [timer startEntry];
	return self;
}


- keyDown:(NXEvent *) theEvent
{
	char key;
	if (![timer isTicking]) return self;
	key = (char) theEvent->data.key.charCode;

	// correct key was hit
	if (key == currentLowestLetter) {
		[gameView lowestLetterWasHit:gameProgress];		// notify view of letter hit
		currentLowestLetter = [gameView lowestLetter];			// find out new char to look for
		}
	else if ((key == (int)'\'') && (currentLowestLetter == ',')) {	// comma == apostrophe
		[gameView lowestLetterWasHit:gameProgress];		// notify view of letter hit
		currentLowestLetter = [gameView lowestLetter];			// find out new char to look for
		}
	else if ((key == (int)',') && (currentLowestLetter == '\'')) {	// comma == apostrophe
		[gameView lowestLetterWasHit:gameProgress];		// notify view of letter hit
		currentLowestLetter = [gameView lowestLetter];			// find out new char to look for
		}
	else [performanceEvaluator logKeyMiss:currentLowestLetter];
	return self;
}


- getReadyPanel
{
	[[gameWindow contentView] lockFocus];
	[self boxWithStr:"Get Ready !"];
	[[gameWindow contentView] unlockFocus];
	return self;
}

	
- boxWithStr:(char *) str
{
	// warning: must lockFocus before calling this
	float boxX,boxY,boxW,boxH,strX,strY,strW,strH;
	NXRect aRect;

	boxX = TA_CLIPVIEW_X+TA_GAMEVIEW_TRAILER_XMARGIN;
	boxY = TA_CLIPVIEW_Y+(TA_CLIPVIEW_H - TA_GAMEVIEW_TRAILER_BOX_H)/2;
	boxW = TA_GAMEVIEW_TRAILER_BOX_W;
	boxH = TA_GAMEVIEW_TRAILER_BOX_H;

	// clear box area and draw border	
	PSsetgray(NX_DKGRAY);
	NXSetRect(&aRect,boxX,boxY,boxW,boxH);
	NXDrawGroove(&aRect,&aRect);

	// draw str centered vertically and horizontally	
	PSstringwidth(str,&strW,&strH);
	strX = boxX + (boxW - strW)/2;
	strY = boxY + (boxH - strH)/2;
	PSsetgray(NX_BLACK);
	PSmoveto(strX,strY);
	PSscale(1,-1);
	PSshow(str);
		
	return self;	
}

- saveAs:sender
{
	NXTypedStream *tstream;
	id sPanel;
	
	sPanel = [SavePanel new];
	[sPanel setRequiredFileType:"ta"];
	if ([sPanel runModal]) {
		tstream = NXOpenTypedStreamForFile([sPanel filename], NX_WRITEONLY);
		[performanceEvaluator writeInfo:tstream];
		[lessonPlan writeInfo:tstream];
		NXCloseTypedStream(tstream);
		strcpy(personalFileName,[sPanel filename]);
		}
	return self;
}

- save:sender
{
	NXTypedStream *tstream;
	id sPanel;

	if (strcmp(personalFileName,"untitled") == 0)
		[self saveAs:sender];
	else {
		tstream = NXOpenTypedStreamForFile(personalFileName,NX_WRITEONLY);
		[performanceEvaluator writeInfo:tstream];
		[lessonPlan writeInfo:tstream];
		NXCloseTypedStream(tstream);
		}	
	return self;
}

- open:sender
{
	NXStream *tstream;
	id oPanel;
	const char *fileTypes[2]={"ta",NULL};

	oPanel = [OpenPanel new];
	if ([oPanel runModalForTypes:fileTypes]) {
		tstream = NXOpenTypedStreamForFile([oPanel filename],NX_READONLY);
		[performanceEvaluator readInfo:tstream];
		[lessonPlan readInfo:tstream];
		NXCloseTypedStream(tstream);
		strcpy(personalFileName,[oPanel filename]);
		}	
	return self;
}

- printInstructions:sender
{
	[[instructionsScrollView docView] printPSCode:sender];
	return self;
}

- (BOOL) appAcceptsAnotherFile:sender { return YES; }
- (int) appOpenFile:(const char *) filename type:(const char *) aType
{
	NXStream *tstream;
	tstream = NXOpenTypedStreamForFile(filename,NX_READONLY);
	[performanceEvaluator readInfo:tstream];
	[lessonPlan readInfo:tstream];
	NXCloseTypedStream(tstream);
	strcpy(personalFileName,filename);
	startedWithFile = YES;
	return YES;
}

@end
