#import "MovieApp.h"
#import "MovieView.h"
#import "Animator.h"
#import "SliderDualActing.h"
#import <appkit/Button.h>
#import <appkit/OpenPanel.h>
#import <appkit/Matrix.h>
#import <appkit/nextstd.h>
#import <appkit/publicWraps.h>
#import <string.h>

@implementation MovieApp

/* connect frameSlider, do some SliderDualActing initialization */
- setFrameSlider:anObject
{
	frameSlider = anObject;
	[frameSlider setUpTarget:self action:@selector(showFrame:)];
	return self;
}

/* connect frequencySlider, do some SliderDualActing initialization */
- setFrequencySlider:anObject
{
	frequencySlider = anObject;
	[frequencySlider setUpTarget:self action:@selector(changeFrequency:)];
	[frequencySlider setFormat:NO left:2 right:2];
	[frequencySlider setMax:30.0 allowHigher:NO min:0.02 allowLower:NO];
	[frequencySlider setAltStep:1 whole:NO default:10.0];
	return self;
}

/* other outlets */
- setDirectionButton:anObject;
{ directionButton = anObject; return self; }
- setGoStopButton:anObject;
{ goStopButton = anObject; return self; }
- setEdgeMatrix:anObject
{ edgeMatrix = anObject; return self; }
- setControlPanel:anObject
{ controlPanel = anObject; return self; }
- setInfoPanel:anObject;
{ infoPanel = anObject; return self; }

/* create the timer at appDidInit: time */
- appDidInit:sender
{
	timer = [Animator newChronon:0.1 adaptation:3.0 target:self
		action:@selector(tickTock:) autoStart:NO
		eventMask:NX_ALLEVENTS];
	return self;
}

/* bring up the Info Panel (mkof = makeKeyAndOrderFront) */
- mkofInfoPanel:sender
{
	if (infoPanel == NULL)
		[self loadNibSection:"Info.nib" owner:self withNames:NO];
	[infoPanel makeKeyAndOrderFront:self];
	return self;
}

/* bring up the Control Panel (mkof = makeKeyAndOrderFront) */
- mkofControlPanel:sender
{
	if (mainWindow == NULL) {
		NXRunAlertPanel("No Can Do", "The Control Panel is useless if there's no movie opened.", "OK", NULL, NULL);
		return self;
	}
	if (controlPanel == NULL)
		[self loadNibSection:"Control.nib" owner:self withNames:NO];
	[self updateFrameSlider];
	[controlPanel makeKeyAndOrderFront:self];
	return self;
}

/* make the frameSlider reflect the main window's current frame */
- updateFrameSlider
{
	[frameSlider setMax:[[mainWindow contentView] numFrames] - 1 allowHigher:NO min:0 allowLower:NO];
	[frameSlider setAltStep:1 whole:YES default:[[mainWindow contentView] currentFrame]];
	return self;
}

/* whenever a movie window resigns main, we stop the animation */
- windowDidResignMain:sender
{
	if ([timer isTicking])
		[goStopButton performClick:self];
	if (mainWindow == NULL)
		[controlPanel orderOut:self];
	return self;
}

/* Whenever a movie window becomes main, we bring up the Control panel */
- windowDidBecomeMain:sender
{
	[self mkofControlPanel:self];
	return self;
}

/* We are always ready to open another file. */
- (BOOL) appAcceptsAnotherFile:sender
{
	return YES;
}

/*
 * appOpenFile is called by the open: method, and by other apps via
 * Speaker/Listener 
 */
- (int)appOpenFile:(const char *)filename type:(const char *)aType
{
	char            temp[128];
	NXStream       *theStream;
	int             i, width, height, numFrames;
	id              theNewWin;

	/* read in data from movie index */
	strcpy(temp, filename);
	strcat(temp, "/index");
	theStream = NXMapFile(temp, NX_READONLY);
	if (theStream == NULL) {
		NXRunAlertPanel("Error", "%s has no index.",
				"OK", NULL, NULL, filename);
		return YES;
	}
	NXScanf(theStream, "%d%d%d", &width, &height, &numFrames);
	NXClose(theStream);
	/* is any data bad or missing? */
	if ((width < 1) || (height < 1) || (numFrames < 1)) {
		NXRunAlertPanel("Error", "%s has a bad index.",
				"OK", NULL, NULL, filename);
		return YES;
	}
	/* create the new window, in a good place */
	theNewWin = [Window
		newContent:[self nextRectForWidth:width Height:height]
		style:NX_TITLEDSTYLE
		backing:NX_RETAINED
		buttonMask:(NX_CLOSEBUTTONMASK | NX_MINIATURIZEBUTTONMASK)
		defer:NO];
	/* we need to receive windowDidBecomeMain: and windowDidResignMain: */
	[theNewWin setDelegate:self];
	/*
	 * create a new MovieView, make it the contentView of our new window,
	 * and destroy the window's old contentView 
	 */
	[[theNewWin setContentView:[MovieView newPath:filename Width:width Height:height NumFrames:numFrames]] free];
	/* make sure the miniwindow has the pretty ".movie" icon */
	[theNewWin setMiniwindowIcon:"movie"];
	/* give the window a good title */
	strcpy(temp, strrchr(filename, '/') + 1);
	strcat(temp, " Ð ");
	strncat(temp, filename, strrchr(filename, '/') - filename);
	[theNewWin setTitle:temp];
	/* display the window, and bring it forth */
	[theNewWin display];
	[theNewWin makeKeyAndOrderFront:self];
	/* bring up the control panel */
	[self mkofControlPanel:self];
	/* show the first frame */
	[[theNewWin contentView] showFrameNumber:0];
	return YES;
}

