//
//  MPT_DocumentController.m
//  AUCoderDemo
//
//  Created by Andrew Salamon on 7/18/09.
//  Copyright 2009 Machine Perception Laboratory, University of California San Diego. All rights reserved.
//

#import "MPT_DocumentController.h"

#import "MPT_AUAppDelegate.h"
#import "MPT_FaceView.h"

@interface MPT_FilePlayback : NSObject<MPT_DocHandler>
{
    NSString *path;
	NSArray *results;
    NSString *moviePath;
}

- (NSString *)path;
- (void)setPath:(NSString *)value;

- (NSArray *)results;
- (void)setResults:(NSArray *)value;

- (NSString *)moviePath;
- (void)setMoviePath:(NSString *)value;

- (NSString *)firstPathFromResults:(NSArray *)_results;

@end

@implementation MPT_FilePlayback
+ (BOOL)handlesReading
{
	return YES;
}
+ (BOOL)handlesWriting
{
	return YES;
}
+ (NSString *)preferredExtension
{
    return @"mpt4up";
}

- (void)dealloc
{
    [path release];
    [results release];
    [moviePath release];
    [super dealloc];
}

- (NSString *)path {
    return [[path retain] autorelease];
}

- (void)setPath:(NSString *)value {
    if (path != value) {
        [path release];
        path = [value copy];
    }
}

- (NSArray *)results {
    return [[results retain] autorelease];
}

- (void)setResults:(NSArray *)value {
    if (results != value) {
        [results release];
        results = [value retain];
    }
}

- (NSString *)moviePath {
    return [[moviePath retain] autorelease];
}

- (void)setMoviePath:(NSString *)value {
    if (moviePath != value) {
        [moviePath release];
        moviePath = [value copy];
    }
}

- (BOOL)writeResults:(NSArray *)resultsArray toFile:(NSString *)outputPath usingController:(id<MPT_PluginControllerProtocol>)pluginController
{
	/*
	 Two possible culprits for why this doesn't work:
	 0 : <CFString 0x8e480 [0xa03fa174]>{contents = "CERT.date"} = <CFDate 0x2a76d620 [0xa03fa174]>{time = 271033340}
	 1 : <CFString 0x8e4d0 [0xa03fa174]>{contents = "CERT.faces"} = <CFArray 0x1a661410 [0xa03fa174]>{type = mutable-small, count = 1, values = (
	 0 : <MPT_Face: 0x2a763d10>	 
	 */
	if( [NSKeyedArchiver archiveRootObject:resultsArray toFile:outputPath] )
	{
        return YES;
	}
    return NO;
    
    if( ![resultsArray writeToFile:outputPath atomically:YES] )
	{
//		NSMutableDictionary *oneMutRes = [[resultsArray objectAtIndex:0] mutableCopy];
//		NSArray *tmp = [NSArray arrayWithObject:[oneMutRes objectForKey:@"CERT.faces"]];
//		NSData *data = [NSKeyedArchiver archivedDataWithRootObject:tmp];
//
//		[oneMutRes removeObjectForKey:@"CERT.faces"];
//		NSLog( @"Removed CERT.faces" );
//		if( ![oneMutRes writeToFile:path atomically:YES] )
//		{
//			[oneMutRes removeObjectForKey:@"CERT.date"];
//			NSLog( @"Removed CERT.date" );
//			if( ![oneMutRes writeToFile:path atomically:YES] )
//			{
//				NSLog( @"Failed to write" );
//				return NO;
//			}
//		}
		return NO;
	}
	return YES;
}

- (NSArray *)resultsFromFile:(NSString *)_path
{
	NSArray *res = [NSKeyedUnarchiver unarchiveObjectWithFile:_path];
    if( [res count] > 0 )
    {
        [self setResults:res];
        [self setPath:_path];
        [self setMoviePath:[self firstPathFromResults:res]];
    }
	return res;
}

- (NSString *)firstPathFromResults:(NSArray *)_results
{
    int maxTries = 10;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSEnumerator *resEnum = [_results objectEnumerator];
    NSDictionary *pluginResults = nil;
    while( (maxTries-- > 0) && (pluginResults = [resEnum nextObject]) )
    {
        NSString *aPath = [pluginResults objectForKey:@"CERT.path"];
        if( aPath )
        {
            BOOL isDir;
            // @"CERT.path" really shouldn't contain the frame number but it might, so remove it.
            NSNumber *frame = [pluginResults objectForKey:@"CERT.frame"];
            if( frame )
            {
                NSString *suff = [NSString stringWithFormat:@":%d", [frame intValue]];
                if( [aPath hasSuffix:suff] )
                {
                    aPath = [aPath substringToIndex:([aPath length] - [suff length])];
                }
            }
            if( [fileManager fileExistsAtPath:aPath isDirectory:&isDir]
               && !isDir
               && [fileManager isReadableFileAtPath:aPath] )
            {
                return aPath;
            }
        }
    }
    
    return nil;
}
@end


