//
//  MPT_FeatureAveraging.m
//  MPT_Features
//
//  Created by Andrew Salamon on 8/12/08.
//  Copyright 2008 Machine Perception Laboratory, University of California San Diego.. All rights reserved.
//

#import "MPT_FeatureAveraging.h"

#import "MPT_FAsetup.h"

@interface MPT_FeatureAveraging(PrivateMethods)
- (NSArray *)setupsFrom:(NSArray *)features forFeature:(NSString *)fname;
- (void)stateDidChange;
- (void)setFeaturePlugins:(NSArray *)value;
- (void)setHasRequiredFeatures:(BOOL)value;
- (void)setRequiredFeatures:(NSSet *)value;
- (void)showSlotViewAtIndex:(int)row;
@end

@implementation MPT_FeatureAveraging
- (id)initWithBundle:(NSBundle *)_bundle
{
	if( self = [super initWithBundle:_bundle] )
	{
		finishingLoading = YES;
		setupPrefs = [[self getPrefForKey:@"setup"] retain];
		
		[self setSlots:[NSArray arrayWithObjects:@"Left Eye", @"Mouth", @"Nose", @"Right Eye", nil]];
		slotArrays = [[NSMutableArray alloc] initWithArray:[self slots]]; // stick placeholder objects in there. They will be replaced with the arrays.
					  
		NSBundle *framework = [NSBundle bundleForClass:[self class]];
		NSDictionary *context = [NSDictionary dictionaryWithObject:self forKey:@"NSOwner"];
		if( ![framework loadNibFile:@"FAPrefPane.nib" externalNameTable:context withZone:[self zone]] )
			NSLog( @"Unable to load pref pane for Feature Averaging plugin." );
		
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pluginChangedNotification:) name:@"pluginStateChangedNotification" object:nil];
	}
	
	return self;
}

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
		
	[slots release];
	[slotArrays release];
	[slotViews release];
	
	[leftEyeSetup release];
	[rightEyeSetup release];
	[mouthSetup release];
	[noseSetup release];
	
	[setupPrefs release];
	
	[featurePlugins release];
	
	[super dealloc];
}

- (void)awakeFromNib
{
	slotViews = [[NSArray alloc] initWithObjects:leftEyeView, mouthView, noseView, rightEyeView, nil];
	[self showSlotViewAtIndex:[slotTable selectedRow]];
}

- (id)copyWithZone:(NSZone *)zone
{
	return nil;
}

- (void)pluginsFinishedLoading:(NSArray *)plugins
{
	NSMutableArray *allFeatures = [NSMutableArray array];
	NSMutableArray *featPlugins = [NSMutableArray array];
	NSEnumerator *pluginEnum = [plugins objectEnumerator];
	id<MPT_PluginProtocol> plugin;
	while( plugin = [pluginEnum nextObject] )
	{
		if( [[plugin step] isEqualToString:MPT_PluginStep_Features] && (plugin != self) )
		{
			if( [(id<MPT_PluginProtocolFeatures>)plugin processor] == NULL )
			{
				MPT_FAsetup *setup = [[MPT_FAsetup alloc] initWithName:[plugin localizedName] iname:[plugin internalName] plugin:plugin];
				[allFeatures addObject:setup];
				[setup release];
				[featPlugins addObject:plugin];
			}
			else
			{
				// Ignore Feature processor plugins for now. We will probably need to handle them at some point.
				if( [(id)plugin respondsToSelector:@selector(outputFeatureTypes)] )
				{
					NSEnumerator *labelEnum = [[(id)plugin performSelector:@selector(outputFeatureTypes)] objectEnumerator];
					NSString *label;
					while( label = [labelEnum nextObject] )
					{
						MPT_FAsetup *setup = [[MPT_FAsetup alloc] initWithName:[(id)plugin performSelector:@selector(labelForFeatureType:) withObject:label] iname:label plugin:plugin];
						[allFeatures addObject:setup];
						[setup release];
					}
					[featPlugins addObject:plugin];
				}
			}
		}
	}
	
	[self setFeaturePlugins:featPlugins];

	finishingLoading = YES;
	[self setLeftEyeSetup:[self setupsFrom:allFeatures forFeature:@"left_eye"]];
	[self setRightEyeSetup:[self setupsFrom:allFeatures forFeature:@"right_eye"]];
	[self setMouthSetup:[self setupsFrom:allFeatures forFeature:@"mouth"]];
	[self setNoseSetup:[self setupsFrom:allFeatures forFeature:@"nose"]];
	finishingLoading = NO;
	[self stateDidChange];
}

