//
//  MPT_SimpleRemoteInput.m
//  MPT_SimpleRemoteInput
//
//  Created by Andrew Salamon on 1/26/09.
//  Copyright 2009 Machine Perception Laboratory, University of California San Diego.. All rights reserved.
//

#import "MPT_SimpleRemoteInput.h"

#import "MPT_Scheduler.h"

#define DEBUG 1

@interface MPT_SimpleRemoteInput(PrivateMethods)
- (void)parseData;
@end

@implementation MPT_SimpleRemoteInput
enum {
	notStarted = 0,
	inHeader,
	inBody,
	inFooter
};

- (id)initWithBundle:(NSBundle *)_bundle
{
	if( self = [super initWithBundle:_bundle] )
	{
		[self setPort:15555];
		if( ![NSBundle loadNibNamed:@"SRIPrefView" owner:self] )
			NSLog( @"Unable to load pref pane for Simple Remote Input plugin." );
//		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clientDidShutdown:) name:[MPT_DO_Client shutdownNotificationName] object:doClient];
		state = notStarted;
		adaptor = NULL;
		mdata = [[NSMutableData alloc] init];
		frame = 1;
	}
	
	return self;
}

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[mdata release];
	[portHandle release];
	[remoteFileHandle release];
	[super dealloc];
}

// It's not OK to copy this plugin, so return nil.
- (id)copyWithZone:(NSZone *)zone
{
	return nil;
}

- (NSString *)pluginID
{
	NSString *ident = [super pluginID];
	if( [ident length] > 0 )
		return ident;
	return @"edu.ucsd.mplab.plugins.SRI";
}

- (NSString *)localizedName
{
	return NSLocalizedStringFromTableInBundle(@"CERT Simple Remote Input", nil, [self bundle], @"");
}

- (NSString *)internalName
{
	return @"sri";
}

- (NSString *)step
{
	return MPT_PluginStep_Results;
}

- (NSView *)prefPane
{
	return prefView;
}

- (void)setEnabled:(BOOL)value
{
	[super setEnabled:value];
	if( value )
		[self start:self];
}
- (unsigned short)port
{
    return port;
}
- (void)setPort:(unsigned short)value
{
	if( 0 == value )
		port = 15555;
	else
		port = value;
}

- (BOOL)isRunning
{
	return isRunning;
}
- (void)setIsRunning:(BOOL)value
{
	if( isRunning != value)
	{
		isRunning = value;
	}
}

- (IBAction)start:(id)sender
{
	[self setIsRunning:NO];
	NSSocketPort  *socketPort = [[NSSocketPort alloc] initWithTCPPort:port];
	int fd = [socketPort socket];
	portHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];

	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc addObserver:self
		   selector:@selector(newConnection:)
			   name:NSFileHandleConnectionAcceptedNotification
			 object:portHandle];
	[nc addObserver:self
		   selector:@selector(otherNotifications:)
			   name:nil
			 object:portHandle];

	state = notStarted;
	[mdata release];
	mdata = [[NSMutableData alloc] init];
	[adaptor release];
	adaptor = nil;
	dataIndex = 0;
	imageIndex = 0;
	packets = 0;
	[self setIsRunning:YES];

	[portHandle acceptConnectionInBackgroundAndNotify];
}
- (IBAction)stop:(id)sender
{
	if( ![self isRunning] )
		return;
	[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:portHandle];
	[portHandle closeFile];
	[portHandle release];
	portHandle = nil;
	[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:remoteFileHandle];
//	[remoteFileHandle closeFile];
//	[remoteFileHandle release];
//	remoteFileHandle = nil;
	[self setIsRunning:NO];
}

- (void)otherNotifications:(NSNotification *)notification
{
	NSLog( @"portHandle notification: %@", [notification name] );
}


- (void)newConnection:(NSNotification *)notification
{
	NSDictionary *userInfo = [notification userInfo];
	remoteFileHandle = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
	
	NSNumber *errorNo = [userInfo objectForKey:@"NSFileHandleError"];
	if( errorNo ) {
		NSLog(@"NSFileHandle Error: %@", errorNo);
		[self stop:self];
		return;
	}

	// For now we only handle one connection at a time.
//	[portHandle acceptConnectionInBackgroundAndNotify];
	
	if( remoteFileHandle )
	{
		// start listening for data
		NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
		[nc addObserver:self
			   selector:@selector(handleData:)
				   name:nil
				 object:remoteFileHandle];
		[remoteFileHandle waitForDataInBackgroundAndNotify];
	}
}


