//
//  MPT_AUAppDelegate.m
//  AUCoderDemo
//
//  Created by Andrew Salamon on 5/31/06.
//  Copyright 2006 Machine Perception Laboratory, University of California San Diego. All rights reserved.
//

#import <Carbon/Carbon.h>

#import <QTKit/QTMovie.h>

#import "MPT_AUAppDelegate.h"
#import "MPT_CERT.h"
#import "MPT_Pair.h"
#import "MPT_FaceView.h"
#import "MPT_Face.h"
#import "MPT_AUView.h"
#import "MPT_ResolveAlias.h"
#import "MPT_RimageAdaptor.h"

#import "MPT_Preferences.h"
#import "MPT_PluginController.h"
#import "MPT_PluginStepTransformer.h"
#import "MPT_PluginProtocolResults.h"

#import "MPT_DataRecorder.h"
#import "MPT_SplitViewConstrainer.h"
#import "MPT_DataLoader.h"

#import "TestController.h"

#import "MPT_Scheduler.h"
#import "MPT_SourceLoader.h"

#import "MPT_Demo.h"

@interface MPT_AUAppDelegate(PrivateMethods)
- (void)checkLicense;

- (void)handleResults:(NSDictionary *)pluginResults;
- (BOOL)loading;
- (void)setLoading:(BOOL)value;

- (void)setIsProcessing:(BOOL)ignored;
- (void)prepareForProcessing;

- (BOOL)autostartWebCam;
@end

@implementation MPT_AUAppDelegate
+ (void)initialize
{
	// Register our plugin step name transformer
	MPT_PluginStepTransformer *transformer = [[[MPT_PluginStepTransformer alloc] init] autorelease];
	[NSValueTransformer setValueTransformer:transformer forName:@"MPT_PluginStepTransformer"];
	
	// Make sure the correct alignment method is used by default unless the user overrides it.
	NSUserDefaults *_prefs = [NSUserDefaults standardUserDefaults];
	NSDictionary *defPrefs = [NSDictionary dictionaryWithObjectsAndKeys:
		[NSNumber numberWithBool:YES], @"findFaces", nil];
	[_prefs registerDefaults:defPrefs];
}

- (void)awakeFromNib
{	
	// Restrict the image view half of the splitter to at least 400 pixels wide,
	// and the AUView side to at least 216 pixels wide (one AUView wide).
	[splitViewDelegate setMinLeftPane:400];
	[splitViewDelegate setMinRightPane:216];
	
	pluginController = [MPT_PluginController defaultController];
	[pluginController setTileView:tileView];
	[pluginController setPluginMenu:pluginMenu];
	[pluginController initPlugins];

	//cert = [[MPT_CERT alloc] init];
	//[MPT_Scheduler setNumThreads:4];
	scheduler = [[MPT_Scheduler alloc] initWithDelegate:self];
	frameNum = 1;
	nextFrame = 1;
	resultsBuffer = [[NSMutableArray alloc] init];

	videoController = [[TestController alloc] init];
	[imageView resetFaceAndEyes];
	[imageView setFaceType:[[NSUserDefaults standardUserDefaults] integerForKey:@"FaceDisplayType"]];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(faceDisplayTypeChanged:) name:faceDisplayTypeNotification object:nil];
	
	[self setLoading:YES];
	[self setVideoWidth:[videoController videoWidth]];
	[self setVideoHeight:[videoController videoHeight]];
	
	rimageTypes = [[NSArray alloc] initWithObjects:@"rimage", @"rimage_bin", @"rimage_xml", nil];
	movieTypes = [[QTMovie movieFileTypes:QTIncludeCommonTypes] retain];
	imageTypes = [[NSImage imageFileTypes] retain];
	allTypes = [rimageTypes arrayByAddingObjectsFromArray:movieTypes];
	allTypes = [[allTypes arrayByAddingObjectsFromArray:imageTypes] retain];
	
	sourceLoader = [[MPT_SourceLoader alloc] initWithDelegate:self imageTypes:imageTypes movieTypes:movieTypes];
	
	id tmp = [pluginController pluginForPluginID:@"edu.ucsd.mplab.plugins.Distributed"];
	if( tmp && [tmp respondsToSelector:@selector(doesScheduleInput:)] )
	{
		doClient = [tmp retain];
		[doClient performSelector:@selector(setScheduler:) withObject:scheduler];
//		[doClient setScheduler:scheduler];
		[doClient setAppDelegate:self];
	}
	
	[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"findFaces" options:NSKeyValueObservingOptionNew context:NULL];

	[self checkLicense];
}