@interface MPT_MovieOverlay : QTMovieView
{
	NSView *overlay;
}
- (NSView *)overlay;
- (void)setOverlay:(NSView *)value;
@end

@implementation MPT_MovieOverlay
- (void)dealloc
{
	[overlay release];
	[super dealloc];
}

- (NSView *)overlay {
    return [[overlay retain] autorelease];
}

- (void)setOverlay:(NSView *)value {
    if (overlay != value) {
        [overlay release];
        overlay = [value retain];
    }
}

- (void)drawRect:(NSRect)rect
{
	[super drawRect:rect];
	[overlay drawRect:rect];
}
@end


@interface MPT_DocumentController(PrivateMethods)
- (void)chooseOriginalMovieFile:(id)delegate;
- (void)setupMovieViewForDoc:(MPT_FilePlayback *)doc withDelegate:(id)delegate;
@end

/*
 1) A list of valid extensions for the open panel. Only for our internal doc types so I can add them to the quicktime and nsimage arrays.
 2) A list of valid extensions for the save panel. Again only internal extensions.
 3) A way to convert extension to a fixed, unique tag of some kind. Use the class name. We will need at least minimal classes anyway because we need to distinguish between filetypes that we read, write or both.
*/
@implementation MPT_DocumentController
+ (MPT_DocumentController *)sharedDocumentController
{
	static MPT_DocumentController *singleton = NULL;
	if( !singleton )
		singleton = [[MPT_DocumentController alloc] init];
	return singleton;
}

+ (float)framerateFromResults:(NSArray *)resultsArray
{
	float framerate = 30.0;
	if( [resultsArray count] > 0 )
	{
		NSDictionary *firstRes = [resultsArray objectAtIndex:0];
		NSString *path = [firstRes objectForKey:@"CERT.path"];
		NSMutableArray *pathComps = [[[path componentsSeparatedByString:@":"] mutableCopy] autorelease];
		if( [pathComps count] > 1 )
		{
			[pathComps removeLastObject];
			path = [pathComps componentsJoinedByString:@":"];
		}
		NSError *error = nil;
		QTMovie *movie = [QTMovie movieWithFile:path error:&error];
		if( movie )
		{
			QTTime dur = [movie duration];
			NSTimeInterval seconds;
			if( QTGetTimeInterval( dur, &seconds ) )
			{
				float tmpRate = [resultsArray count] / seconds;
				if( (tmpRate > 0.0) && (tmpRate < 600.00) ) // arbitrary upper limit
					framerate = tmpRate;
			}
		}
		else
		{
			// display or log an error message?
		}
	}
	return framerate;
}

- (id)init
{
	self = [super init];
	
	if( ![NSBundle loadNibNamed:@"MPT_FileFormat" owner:self] )
	{
		NSLog( @"Error loading file format nib." );
	}
	
	return self;
}

- (void)awakeFromNib
{
    NSArray *bundleTypes=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"];
    // instead of this hidden documents list, check each doc type to see if it has a class and if
    // that classes name begins with MPT_File
    NSEnumerator *typeEnum=[bundleTypes objectEnumerator];
    NSDictionary *dict;
    
	[fileFormatPopupButton removeAllItems];
	
    while( dict = [typeEnum nextObject] )
    {
		// Should handle this, but not yet.
		//        NSArray *types=[dict objectForKey:@"LSItemContentTypes"];
        NSArray *extensionsForOneDocType=[dict objectForKey:@"CFBundleTypeExtensions"];
        if(extensionsForOneDocType)
        {
            NSString *description=[dict objectForKey:@"CFBundleTypeName"];
			NSString *docClassName = [dict objectForKey:@"NSDocumentClass"];
			Class docClass = NSClassFromString(docClassName);
			if( docClass && [docClass handlesWriting] )
			{
                [fileFormatPopupButton addItemWithTitle:description];
                NSMenuItem *newItem = [fileFormatPopupButton itemWithTitle:description]; // lastItem method might work too
                id docHandler = [[docClass alloc] init];
                [newItem setRepresentedObject:docHandler];
                [newItem setTarget:self];
                [newItem setAction:@selector(fileTypeChanged:)];
			}
			else
			{
				// So far this only happens for the image, movie and license types.
				//				NSLog( @"No class or it doesn't handle writing for doctype %@", description );
			}
        }
    }
    
    [fileFormatPopupButton setTarget:self];
    [fileFormatPopupButton setAction:@selector(fileTypeChanged:)];
}

    // From: http://code.google.com/p/xee/source/browse/trunk/CSFileTypeList.m?r=11