/*
 CERT Begin Image width height <image byte count>
 <image data> (as 0-255 bytes)
 CERT End Image
 
 width, height and # bytes as text
 Each line above separated by a new line
 
 Try port 15555 for now
 */
- (void)handleData:(NSNotification *)notification
{
	// We need a pool here because if CERT is running as anything other than the frontmost application and is not receiving
	// any UI events (mouse, keyboard, etc), then the runloop's auto release pool is NOT being released and all of the memory
	// we use up here was not being released.
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSString *name = [notification name];
//	NSLog( @"Object: %d, \"%@\"; %@", [notification object], [notification name], [notification userInfo]?@"Has userInfo":@"" );
	if( [name isEqualToString:NSFileHandleDataAvailableNotification] )
	{
		NSData *data = [[notification object] availableData];
		if( 0 == [data length] )
		{
//			[self stop:self];
			[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:remoteFileHandle];
//			[remoteFileHandle closeFile];
//			[remoteFileHandle release];
//			remoteFileHandle = nil;
			NSLog( @"Null data." );
			
			// Start listening for new connections
			[portHandle acceptConnectionInBackgroundAndNotify];
			[pool release];
			return;
		}
		packets++;
#ifdef DEBUG
		if( 0 == (packets % 100) )
			NSLog( @"Packet count: %d", packets );
#endif
		[mdata appendData:data];
		[self parseData];
		// accumulate text and see if there's a complete image
	}
	else if( [name isEqualToString:NSFileHandleReadCompletionNotification] )
	{
		NSLog( @"Unhandled NSFileHandleReadCompletionNotification" );
#ifdef xxxDEBUG
		NSData *data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
		NSString *dataString = [NSString stringWithCString:[data bytes] length:[data length]];
		NSLog( @"%@", dataString );
#endif
	}
	[[notification object] waitForDataInBackgroundAndNotify];
	[pool release];
}
- (NSMutableDictionary *)processResults:(NSDictionary *)results usingController:(id<MPT_PluginControllerProtocol>)controller
{	
	return nil;
}

/*
 parsing incoming data
 States:
   Not in image
   In header
   In footer
   In image data
 
 CERT Begin Image width height <image byte count>
 <image data> (as 0-255 bytes)
 CERT End Image
 
 void *current;
 void *imageStart;
 void *imageEnd;
 
 */
static NSString *headerTag = @"CERT Begin Image"; // 16
static NSString *footerTag = @"CERT End Image"; // 14