- (NSArray *)setupsFrom:(NSArray *)features forFeature:(NSString *)fname
{
	NSArray *fPref = [setupPrefs objectForKey:fname];
	NSMutableArray *cpArray = [NSMutableArray array];
	NSEnumerator *setupEnum = [features objectEnumerator];
	MPT_FAsetup *setup;
	while( setup = [setupEnum nextObject] )
	{
		MPT_FAsetup *cp = [setup copy];
		[cp setDelegate:self];
		if( [fPref count] > 0 )
		{
			if( [fPref containsObject:[cp iname]] )
				[cp setIncluded:YES];
		}
		else
		{
			if( [[cp iname] isEqualToString:fname] )
				[cp setIncluded:YES];
		}
		[cpArray addObject:cp];
		[cp release];
	}
	return [[cpArray copy] autorelease];
}

- (NSString *)step
{
	return MPT_PluginStep_Features;
}

- (NSString *)localizedName
{
	return NSLocalizedStringFromTableInBundle(@"FeatureName", nil, [self bundle], @"");
}

- (NSString *)internalName
{
	return NSLocalizedStringFromTableInBundle(@"InternalName", nil, [self bundle], @"");
}

- (NSString *)featureDataFile
{
	return nil;
}
- (NSString *)gpriorFile
{
	return nil;
}

- (BOOL)handlesLoading
{
	return NO;
}

- (BOOL)loadFeatureData:(void *)fd andGPrior:(void *)prior
{
	return NO;
}

- (NSColor *)displayColor
{
	return [NSColor blueColor];
}

- (NSView *)prefPane
{
	return prefPane;
}

- (void)setEnabled:(BOOL)value
{
	BOOL oldValue = [self enabled];
	[super setEnabled:value];
	if( oldValue != value )
	{
		[self stateDidChange];
	}
}

- (NSArray *)featurePlugins {
    return [[featurePlugins retain] autorelease];
}
- (void)setFeaturePlugins:(NSArray *)value
{
    if (featurePlugins != value) {
        [featurePlugins release];
        featurePlugins = [value copy];
    }
}

- (id<MPT_PluginProtocol>)findFeatureWithIName:(NSString *)iname
{
	NSEnumerator *featEnum = [featurePlugins objectEnumerator];
	id<MPT_PluginProtocol> plugin;
	while( plugin = [featEnum nextObject] )
	{
		if( [[plugin internalName] isEqualToString:iname] )
			return plugin;
	}
	return nil;
}

- (void)calculateRequiredFeatures
{
	if( finishingLoading )
		return;
	
	NSMutableSet *requiredINames = [NSMutableSet set];
	NSMutableArray *allSetups = [NSMutableArray arrayWithArray:leftEyeSetup];
	[allSetups addObjectsFromArray:rightEyeSetup];
	[allSetups addObjectsFromArray:mouthSetup];
	[allSetups addObjectsFromArray:noseSetup];
	NSEnumerator *setupEnum = [allSetups objectEnumerator];
	MPT_FAsetup *setup;
	while( setup = [setupEnum nextObject] )
	{
		if( [setup included] )
			[requiredINames addObject:[[setup plugin] internalName]];
	}
	
	[self setRequiredFeatures:requiredINames];
	
	//NSLog( @"Required feature inames: %@", [requiredINames description] );
	
	// now walk through all features and see if all the required ones are enabled
	BOOL hasAll = YES;
	NSEnumerator *inameEnum = [requiredINames objectEnumerator];
	NSString *iname;
	while( hasAll && (iname = [inameEnum nextObject]) )
	{
		id<MPT_PluginProtocol> plugin = [self findFeatureWithIName:iname];
		if( ![plugin enabled] )
			hasAll = NO;
	}
	[self setHasRequiredFeatures:hasAll];
}