- (NSArray *)extensionsForReading
{
	NSMutableSet *allExtensions = [NSMutableSet set];
    NSArray *bundleTypes=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"];
    // instead of this hidden documents list, check each doc type to see if it has a class and if
    // that classes name begins with MPT_File
    NSEnumerator *typeEnum=[bundleTypes objectEnumerator];
    NSDictionary *dict;
    
    while( dict = [typeEnum nextObject] )
    {
		// Should handle this, but not yet.
//        NSArray *types=[dict objectForKey:@"LSItemContentTypes"];
        NSArray *extensionsForOneDocType=[dict objectForKey:@"CFBundleTypeExtensions"];
        if(extensionsForOneDocType)
        {
            NSString *description=[dict objectForKey:@"CFBundleTypeName"];
			NSString *docClassName = [dict objectForKey:@"NSDocumentClass"];
			Class docClass = NSClassFromString(docClassName);
			if( docClass && [docClass handlesReading] )
			{
				NSLog( @"Class %@ handles reading for doctype %@", docClassName, description );
				[allExtensions addObjectsFromArray:extensionsForOneDocType];
			}
			else
			{
				NSLog( @"No class or it doesn't handle reading for doctype %@", description );
			}
        }
    }
    
    return [allExtensions allObjects];
}

- (NSArray *)extensionsForWriting
{
	NSMutableSet *allExtensions = [NSMutableSet set];
    NSArray *bundleTypes=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"];
    // instead of this hidden documents list, check each doc type to see if it has a class and if
    // that classes name begins with MPT_File
    NSEnumerator *typeEnum=[bundleTypes objectEnumerator];
    NSDictionary *dict;
    
    while( dict = [typeEnum nextObject] )
    {
		// Should handle this, but not yet.
		//        NSArray *types=[dict objectForKey:@"LSItemContentTypes"];
        NSArray *extensionsForOneDocType=[dict objectForKey:@"CFBundleTypeExtensions"];
        if(extensionsForOneDocType)
        {
            NSString *description=[dict objectForKey:@"CFBundleTypeName"];
			NSString *docClassName = [dict objectForKey:@"NSDocumentClass"];
			Class docClass = NSClassFromString(docClassName);
			if( docClass && [docClass handlesWriting] )
			{
				NSLog( @"Class %@ handles writing for doctype %@", docClassName, description );
				[allExtensions addObjectsFromArray:extensionsForOneDocType];
			}
			else
			{
				NSLog( @"No class or it doesn't handle writing for doctype %@", description );
			}
        }
    }
    
    return [allExtensions allObjects];
}

- (NSPopUpButton *)popupForWritingDocTypesWithRect:(NSRect)rect
{
	return fileFormatPopupButton;
//    NSPopUpButton *button = [[[NSPopUpButton alloc] initWithFrame:rect pullsDown:NO] autorelease];
//    
//    NSArray *bundleTypes=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"];
//    // instead of this hidden documents list, check each doc type to see if it has a class and if
//    // that classes name begins with MPT_File
//    NSEnumerator *typeEnum=[bundleTypes objectEnumerator];
//    NSDictionary *dict;
//    
//    while( dict = [typeEnum nextObject] )
//    {
//		// Should handle this, but not yet.
////        NSArray *types=[dict objectForKey:@"LSItemContentTypes"];
//        NSArray *extensionsForOneDocType=[dict objectForKey:@"CFBundleTypeExtensions"];
//        if(extensionsForOneDocType)
//        {
//            NSString *description=[dict objectForKey:@"CFBundleTypeName"];
//			NSString *docClassName = [dict objectForKey:@"NSDocumentClass"];
//			Class docClass = NSClassFromString(docClassName);
//			if( docClass && [docClass handlesWriting] )
//			{
//                [button addItemWithTitle:description];
//                id<NSMenuItem> newItem = [button itemWithTitle:description]; // lastItem method might work too
//                id docHandler = [[docClass alloc] init];
//                [newItem setRepresentedObject:docHandler];
//                [newItem setTarget:self];
//                [newItem setAction:@selector(fileTypeChanged:)];
//			}
//			else
//			{
//				// So far this only happens for the image, movie and license types.
////				NSLog( @"No class or it doesn't handle writing for doctype %@", description );
//			}
//        }
//    }
//    
//    [button setTarget:self];
//    [button setAction:@selector(fileTypeChanged:)];
//    return button;
}