- (void)parseData
{
	switch( state )
	{
		case notStarted:
			dataIndex = 0;
			imageIndex = 0;
			// deliberate fall-thru
		case inHeader:
		{
			char *current = [mdata mutableBytes];
			state = inHeader;
			// look for the header tag
			// in all of these loops, we also need to make sure we don't go past the end of the data
			while( inHeader == state )
			{
				int missed = 0;
				while( (current[dataIndex] != 'C') && (dataIndex < [mdata length]) )
				{
					++dataIndex;
					++missed;
				}
				if( missed > 0 )
					NSLog( @"Had to skip %d bytes to find new header.", missed );
				NSString *head = [NSString stringWithCString:(current+dataIndex) length:16];
				if( [head isEqualToString:headerTag] )
				{
#ifdef DEBUG
					NSLog( @"Found header" );
#endif
					int saveIndex = dataIndex;
					dataIndex += 17; // should be pointing to width
					int len = 0;
					char *tmp = current + dataIndex;
					while( (current[dataIndex] != '\n') && (dataIndex < [mdata length]) )
					{
						++len;
						++dataIndex;
					}
					if( (0 == len) || ('\n' != current[dataIndex]) )
					{
						// we didn't find a newline, probably because we are near the end of the data and haven't gotten the rest yet.
						dataIndex = saveIndex;
						return;
					}
					
					NSString *valString = [NSString stringWithCString:tmp length:len];
					NSScanner *scan = [NSScanner scannerWithString:valString];
					int width, height;
					if( [scan scanInt:&width] && [scan scanInt:&height] && [scan scanInt:&numBytes] )
					{
						adaptor = [[MPT_RimageAdaptor alloc] initWithWidth:width height:height];
						NSAssert( numBytes == (width * height), @"Number of Bytes does not match width and height" );
					}
					else
					{
						NSLog( @"Failed to find header data items." );
					}
					// get the width, height and byte count
					// reset current to point to the beginning of the image data
					++dataIndex; // skip the newline
					state = inBody;
					if( dataIndex < [mdata length] )
						[self parseData];
				}
			}
			break;
		}
		case inBody:
		{
			if( adaptor )
			{
				unsigned char *current = [mdata mutableBytes];
				float *array = [adaptor array];
				while( (dataIndex < [mdata length]) && (imageIndex < numBytes) )
				{
					float val = current[dataIndex];
					val /= 255.0;
					NSAssert( ( (val <= 1.0) && (val >= 0.0) ), @"Bad value in transferred image." );
					array[imageIndex++] = val;
					++dataIndex;
				}
//				if( imageIndex >= (numBytes - 1) )
				if( imageIndex >= numBytes )
				{
					state = inFooter;
					
					// do something with the rimage adaptor
					NSMutableDictionary *task = [NSMutableDictionary dictionary];
					[task setObject:adaptor forKey:@"CERT.rimage"];
					[task setObject:@"Remote video frame" forKey:@"CERT.path"];
					[task setObject:[NSNumber numberWithUnsignedInt:frame++] forKey:@"CERT.frame"];
					[task setObject:[NSDate date] forKey:@"CERT.date"];
					
#ifdef DEBUG
					if( 0 == (frame % 10) )
						NSLog( @"Sent off frame %d", frame );
#endif
					[scheduler scheduleInput:task];
					
					[adaptor release];
					adaptor = nil;
					imageIndex = 0;
					[self parseData];
				}
			}
			else
			{
#ifdef DEBUG
				NSLog( @"In body but no adaptor." );
#endif
//				state = inHeader; // reset so we at least have a hope of finding the next image.
			}
			break;
		}
		case inFooter:
		{
			char *current = [mdata mutableBytes];
			if( dataIndex >= [mdata length] )
			{
#ifdef DEBUG
				NSLog( @"Data index is off." );
#endif
				return;
			}
			if( '\n' == current[dataIndex] )
				++dataIndex;
			if( ([mdata length] - dataIndex) >= ([footerTag length] + 1) )
			{
				NSString *foot = [NSString stringWithCString:(current+dataIndex) length:[footerTag length]];
				if( [foot isEqualToString:footerTag] )
				{
#ifdef DEBUG
					NSLog( @"Found footer" );
#endif
					dataIndex += [footerTag length];
					if( '\n' == current[dataIndex] )
					{
						++dataIndex;
						state = inHeader;
					}
					// At this point I think we should delete all of the data up to this point and reset whatever variables we need to.
//					int dlen = [mdata length] - (dataIndex + 1);
//					if( dlen > 0 )
//					{
//						NSMutableData *tmpData = [[NSMutableData alloc] initWithBytes:([mdata bytes] + dataIndex) length:dlen];
//						[mdata release];
//						mdata = tmpData;
//					}
//					else
//					{
//						[mdata release];
//						mdata = [[NSMutableData alloc] init];
//					}
					if( dataIndex <= [mdata length] )
					{
						[mdata replaceBytesInRange:NSMakeRange(0,dataIndex) withBytes:NULL length:0];
					}
					else
					{
						[mdata replaceBytesInRange:NSMakeRange(0,[mdata length]) withBytes:NULL length:0];
					}
					dataIndex = 0;
				}
				else
				{
#ifdef DEBUG
					NSLog( @"Footer not found: %@", foot );
#endif
					state = inHeader; // reset so we at least have a hope of finding the next image.
				}
			}
			else
			{
				return; // hopefully it's just a matter of needing to wait for the next packet
			}
//			if( dataIndex < [mdata length] )
//				[self parseData];
			break;
		}
	}
}

- (void)setScheduler:(id)_sched
{
	if( _sched != scheduler )
	{
		[scheduler release];
		scheduler = [_sched retain];
	}
}

@end