- (void)dealloc
{
	[cert release];
	[videoController release];
	[rimageTypes release];
	[movieTypes release];
	[imageTypes release];
	[allTypes release];
	[scheduler release];
	[resultsBuffer release];
	[doClient release];
	
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"findFaces"];
	[super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	BOOL optKeyDown = ((GetCurrentKeyModifiers() & (optionKey |  rightOptionKey)) != 0) ? YES : NO;

	[[self window] makeKeyAndOrderFront:self];
	
	// Don't load the kernels if the option key is down.
	if( !optKeyDown )
	{
		MPT_DataLoader *loader = [[MPT_DataLoader alloc] initWithDelegate:self andScheduler:scheduler]; // deallocated in loaderFinished:.
		[dataLoading setMinValue:1.0];
		[dataLoading setDoubleValue:1.0];
		[dataLoading setIndeterminate:NO];
		[NSThread detachNewThreadSelector:@selector(loadAllWeights) toTarget:loader withObject:nil];
	}
	else
	{
		[self setLoading:NO];
	}
}

- (void)checkLicense
{
	MPT_Demo *demo = [MPT_Demo sharedDemo];
	
	if( lic_invalid == [demo type] )
	{
		[demo notifyUserInvalidLicense];
		[NSApp terminate:self];
	}
	else if( [demo isEvaluation] )
	{
		if( [demo isDemo] )
		{
			if( [demo hasDemoEnded] )
			{
				[demo notifyUserDemoEnded];
				[NSApp terminate:self];
			}
		}
		else
		{
			if( ![demo startDemo] )
			{
				[demo notifyUserDemoNotAllowed];
				[NSApp terminate:self];
			}
		}
		
		// Evaluation copies need to prevent the user from saving data, so remove the menu item that shows the data recorder window.
		[[showDataWindowItem menu] removeItem:showDataWindowItem];
		// For some reason if we put up an alert box during awakeFromNib we end up with two help menus, so remove one.
		[[helpMenuItem menu] removeItem:helpMenuItem];

		// If we were already started and are still in the demo period,
		// or if we successfully started a demo period then notify the user how much time they have left.
		[demo notifyUserDemoLeft];
	}
	else if( [demo isLicensed] )
	{
		if( [demo licenseHasEnded] )
		{
			[demo notifyUserLicenseEnded];
			[NSApp terminate:self];
		}
		else
		{
			// For some reason if we put up an alert box during awakeFromNib we end up with two help menus, so remove one.
			[[helpMenuItem menu] removeItem:helpMenuItem];
			[demo notifyUserLicenseInfo];
		}
	}
	
	// if the license type is lic_internal, then just continue.	
}