- (IBAction)fileTypeChanged:(id)sender
{
    NSSavePanel *panel = savePanel;
    NSPopUpButton *popup = fileFormatPopupButton; // (NSPopUpButton *)[panel accessoryView];

    if( [popup isKindOfClass:[NSPopUpButton class]] )
    {
        id<MPT_DocHandler> docHandler = [[popup selectedItem] representedObject];
        if( docHandler && [[docHandler class] respondsToSelector:@selector(preferredExtension)] )
		{
			NSString *ext = [[docHandler class] preferredExtension];
//            [panel setRequiredFileType:[[docHandler class] preferredExtension]];
			[panel setAllowedFileTypes:[NSArray arrayWithObject:ext]];
			[savePanel setRequiredFileType:ext];
//			NSString *filename = 
			[savePanel setExtensionHidden:NO];
		}
        else
		{
//            [panel setRequiredFileType:nil];
			[panel setAllowedFileTypes:nil];
		}
    }
	[panel validateVisibleColumns];
}

- (void)saveDataModalToWindow:(NSWindow *)window withDelegate:(id)saveDelegate
{
    MPT_DocumentController *docs = self;
    NSPopUpButton *popup = fileFormatPopupButton;

    savePanel = [NSSavePanel savePanel];
    [savePanel setPrompt:@"Save Data"];
    [savePanel setTitle:@"Save Data"];
    [savePanel setDelegate:docs];
    [savePanel setAccessoryView:savePanelAccessoryView];
	[savePanel setExtensionHidden:NO];
	
    NSString *filename = @"AU Data";
    NSString *ext = @"txt";
    id<MPT_DocHandler> docHandler = [[popup selectedItem] representedObject];
    if( docHandler && [[docHandler class] respondsToSelector:@selector(preferredExtension)] )
        ext = [[docHandler class] preferredExtension];
//    filename = [filename stringByAppendingPathExtension:ext];
	[savePanel setAllowedFileTypes:nil];
	[savePanel setAllowedFileTypes:[NSArray arrayWithObject:ext]];
	[savePanel setRequiredFileType:ext];
	[savePanel setExtensionHidden:NO];

    [savePanel beginSheetForDirectory:nil
                                 file:filename
                       modalForWindow:window
                        modalDelegate:saveDelegate
                       didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)
                          contextInfo:popup];
}

//- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
//{
//    NSSavePanel *panel = sender;
//    NSPopUpButton *popup = (NSPopUpButton *)[panel accessoryView];
//	
//    if( [popup isKindOfClass:[NSPopUpButton class]] )
//    {
//        id<MPT_DocHandler> docHandler = [[popup selectedItem] representedObject];
//        if( docHandler && [[docHandler class] respondsToSelector:@selector(preferredExtension)] )
//		{
//			NSString *ext = [[docHandler class] preferredExtension];
//			filename = [filename stringByDeletingPathExtension];
//			filename = [filename stringByAppendingPathExtension:ext];
//		}
//    }
//	return filename;
//}

- (BOOL)handlePlaybackFiles:(NSArray *)filenames withDelegate:(id)delegate
{
	NSEnumerator *fileEnum = [filenames objectEnumerator];
	NSString *filename;
	
	appDelegate = delegate;
	
	while( filename = [fileEnum nextObject] )
	{
		if( [[filename pathExtension] isEqualToString:[MPT_FilePlayback preferredExtension]] )
		{
            MPT_FilePlayback *doc = [[MPT_FilePlayback alloc] init];
            NSArray *playbackData = [doc resultsFromFile:filename];
            currentPlayback = doc;
            if( [playbackData count] > 0 )
            {
                // handle it here.
                // read in the playback file
                // get the first valid path entry
                // see if we can load that movie at that path (should extend it to also do directories, but not now)
                // if not, ask the user where the movie is
                if( ![doc moviePath] )
                {
                    // ask user where the movie is
                    [self performSelector:@selector(chooseOriginalMovieFile:) withObject:delegate afterDelay:0.01];
//                    [self chooseOriginalMovieFile:[delegate window]];
                }
				else
				{
					[self setupMovieViewForDoc:doc withDelegate:delegate];
				}

				[[NSDocumentController sharedDocumentController] performSelectorOnMainThread:@selector(noteNewRecentDocumentURL:) withObject:[NSURL fileURLWithPath:filename] waitUntilDone:NO];

                return YES;
            }
		}
	}
	return NO;
}

