//
//  MPT_Scheduler.m
//  AUCoderDemo
//
//  Created by Andrew Salamon on 6/5/08.
//  Copyright 2008 Machine Perception Laboratory, University of California San Diego.. All rights reserved.
//

#import "MPT_Scheduler.h"

#import "MPT_CERT.h"
#import "MPT_Pair.h"

// For finding the number of available cpus
#include <mach/machine.h>
#include <mach/mach.h>
#include <mach/mach_host.h>
#include <mach/host_info.h>

static int numThreads = -1;

typedef enum {
	PROC_AVAIL = 1,
	PROC_NOAVAIL,
	PROC_STARTING,
	PROC_FULL
} PROC_CONDITION;

@interface MPT_Scheduler(PrivateMethods)
- (void)makeAvailable:(id)proc;
- (id)getNextAvailable;
- (void)runProcessor:(MPT_Pair *)pair;
@end

@implementation MPT_Scheduler
+ (unsigned int)numThreads
{
    return numThreads;
}

+ (void)setNumThreads:(unsigned int)value
{
    numThreads = value;
}

+ (unsigned int)availableCPUs
{
	host_basic_info_data_t hostInfo;
	mach_msg_type_number_t infoCount;
	
	infoCount = HOST_BASIC_INFO_COUNT;
	host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount);
	
	//		printf( "Max CPUs: %d\n", (unsigned int)hostInfo.max_cpus );
	//		printf( "Used CPUs: %d\n", (unsigned int)hostInfo.avail_cpus );
	return hostInfo.avail_cpus;
}

- (id)initWithDelegate:(id)_delegate
{
	if( self = [super init] )
	{
		stopping = NO;
		delegate = _delegate;
		
		available = [[NSMutableArray alloc] init];

		if( -1 == numThreads)
			numThreads = [[self class] availableCPUs];
		
		condLock = [[NSConditionLock alloc] initWithCondition:PROC_STARTING];
	}
	
	return self;
}

- (void)dealloc
{
	[processors release];
	processors = nil;
	[available release];
	available = nil;
	[condLock release];
	condLock = nil;
	[super dealloc];
}


- (id)delegate
{
    return delegate;
}
- (void)setDelegate:(id)value
{
	delegate = value;
}

- (void)createProcessors
{
	if( [processors count] != 0 )
		return;
	
	NSMutableArray *tmp = [NSMutableArray array];
	[condLock lock];
	for( int i = numThreads; i > 0; --i )
	{
		[delegate performSelectorOnMainThread:@selector(incrementWeightsProgressBy:) withObject:nil waitUntilDone:NO];
		MPT_CERT *cert = [[MPT_CERT alloc] init];
		if( stopping )
		{
			[condLock unlockWithCondition:PROC_STARTING];
			return;
		}
		[cert makeThreadsafe];
		[tmp addObject:cert];
		[available addObject:cert];
		[cert release];
	}
	processors = [tmp copy];
	
	[condLock unlockWithCondition:(([available count] > 0)?PROC_AVAIL:PROC_NOAVAIL)];
	
//	if( 1 == [processors count] )
//		oneProc = [processors lastObject];
}

- (BOOL)isEmpty
{
	BOOL scheduleIsEmpty = NO;
	[condLock lock];
	scheduleIsEmpty = [available count] == [processors count];
	[condLock unlockWithCondition:(([available count] > 0)?PROC_AVAIL:PROC_NOAVAIL)];
	return scheduleIsEmpty;
}

- (BOOL)isAvailable
{
	int cnt = 0;
	[condLock lock];
	cnt = [available count];
	[condLock unlockWithCondition:((cnt > 0)?PROC_AVAIL:PROC_NOAVAIL)];
	return cnt > 0;
}

- (void)makeAvailable:(id)proc
{
	BOOL scheduleIsEmpty = NO;
	[condLock lock];
	[available addObject:proc];
	scheduleIsEmpty = [available count] == [processors count];
	if( stopping )
	{
		if( scheduleIsEmpty )
			[condLock unlockWithCondition:PROC_FULL];
		else
			[condLock unlockWithCondition:PROC_AVAIL];
	}
	else
	{
		[condLock unlockWithCondition:PROC_AVAIL];
		if( scheduleIsEmpty )
			[delegate performSelectorOnMainThread:@selector(scheduleIsEmpty) withObject:nil waitUntilDone:NO];
	}
}

- (id)getNextAvailable
{
	[condLock lockWhenCondition:PROC_AVAIL];
	id proc = [available lastObject];
	[available removeLastObject];
	[condLock unlockWithCondition:(([available count] > 0)? PROC_AVAIL: PROC_NOAVAIL)];
	return proc;
}

- (BOOL)doesScheduleInput:(NSDictionary *)inputs
{
	[self scheduleInput:inputs];
	return YES;
}

- (void)scheduleInput:(NSDictionary *)inputs
{
	if( stopping ) return;
	
	if( oneProc )
	{
		[oneProc process:inputs];
		[delegate handleResults:[oneProc pluginResults]];
		[delegate performSelector:@selector(scheduleIsEmpty)];
	}
	else
	{
		id proc = [self getNextAvailable];
		MPT_Pair *pair = [MPT_Pair pairWith:proc and:inputs];
		
//		NSLog( @"Detaching cert: %d", proc );
		[NSThread detachNewThreadSelector:@selector(runProcessor:) toTarget:self withObject:pair];
	}
}

- (void)runProcessor:(MPT_Pair *)pair
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	id proc = [pair first];
	
	[NSThread setThreadPriority:1.0];
	
	[proc process:[pair second]];

//	NSLog( @"Finishing cert: %d", proc );
	if( !stopping )
		[delegate performSelectorOnMainThread:@selector(handleUnsortedResults:) withObject:[proc pluginResults] waitUntilDone:NO];
	
	[self makeAvailable:proc];
	
	[pool release];
}

/// Give all CERT processors a chance to stop otherwise we can get crashes when quitting.
- (void)cleanup
{
	stopping = YES;
	
	[condLock lock];
	if( [available count] != [processors count] )
	{
		NSLog( @"Not all CERT processors are finished." );
		[condLock unlockWithCondition:PROC_NOAVAIL];
		[condLock lockWhenCondition:PROC_FULL beforeDate:[NSDate dateWithTimeIntervalSinceNow:(2.5 * numThreads)]];
		NSLog( @"All CERT processors are now finished." );
	}
	
	[available removeAllObjects];
	available = nil;
	[processors release];
	processors = nil;
	[condLock unlockWithCondition:PROC_NOAVAIL]; // probably a waste, since we're quitting, but it's safer this way.
}
@end