// Callbacks for handling progress and errors from the weights loader
- (void)setLoadingMax:(NSNumber *)max
{
	[dataLoading setMaxValue:[max floatValue]];
	[dataLoading setDoubleValue:0.0];
}
- (void)setLoadingMessage:(NSString *)msg
{
	[dataLoadingMessage setStringValue:NSLocalizedString( msg, msg )];
}
- (void)incrementWeightsProgressBy:(NSNumber *)incNum
{
	float inc = 1.0;
	if( incNum )
		inc = [incNum floatValue];
	[dataLoading incrementBy:inc];
	[dataLoading displayIfNeeded];
}
- (void)loaderFinished:(MPT_DataLoader *)loader
{
	BOOL hadErrors = NO;
	NSEnumerator *errorEnum = [[loader errors] objectEnumerator];
	id<MPT_PluginProtocol> plugin;
	while( plugin = [errorEnum nextObject] )
	{
		NSString *pluginName = [plugin localizedName];
		NSString *msg = [NSString stringWithFormat:@"Unable to load all '%@' weights files.", pluginName];
		
		[plugin setEnabled:NO];

		[[NSAlert alertWithMessageText:msg
						 defaultButton:@"OK"
					   alternateButton:nil
						   otherButton:nil
			 informativeTextWithFormat:@"The '%@' plugin will not work correctly and has been disabled.", pluginName] runModal];
		hadErrors = YES;
	}

	if( hadErrors )
		[tileView setNeedsDisplay:YES];
	
	[self setLoading:NO];
	
	[loader release];
	
	// Now that all of the weights are loaded, we can try starting the web cam, if the pref is set.
	if( [self autostartWebCam] )
		[self startVideo:self];	
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
	[self interrupt:self];
	if( [self videoTimer] )
	{
		[self stopVideo:self];
	}
	[scheduler cleanup];
	[scheduler release];
	scheduler = nil;
	//	NSTerminateLater - it may be ok to proceed with termination later.  The application must call -replyToApplicationShouldTerminate: with YES or NO once the answer is known
	//	this return value is for delegates who need to provide document modal alerts (sheets) in order to decide whether to quit.
	
	return NSTerminateNow;
}

- (void)applicationWillTerminate:(NSNotification *)notification
{
	[pluginController applicationWillTerminate:notification];
}

- (NSWindow *)window
{
	return window;
}

- (IBAction)interrupt:(id)sender
{
	interrupt = YES;
	[sourceLoader interrupt];
//	[pluginController performSelector:@selector(resetViews) withObject:nil afterDelay:0.1];
}

- (IBAction)chooseSource:(id)sender
{
	NSOpenPanel *panel = [NSOpenPanel openPanel];
	[panel setCanChooseDirectories:YES];
	[panel setAllowsMultipleSelection:YES];
	[panel setCanChooseFiles:YES];
	[panel setMessage:@"Choose files and/or directories to process"];
	[panel setAccessoryView:openPanelAccessory];
	
	[cert setInputIsFaces:NO];
	
	[panel beginSheetForDirectory:nil
							 file:nil
							types:allTypes
				   modalForWindow:[self window]
					modalDelegate:self
				   didEndSelector:@selector(openSourcePanelDidEnd:returnCode:contextInfo:)
					  contextInfo:nil];
}

- (void)openSourcePanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode  contextInfo:(void *)contextInfo
{
	if( returnCode == NSOKButton )
	{
		NSArray *filenames = [panel filenames];
		if( [filenames count] > 0 )
		{
			NSString *path = [filenames objectAtIndex:0];
			if( [path length] > 0 )
			{
				[self prepareForProcessing];
				batchStart = [[NSDate date] retain];
				[sourceLoader processSources:[panel filenames]];
			}
		}
	}
}

- (void)prepareForProcessing
{
	interrupt = NO;
	nextFrame = frameNum; // make sure we're in sync. I could also just reset to zero, but it might be useful to leave it counting.
	[self setIsProcessing:YES];
	[progress startAnimation:self];
	[frameRateField setStringValue:@""];
	sourceLoaderFinished = NO;
	scheduleIsEmpty = NO;
	[resultsBuffer removeAllObjects];
}

- (void)doProcessingFinished
{
	[progress stopAnimation:self];
	[self setIsProcessing:NO];
	interrupt = YES;
	[pluginController performSelector:@selector(resetViews) withObject:nil afterDelay:0.1];
	if( batchStart )
	{
		NSLog( @"Batch run time: %f", [[NSDate date] timeIntervalSinceDate:batchStart] );
		[batchStart release];
		batchStart = nil;
	}
}

