//
//  TestController.m
//  SequenceGrabberTest
//
//  Created by Tim Omernick on 3/18/05.
//  Copyright 2005 Tim Omernick. All rights reserved.
//

#import "TestController.h"
#import "MPT_PluginController.h"

#import <CocoaSequenceGrabber/CocoaSequenceGrabber.h>

#import "MPT_RimageCamera.h"

@interface TestController(PrivateMethods)
- (void)setLastImage:(CSGImage *)value;
- (void)setLastRimage:(MPT_RimageAdaptor *)value;
- (void)setLastError:(NSError *)value;
- (void)setCameraStarted:(BOOL)value;
@end

static const int frameCount = 30;

@implementation TestController
+ (NSString *)videoServerID
{
	return @"edu.ucsd.mplab.plugins.VideoServer";
}

// Init and dealloc
- (id)init
{
	if( self = [super init] )
	{
		// Start recording
		camera = [[MPT_RimageCamera alloc] init];
		[camera setDelegate:self];
		[self setCameraStarted:NO];
		index = 0;
		for( int i=0; i<frameCount; ++i )
			rates[i] = 0.0;
		lastPulledTime = -1.0;
		[self setVideoWidth:320];
		[self setVideoHeight:240];
//		[camera startWithSize:NSMakeSize(640, 480)];
		[self setFrameRateType:pull_rate];
		
		[self setThreaded:YES];
		
		MPT_PluginController *pc = [MPT_PluginController defaultController];
		id tmp = [pc pluginForPluginID:[[self class] videoServerID]];
		if( tmp && [tmp conformsToProtocol:@protocol(VideoServerProtocol)]
			&& [tmp conformsToProtocol:@protocol(MPT_PluginProtocol)] )
		{
			vsPlugin = [tmp retain];
		}
	}
	return self;
}


- (void)dealloc;
{
	if( [self cameraStarted] )
		[camera stop];
	[camera release];
	[lastImage release];
	[lastRimage release];
	[imageLock release];
	[rimageLock release];
	[errorLock release];
	[vsPlugin release];
	[super dealloc];
}

- (CSGImage *)lastImage
{
	[imageLock lock];
	if( [self frameRateType] == pull_rate )
	{
		NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
		if( lastPulledTime > -1.0 )
			rates[index++] = now - lastPulledTime;
		lastPulledTime = now;
		if( index >= frameCount ) index = 0;
	}
	CSGImage *tmp = [[lastImage retain] autorelease]; //[[lastImage copy] autorelease];
	[imageLock unlock];
	return tmp;
}

- (void)setLastImage:(CSGImage *)value
{
    if (lastImage != value)
	{
		[imageLock lock]; // - (BOOL)lockBeforeDate:(NSDate *)limit
		if( lastImage )
		{
			if( [self frameRateType] == instant_rate )
			{
				NSTimeInterval diff = [value sampleTime] - [lastImage sampleTime];
				rates[0] = 1.0/diff;
			}
			else if( [self frameRateType] == avg_rate )
			{
				NSTimeInterval diff = [value sampleTime] - [lastImage sampleTime];
				rates[index++] = diff;
				if( index >= frameCount ) index = 0;
			}
		}
		
        [lastImage release];
        lastImage = [value retain];
		[imageLock unlock];
    }
}

- (void)setLastRimage:(MPT_RimageAdaptor *)value
{
    if( lastRimage != value )
	{
		[rimageLock lock];
        [lastRimage release];
        lastRimage = [value retain];
		[rimageLock unlock];
    }
}

- (MPT_RimageAdaptor *)lastRimage
{
	[rimageLock lock];
	MPT_RimageAdaptor *cp = [[lastRimage retain] autorelease]; // [[lastRimage copy] autorelease];
	[rimageLock unlock];
	return cp;
}

- (NSError *)lastError
{
	[errorLock lock];
	NSError *cp = [[lastError copy] autorelease];
	[errorLock unlock];
    return cp;
}

- (void)setLastError:(NSError *)value
{
    if( lastError != value )
    {
		[errorLock lock];
        [lastError release];
        lastError = [value retain];
		[errorLock unlock];
    }
}

/*
 This is mostly for use by an NSInvocation so that images can be pulled from the camera immediately
 before we are ready to process them, rather than before we even start waiting for the processor queue
 to have an available processor (in MPT_Scheduler).
 */
- (void)addImagesToInputs:(NSMutableDictionary *)inputs
{
	CSGImage *vImage = [self lastImage];
	MPT_RimageAdaptor *adaptor = [self lastRimage];
	NSError *error = [self lastError];
	
	if( error )
	{
		[inputs setObject:error forKey:@"CERT.error"];
	}
	// else
	{
		if( vImage )
			[inputs setObject:vImage forKey:@"CERT.image"];
		
		if( adaptor )
			[inputs setObject:adaptor forKey:@"CERT.rimage"];
	}
}