/***************************************************************************/
/* nextRectForWidth:Height: - return the next good content rectangle       */
/*  from Carl F. Sutter's wonderful ViewGif2 'Controller' method...        */
/***************************************************************************/
/* nextTopLeft - return the next good top left window position		   */
/***************************************************************************/

- (NXRect *)nextRectForWidth:(NXCoord)width Height:(NXCoord)height
{
#define OFFSET 10.0
#define MAX_STEPS 20
#define INITIAL_X 356.0
#define INITIAL_Y 241.0
	NXPoint         nxpTopLeft;
	NXRect          nxrTemp;	/* used to find window height	 */
	NXRect          nxrWinHeight;	/* bounds of enclosing window	 */
	NXSize          nxsScreen;	/* size of screen		 */
	static NXRect   nxrResult;	/* the Answer!			 */
	static int      nCurStep = 0;

	/* find a good top-left coord */
	nxpTopLeft.x = INITIAL_X + nCurStep * OFFSET;
	nxpTopLeft.y = INITIAL_Y + nCurStep * OFFSET;
	if (++nCurStep > MAX_STEPS)
		nCurStep = 0;
	/* find window height using nxrTemp */
	nxrTemp.size.width = width;
	nxrTemp.size.height = height;
	nxrTemp.origin.x = nxrTemp.origin.y = 0;
	[Window getFrameRect:&nxrWinHeight forContentRect:&nxrTemp
	 style:NX_TITLEDSTYLE];
	[NXApp getScreenSize:&nxsScreen];
	/* find the lower-left coord */
	nxrResult.origin.x = nxpTopLeft.x;
	nxrResult.origin.y = nxsScreen.height - nxrWinHeight.size.height - nxpTopLeft.y;
	nxrResult.size.width = width;
	nxrResult.size.height = height;
	return (&nxrResult);
}

/* open is called by the "Open..." button in the "Movie" menu */
- open:sender
{
	const char     *const ext[2] = {"movie", NULL};
	const char     *filename;
	id              openReq = [OpenPanel new];
	if ([openReq runModalForTypes:ext] && (filename = [openReq filename]))
		[self appOpenFile:filename type:""];
	return self;
}

/* showFrame is called continuously as the "Current Frame" slider is moved */
- showFrame:sender
{
	[[mainWindow contentView] showFrameNumber:[sender intValue]];
	return self;
}

/*
 * changeFrequency is called whenever the user is done manipulating the
 * "Frames Per Second" slider.  This method actually frees the old timer
 * and creates a new one, because Animator's setIncrement: method doesn't
 * work the way it should (it doesn't change desireddt).
 */
- changeFrequency:sender
{
	BOOL wasTicking = [timer isTicking];
	[timer free];
	timer = [Animator newChronon:(1.0 / [sender doubleValue])
		adaptation:3.0 target:self action:@selector(tickTock:)
		autoStart:wasTicking eventMask:NX_ALLEVENTS];
	return self;
}

/* toggleGoStop: is called when the user hits the "Go/Stop" Button */
- toggleGoStop:sender
{
	if ([timer isTicking])
		[timer stopEntry];
	else
		[timer startEntry];
	[timer resetRealTime];
	return self;
}

/* tickTock: is called by the timer */
- tickTock:sender
{
	int             newFrameNum;

	newFrameNum = [[mainWindow contentView] currentFrame];
	if ([directionButton state])
		newFrameNum++;
	else
		newFrameNum--;
	if (newFrameNum == [[mainWindow contentView] numFrames]) {
		switch ([edgeMatrix selectedTag]) {
		case 0:	/* hard edges */
			[goStopButton performClick:self];
			return;
		case 1:	/* bouncy edges */
			[directionButton performClick:self];
			newFrameNum--;
			break;
		case 2:	/* wraparound */
			newFrameNum = 0;
			break;
		}
	} else if (newFrameNum == -1) {
		switch ([edgeMatrix selectedTag]) {
		case 0:	/* hard edges */
			[goStopButton performClick:self];
			return;
		case 1:	/* bouncy edges */
			[directionButton performClick:self];
			newFrameNum++;
			break;
		case 2:	/* wraparound */
			newFrameNum = [[mainWindow contentView] numFrames] - 1;
			break;
		}
	}
	/* show the new frame */
	[[mainWindow contentView] showFrameNumber:newFrameNum];
	/* change the slider to the new frame number */
	[frameSlider setIntValue:newFrameNum];
	/* wait for all the Display Postscript to be executed */
	NXPing();
}
@end