- (void)sourceProcessingFinished
{
	sourceLoaderFinished = YES;
	if( [scheduler isEmpty] )
	{
		[self doProcessingFinished];
	}
}

- (void)scheduleIsEmpty
{
	scheduleIsEmpty = YES;
	if( sourceLoaderFinished )
	{
		[self doProcessingFinished];
	}
}

- (BOOL)displayGray
{
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"displayGray"];
}

- (BOOL)findFaces
{
	return [[NSUserDefaults standardUserDefaults] boolForKey:@"findFaces"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	if( [keyPath isEqualToString:@"findFaces"] )
	{
		if( ![[change objectForKey:NSKeyValueChangeNewKey] boolValue] )
			[imageView resetFaceAndEyes];
	}
}

- (float)videoWidth {
    return [videoController videoWidth];
}

- (void)setVideoWidth:(float)value {
	[videoController setVideoWidth:value];
}

- (float)videoHeight {
	return [videoController videoHeight];
}

- (void)setVideoHeight:(float)value {
	[videoController setVideoHeight:value];
}

- (BOOL)loading
{
    return loading;
}

- (void)setLoading:(BOOL)value
{
    if (loading != value)
	{
        loading = value;
		[self setIsProcessing:value];
    }
}

- (BOOL)isProcessing;
{
//	if( loading ) return NO;
//	if( [self videoTimer] ) return NO;
    return isProcessing;
}
- (void)setIsProcessing:(BOOL)ignored
{
	isProcessing = ignored;
}

- (BOOL)videoTimer
{
    return videoTimer;
}

- (void)setVideoTimer:(BOOL)value
{
    videoTimer = value;
}

- (MPT_CERT *)cert
{
    return cert;
}

- (IBAction)imageChanged:(id)sender
{
	BOOL haveRImage = NO;
	NSImage *image = NULL;
	NSString *imagePath = NULL;
	if( [sender isKindOfClass:[NSImage class]] )
	{
		image = (NSImage *)sender;
	}
	else if( [sender isKindOfClass:[MPT_Pair class]] )
	{
		MPT_Pair *pair = (MPT_Pair *)sender; 
		image = [pair first];
		imagePath = [pair second];
	}
	else if( [sender isKindOfClass:[MPT_FaceView class]] )
	{
		[imageView resetFaceAndEyes];
		image = [imageView image];
		imagePath = [sender lastDropPath];
	}
	else if( [sender isKindOfClass:[NSString class]] )
	{
		haveRImage = YES;
		image = NULL;
		imagePath = sender;
	}
	else
		return;
	
	NSMutableDictionary *inputs = [NSMutableDictionary dictionary];
	if( image )
		[inputs setObject:image forKey:@"CERT.image"];
	if( imagePath )
		[inputs setObject:imagePath forKey:@"CERT.path"];
	[inputs setObject:[NSNumber numberWithUnsignedInt:frameNum++] forKey:@"CERT.frame"];
	[inputs setObject:[NSDate date] forKey:@"CERT.date"];
	
	if( ![doClient doesScheduleInput:inputs] )
		[scheduler scheduleInput:inputs];
}

- (void)handleUnsortedResults:(NSDictionary *)pluginResults
{
	unsigned int curFrame = [[pluginResults objectForKey:@"CERT.frame"] unsignedIntValue];
	
	if( curFrame == nextFrame )
	{
		[self handleResults:pluginResults];
		++nextFrame;
	}
	else if( 0 == [resultsBuffer count] )
	{
		[resultsBuffer addObject:pluginResults];
	}
	else
	{
		BOOL inserted = NO;
		for( int ind = 0; ind < [resultsBuffer count]; ++ind )
		{
			unsigned int frm = [[[resultsBuffer objectAtIndex:ind] objectForKey:@"CERT.frame"] unsignedIntValue];
			if( curFrame < frm )
			{
				inserted = YES;
				[resultsBuffer insertObject:pluginResults atIndex:ind]; // have to make sure it's added at the right place.
				break;
			}
		}
		if( !inserted )
		{
			[resultsBuffer addObject:pluginResults];
		}
	}
	
	for( int ind = 0; ind < [resultsBuffer count]; ++ind )
	{
		NSDictionary *res = [resultsBuffer objectAtIndex:ind];
		unsigned int frm = [[res objectForKey:@"CERT.frame"] unsignedIntValue];
		if( frm == nextFrame )
		{
			[self handleResults:res];
			[resultsBuffer removeObjectAtIndex:ind];
			++nextFrame;
			--ind; // because we removed an object
		}
		else if( frm < nextFrame )
		{
			// uh, oh, missed one. Silently drop it, for now
			NSLog( @"Dropping frame %d", frm );
			[resultsBuffer removeObjectAtIndex:ind];
			--ind; // because we removed an object
		}
		else if( frm > nextFrame )
		{
			break;
		}
	}
}

- (void)handleResults:(NSDictionary *)pluginResults
{
	// First let all post processing plugins run
	NSMutableDictionary *tmp = [[pluginResults mutableCopy] autorelease];
	NSEnumerator *postEnum = [[pluginController enabledPluginsForStep:MPT_PluginStep_Results] objectEnumerator];
	id<MPT_PluginProtocolResults> postPlugin;
	while( postPlugin = [postEnum nextObject] )
	{
		NSMutableDictionary *postProc = nil;
		@try
		{
			postProc = [postPlugin processResults:pluginResults usingController:pluginController];
		}
		@catch( NSException *exception )
		{
			NSLog( @"Error running plugin %@ (ID %@). Error Message: %@", [postPlugin localizedName], [postPlugin pluginID], [exception reason] );
		}
		if( postProc && ([postProc count] > 0) )
			[tmp setObject:postProc forKey:[postPlugin pluginID]];
	}
	pluginResults = tmp;
	
	// Get results for all plugins and update their views.
	NSEnumerator *pluginEnum = [[pluginController plugins] objectEnumerator];
	id<MPT_PluginProtocol> plugin;
	while( plugin = [pluginEnum nextObject] )
	{
		NSEnumerator *labelEnum = [[plugin labels] objectEnumerator];
		NSString *label;
		while( label = [labelEnum nextObject] )
		{
			MPT_AUView *view = [plugin viewForLabel:label];
			if( [plugin enabled] )
			{
				NSNumber *resNum = [pluginResults objectForKey:[plugin pluginID]];
				if( !resNum )
				{
					[view addNoValue];
				}
				else if( [resNum isKindOfClass:[NSNumber class]] )
				{
					double res = [resNum doubleValue];
					[view addValue:res];
				}
				else if( [resNum isKindOfClass:[NSDictionary class]] )
				{
					double res = [[(NSDictionary *)resNum objectForKey:label] doubleValue];
					[view addValue:res];
				}
				else
				{
					NSLog( @"Unknown type in plugin results" );
					[view addNoValue];
				}
			}
			else
			{
				[view addNoValue];
			}
		}
	}
		
	NSImage *image = [pluginResults objectForKey:@"CERT.image"];	
	if( [self displayGray] || !image )
	{
		MPT_RimageAdaptor *rimageAdaptor = [pluginResults objectForKey:@"CERT.rimage"];
		image = [rimageAdaptor image];
	}
	
	NSArray *faces = [pluginResults objectForKey:@"CERT.faces"];
	
	[imageView setFaces:faces];
	[imageView setImage:image];	
	
	NSString *imagePath = [pluginResults objectForKey:@"CERT.path"];
	if( imagePath )
	{
		[imageView setLastDropPath:imagePath];
		[fileNameField setStringValue:imagePath];
	}
	
	[dataRecorder recordDataPoint:pluginResults];
	
	if( [self videoTimer] )
	{
		NSTimeInterval dur = [[NSDate date] timeIntervalSinceDate:batchStart];
		if( dur > 30.0 )
		{
			[batchStart release];
			batchStart = [[NSDate date] retain];
			int curFrame = [[pluginResults objectForKey:@"CERT.frame"] intValue];
			float frmPerSec = curFrame - videoBatchFrame; // number of frames
			frmPerSec /= dur;
			NSLog( @"Average frame rate over last 30 seconds: %f", frmPerSec );
			videoBatchFrame = curFrame;
		}
		[frameRateField setFloatValue:[videoController frameRate]];
	}

}

- (IBAction)startVideo:(id)sender
{
	if( [self videoTimer] )
	{
		[self stopVideo:self];
	}
	else
	{
		[self prepareForProcessing];
		videoBatchFrame = frameNum;
		[self setVideoTimer:YES];
		[fileNameField setStringValue:@""];
		[imageView resetFaceAndEyes];
		[videoController start];

		if( ![videoController cameraStarted] )
		{
			[self stopVideo:self];
			if( [self autostartWebCam] )
			{
				NSLog( @"Quitting CERT: unable to connect to a camera in autostart mode." );
				[NSApp terminate:self];
			}
			else
			{
				NSRunAlertPanel( @"Video Error", @"No video source was found, or an error was encountered while trying to access the video.", @"OK", nil, nil );
			}
		}
		else
		{
			// My testing suggests that even on single processor machines, running this in a separate thread doesn't hurt and may even help a bit. So don't use this timer option.
			if( [[NSUserDefaults standardUserDefaults] boolForKey:@"useVideoTimer"] )
				[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(videoTimerFired:) userInfo:NULL repeats:NO];
			else
				[NSThread detachNewThreadSelector:@selector(threadedVideo) toTarget:self withObject:nil];
		}
	}
}

- (IBAction)stopVideo:(id)sender
{
	[videoController stop];
	[self setVideoTimer:NO];
	//	[self doProcessingFinished];
	[self sourceProcessingFinished];
}

- (void)handleVideoError:(NSError *)error
{
	if( [self autostartWebCam] )
	{
		NSLog( @"Quitting CERT: lost camera connection in autostart mode." );
		[NSApp terminate:self];
	}
	else
	{
		NSAlert *theAlert = [NSAlert alertWithError:error];
		if( -2211 == [error code] )
			[theAlert setInformativeText:@"Possible lost camera connection. Try unplugging it then plugging it back in again."];
		int button = [theAlert runModal];
		if( button != NSAlertFirstButtonReturn )
		{
			// handle
		}
		[self stopVideo:self];
	}
}

- (void)videoTimerFired:(NSTimer *)timer
{
	if( interrupt )
	{
		[self stopVideo:self];
		return;
	}
	
	if( [self videoTimer] )
	{
		NSImage *vImage = (NSImage *)[videoController lastImage];
		MPT_RimageAdaptor *adaptor = [videoController lastRimage];
		NSError *error = [videoController lastError];
		
		if( error )
		{
			[self handleVideoError:error];
			return;
		}
		
		if( vImage )
		{
			NSString *imagePath = [NSString stringWithFormat:@"frame%d", frameNum];
			NSMutableDictionary *inputs = [NSMutableDictionary dictionary];
			if( vImage )
				[inputs setObject:vImage forKey:@"CERT.image"];
			if( adaptor )
				[inputs setObject:adaptor forKey:@"CERT.rimage"];
			[inputs setObject:imagePath forKey:@"CERT.path"];
			[inputs setObject:[NSNumber numberWithUnsignedInt:frameNum++] forKey:@"CERT.frame"];
			[inputs setObject:[NSDate date] forKey:@"CERT.date"];
			
			if( ![doClient doesScheduleInput:inputs] )
				[scheduler scheduleInput:inputs];
		}
		else
		{
			// So no errors, but also no images. Usually that's because the iSight iris is closed.
		}
		[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(videoTimerFired:) userInfo:NULL repeats:NO];
	}
}

- (void)threadedVideo
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	int cnt = 0;
	
	while( !interrupt )
	{
		NSImage *vImage = (NSImage *)[videoController lastImage];
		MPT_RimageAdaptor *adaptor = [videoController lastRimage];
		NSError *error = [videoController lastError];
		
		if( error )
		{
			[self performSelectorOnMainThread:@selector(handleVideoError:) withObject:error waitUntilDone:NO];
			[pool release];
			return;
		}
		
		if( vImage || adaptor )
		{
			NSMutableDictionary *inputs = [NSMutableDictionary dictionary];
			if( vImage )
				[inputs setObject:vImage forKey:@"CERT.image"];
			if( adaptor )
				[inputs setObject:adaptor forKey:@"CERT.rimage"];
			[inputs setObject:[NSString stringWithFormat:@"frame%d", frameNum] forKey:@"CERT.path"];
			[inputs setObject:[NSNumber numberWithUnsignedInt:frameNum++] forKey:@"CERT.frame"];
			[inputs setObject:[NSDate date] forKey:@"CERT.date"];
			
			NSDate *limit = [NSDate dateWithTimeIntervalSinceNow:(1.0/60.0)];
			if( ![doClient doesScheduleInput:inputs] )
				[scheduler scheduleInput:inputs];
			NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:limit];
			if( elapsed < 0.0 )
			{
				//NSLog( @"Schedule elapsed time: %lf", elapsed );
				[NSThread sleepUntilDate:limit];
			}
		}
		if( 0 == (cnt % 30) )
		{
			[pool release];
			pool = [[NSAutoreleasePool alloc] init];
		}
		// We have to throttle it in this case, otherwise we overwhelm the main thread with calls to handleResults:
		if( ![self findFaces] )
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:(1.0/60.0)]];
	}
	[pool release];
	
	[self performSelectorOnMainThread:@selector(stopVideo:) withObject:self waitUntilDone:NO];
}