- (NSTimeInterval)frameRate
{
	NSTimeInterval res = 0.0;
	[imageLock lock];
	if( [self frameRateType] == instant_rate )
	{
		res = rates[0];
	}
	else if( [self frameRateType] == avg_rate )
	{
		NSTimeInterval rate = 0.0;
		for( int i=0; i<frameCount; ++i )
			rate += rates[i];
		res = frameCount / rate;
	}
	else if( [self frameRateType] == pull_rate )
	{
		NSTimeInterval rate = 0.0;
		for( int i=0; i<frameCount; ++i )
			rate += rates[i];
		res = frameCount / rate;
	}
	[imageLock unlock];
	return res;
}

- (float)videoWidth {
    return videoWidth;
}

- (void)setVideoWidth:(float)value {
    if (videoWidth != value) {
        videoWidth = value;
    }
}

- (float)videoHeight {
    return videoHeight;
}

- (void)setVideoHeight:(float)value {
    if (videoHeight != value) {
        videoHeight = value;
    }
}

- (frameRateTypeType)frameRateType
{
    return frameRateType;
}
- (void)setFrameRateType:(frameRateTypeType)value
{
	frameRateType = value;
}

- (BOOL)threaded
{
    return threaded;
}

- (void)setThreaded:(BOOL)value
{
	if( threaded != value )
	{
		// we can't do anything if the camera is running (UI should prevent this).
		if( [self cameraStarted] )
			return;
		
		threaded = value;
		
		if( threaded )
		{
			imageLock = [[NSLock alloc] init];
			rimageLock = [[NSLock alloc] init];
			errorLock = [[NSLock alloc] init];
		}
		else
		{
			NSLock *hold;
			// we use a copy of the pointer, and null out the member variable so no one else can lock it before we release it.
			
			[imageLock lock];
			hold = imageLock;
			imageLock = NULL;
			[hold unlock];
			[hold release];
			
			[rimageLock lock];
			hold = rimageLock;
			rimageLock = NULL;
			[hold unlock];
			[hold release];

			[errorLock lock];
			hold = errorLock;
			errorLock = NULL;
			[hold unlock];
			[hold release];
		}
			
		[camera setThreaded:threaded];
	}
}

- (void)setCameraStarted:(BOOL)value
{
	cameraStarted = value;
}
- (BOOL)cameraStarted
{
    return cameraStarted;
}

- (void)start
{
	NSSize size = NSMakeSize( videoWidth, videoHeight );
	[self setCameraStarted:[camera startWithSize:size]];
	if( [vsPlugin enabled] )
	{
		[vsPlugin setRowBytes:[camera getRowBytes]];
		[vsPlugin startWithSize:size];
	}
}

- (void)stop
{
	if( [self cameraStarted] )
	{
		[camera stop];
	}
}

// MPT_RimageCamera delegate
- (void)cameraDidStop:(CSGCamera *)aCamera
{
	[self setCameraStarted:NO];
	if( [vsPlugin enabled] )
		[vsPlugin stop];

	for( int i=0; i<frameCount; ++i )
		rates[i] = 0.0;
}

- (void)camera:(CSGCamera *)aCamera didReceiveFrame:(CSGImage *)aFrame
{
	NSAssert( (aCamera == camera), @"Invalid camera in TestController" );
	if( [self cameraStarted] )
	{
		[self setLastImage:aFrame];
		[self setLastError:nil];
		unsigned char *pixels = [camera basePixelAddress];
		if( [vsPlugin enabled] )
			[vsPlugin copyFrameToBuffer:pixels];
	}
}

- (void)camera:(CSGCamera *)aCamera didReceiveAudio:(AudioStreamBasicDescription)asbd :(Ptr)audioData :(long)audioLen
{
	NSAssert( (aCamera == camera), @"Invalid camera in TestController" );
	if( [self cameraStarted] )
	{
		[self setLastError:nil];
		if( [vsPlugin enabled] ) {
			[vsPlugin addAudioChunkToCurrentFrame:asbd:audioData:audioLen];
		}
	}
}

- (void)camera:(CSGCamera *)aCamera didReceiveRImage:(MPT_RimageAdaptor *)aFrame
{
	if( [self cameraStarted] )
	{
		[self setLastRimage:aFrame];
		[self setLastError:nil];
	}
}

- (void)camera:(CSGCamera *)aCamera didReceiveError:(OSErr)err
{
	if( [self cameraStarted] )
	{
		[self setLastError:[NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:[NSDictionary dictionaryWithObject:[NSCalendarDate date] forKey:@"datetime"]]];
	}
}

// NSWindow delegate

- (void)windowWillClose:(NSNotification *)notification;
{
	if( [self cameraStarted] )
		[camera stop];
}

@end