- (IBAction)enableRequiredPlugins:(id)sender
{
	NSMutableArray *pluginIDs = [NSMutableArray array];
	NSEnumerator *inameEnum = [[self requiredFeatures] objectEnumerator];
	NSString *iname;
	while( iname = [inameEnum nextObject] )
	{
		id<MPT_PluginProtocol> plugin = [self findFeatureWithIName:iname];
		[pluginIDs addObject:[plugin pluginID]];
		// [plugin setEnabled:YES];
	}
	[[self delegate] performSelector:@selector(enablePluginsWithIDs:) withObject:pluginIDs];

	[self setHasRequiredFeatures:YES]; // Not as safe as calling calculateRequiredFeatures, but faster.
}

- (void)pluginChangedNotification:(NSNotification *)notification
{
	id<MPT_PluginProtocol> plugin = [[notification userInfo] objectForKey:@"plugin"];
	if( (plugin != self) && [[plugin step] isEqualTo:MPT_PluginStep_Features] )
	{
		[self calculateRequiredFeatures];
	}
}

- (NSSet *)requiredFeatures {
    return [[requiredFeatures retain] autorelease];
}
- (void)setRequiredFeatures:(NSSet *)value {
    if (requiredFeatures != value) {
        [requiredFeatures release];
        requiredFeatures = [value copy];
    }
}

- (BOOL)hasRequiredFeatures {
    return hasRequiredFeatures;
}
- (void)setHasRequiredFeatures:(BOOL)value {
	hasRequiredFeatures = value;
}

/* Generates an array of feature names from the array of MPT_FAsetup objects and their included states.
 */
- (NSArray *)setupFromArray:(NSArray *)features
{
	NSMutableArray *setups = [NSMutableArray array];
	NSEnumerator *fEnum = [features objectEnumerator];
	MPT_FAsetup *setup;
	while( setup = [fEnum nextObject] )
	{
		if( [setup included] )
		{
			[setups addObject:[setup iname]];
		}
	}
	return setups;
}

- (NSDictionary *)generateSetup
{
	NSDictionary *res = [NSDictionary dictionaryWithObjectsAndKeys:[self setupFromArray:leftEyeSetup], @"left_eye", [self setupFromArray:rightEyeSetup], @"right_eye", [self setupFromArray:mouthSetup], @"mouth", [self setupFromArray:noseSetup], @"nose", nil];
	return res;
}

- (void)stateDidChange
{
	[[NSNotificationCenter defaultCenter] postNotificationName:@"MPT_FeatureAveraging" object:self];
	
	NSDictionary *setup = [self generateSetup];
	[self setPrefForKey:@"setup" to:setup];
	
	[self calculateRequiredFeatures];
}

- (NSArray *)slots {
    return [[slots retain] autorelease];
}
- (void)setSlots:(NSArray *)value {
    if (slots != value) {
        [slots release];
        slots = [value copy];
    }
}

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

- (void)setLeftEyeSetup:(NSArray *)value {
    if (leftEyeSetup != value) {
        [leftEyeSetup release];
        leftEyeSetup = [value copy];
		[slotArrays replaceObjectAtIndex:0 withObject:leftEyeSetup];
    }
}

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

- (void)setRightEyeSetup:(NSArray *)value {
    if (rightEyeSetup != value) {
        [rightEyeSetup release];
        rightEyeSetup = [value copy];
		[slotArrays replaceObjectAtIndex:3 withObject:leftEyeSetup];
    }
}

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

- (void)setMouthSetup:(NSArray *)value {
    if (mouthSetup != value) {
        [mouthSetup release];
        mouthSetup = [value copy];
		[slotArrays replaceObjectAtIndex:1 withObject:leftEyeSetup];
    }
}

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

- (void)setNoseSetup:(NSArray *)value {
    if (noseSetup != value) {
        [noseSetup release];
        noseSetup = [value copy];
		[slotArrays replaceObjectAtIndex:2 withObject:leftEyeSetup];
    }
}

- (void)showSlotViewAtIndex:(int)row
{
	[slotViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
	[[self prefPane] addSubview:[slotViews objectAtIndex:row]];
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
	if( [notification object] == slotTable )
	{
		[self showSlotViewAtIndex:[slotTable selectedRow]];
	}
}

- (void *)gprior
{
	return nil;
}
- (void *)featuredata
{
	return nil;
}
- (void *)processor
{
	return nil;
}

@end