- (IBAction)showPreferences:(id)sender
{
	if( !preferences )
	{
		preferences = [[MPT_Preferences alloc] initWithWindowNibName:@"MPTPrefs"];
		[preferences setAppDelegate:self];
		[preferences showWindow:sender];
	}
	else
	{
		[[preferences window] makeKeyAndOrderFront:sender];
	}
}

- (IBAction)showDataWindow:(id)sender
{
	if( ![[MPT_Demo sharedDemo] isEvaluation] )
		[dataWindow makeKeyAndOrderFront:sender];
}

- (void)displayAboutPanel:(id)sender
{
	NSBundle *certBundle = [NSBundle bundleWithIdentifier:@"edu.ucsd.mplab.CERTFramework"];
	NSString *mptVersion = [certBundle objectForInfoDictionaryKey:@"MPTVersion"];
	NSString *certVersion = [certBundle objectForInfoDictionaryKey:@"CERTVersion"];

//	int rev = getCertRevision();
////	char *revDate = getCertRevisionDate();
//	const char *mptRev = getMPTRevisionString();
	NSString *demoString = @"";

	if( [[MPT_Demo sharedDemo] isEvaluation] )
		demoString = @" Eval";

	NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"build %@::%@%@", mptVersion, certVersion, demoString], @"Version", nil]; // , @"1.0", @"ApplicationVersion", nil
	[NSApp orderFrontStandardAboutPanelWithOptions:options];
//	[NSApp orderFrontStandardAboutPanel:sender];
}

- (void)faceDisplayTypeChanged:(NSNotification *)notification
{
	[imageView setFaceType:[[NSUserDefaults standardUserDefaults] integerForKey:@"FaceDisplayType"]];
}

- (BOOL)autostartWebCam
{
	return [[NSUserDefaults standardUserDefaults] boolForKey:@"autostartWebCam"];
}
@end