- (void)chooseOriginalMovieFile:(id)delegate
{
	NSOpenPanel *panel = [NSOpenPanel openPanel];
	[panel setCanChooseDirectories:NO];
	[panel setAllowsMultipleSelection:NO];
	[panel setCanChooseFiles:YES];
	[panel setMessage:@"Locate the original movie file."];
    //	[panel setAccessoryView:openPanelAccessory];
    	
	[panel beginSheetForDirectory:nil
							 file:nil
							types:[QTMovie movieFileTypes:QTIncludeCommonTypes]
				   modalForWindow:[delegate window]
					modalDelegate:self
				   didEndSelector:@selector(chooseOriginalMovieFileDidEnd:returnCode:contextInfo:)
					  contextInfo:(void *)delegate];
}

- (void)chooseOriginalMovieFileDidEnd:(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 )
            {                
                [currentPlayback setMoviePath:path];
				[self setupMovieViewForDoc:currentPlayback withDelegate:(id)contextInfo];
            }
        }
    }
}

//- (void)sortSubviewsUsingFunction:(NSComparisonResult (*)(id, id, void *))compare context:(void *)context
NSComparisonResult sortSubviews( id view1, id view2, void *context )
{
	if( [view1 isKindOfClass:[QTMovieView class]] )
	{
		return NSOrderedAscending;
	}
	return NSOrderedDescending;
}
/*
 create a movie view (subclass, probably) with controller
 set the movie
 Add the face view as a sub-view to the movie view and make sure it's in the right place and the right size
 Set the face view to not display the image and to not draw any background (i.e. be transparent)
 Add the movie view to the adjustable box where the Face View normally is
 Whenever the movie view's image changes, figure out which frame it's on and update the data views to match.
 Make sure to undo all of that before ending
 */
- (void)setupMovieViewForDoc:(MPT_FilePlayback *)doc withDelegate:(id)delegate
{
	MPT_FaceView *faceView = [delegate faceView];
	NSView *supView = [faceView superview];
	QTMovie *movie = [QTMovie movieWithFile:[doc moviePath] error:nil];
	if( !movie )
	{
		return;
	}
	
	movieView = [[MPT_MovieOverlay alloc] initWithFrame:[faceView frame]];
	[movieView setAutoresizingMask:NSViewMaxXMargin|NSViewMaxYMargin];
	[movieView setPreservesAspectRatio:YES];
	[movieView setDelegate:self];
//	[movie setDelegate:self];
	[movieView setMovie:movie];
	[movieView setOverlay:faceView];
//	[faceView retain];
//	[faceView removeFromSuperview];
//	[movieView addSubview:faceView];
//	[faceView release];
	[supView addSubview:movieView];
//	[supView sortSubviewsUsingFunction:sortSubviews context:nil];
	[faceView setOverlayOnly:YES];
	NSRect rect = [faceView frame];
	rect.size.height -= 20;
	rect.origin.y += 20;
//	[faceView setFrame:rect];
	
	[movie gotoEnd];
	endTime = [movie currentTime];
	[movie gotoBeginning];	
}

- (void)cancelMovie
{
	[movieView removeFromSuperview];
	[movieView release];
	movieView = nil;
	[[appDelegate faceView] setOverlayOnly:NO];
}

- (BOOL)movieShouldTask:(id)movie
{
//	[self view:movieView willDisplayImage:nil];
	return YES;
}

- (CIImage *)view:(QTMovieView *)view willDisplayImage:(CIImage *)image
{
	NSTimeInterval cur;
	NSTimeInterval end;
	QTTime curTime = [[view movie] currentTime];

	QTGetTimeInterval( curTime, &cur );
	QTGetTimeInterval( endTime, &end );
	
	NSArray *res = [currentPlayback results];
	double ratio = cur / end;
	int frameIndex = ratio * [res count];
	if( (frameIndex >= 0) && (frameIndex < [res count]) )
	{
		NSMutableDictionary *pluginResults = [res objectAtIndex:frameIndex];
		// temporary measure to prevent faces from being draw behind the movie view
		if( ![pluginResults isKindOfClass:[NSMutableDictionary class]] )
			pluginResults = [pluginResults mutableCopy];
		[pluginResults removeObjectForKey:@"CERT.faces"];

		// Tried to get the enjoyment displays to update while a user was scrubbing through video, but it didn't work.
//		[appDelegate performSelectorOnMainThread:@selector(updateViewsWith:) withObject:pluginResults waitUntilDone:YES];
		[appDelegate updateViewsWith:pluginResults];
		NSLog( @"syncing data" );
	}
	else
	{
		NSLog( @"frame index out of range." );
	}
	
	return image;
}

@end
