/*
        Copyright (c) 1996, NeXT Software, Inc.
        All rights reserved.

        You may freely copy, distribute and reuse the code in this example.
        NeXT disclaims any warranty of any kind, expressed or implied,
        as to its fitness for any particular use.
*/
#import <EOExtensions/InsertionPopUpAssociation.h>
#import <EOExtensions/NSObjectAdditions.h>

static NSString *EOEnabledAspect 	= @"enabled";
static NSString *EOTitlesAspect 	= @"titles";
static NSString *EOSelectedTitleAspect 	= @"selectedTitle";
static NSString *EOSelectedTagAspect 	= @"selectedTag";
static NSString *EOSelectedObjectAspect = @"selectedObject";

#define POPUPASSOC_CURRENTVERSION 2

@implementation InsertionPopUpAssociation
+ (NSString *)displayName {
    return @"EOPopupAssoc";
}

+ (BOOL)isUsableWithObject:(id)object
{
    return [object isKindOfClass:[NSPopUpButton class]];
}

+ (NSArray *)aspects
{
    static NSArray *array = nil;
    if (!array)
        array = [[NSArray alloc] initWithObjects:EOTitlesAspect, EOSelectedTitleAspect,EOSelectedTagAspect, EOSelectedObjectAspect, EOEnabledAspect, nil];
    return array;
}

+ (NSArray *)aspectSignatures
{
    static NSArray *array = nil;
    if (!array)
        array = [[NSArray alloc] initWithObjects:@"A", @"A", @"A", @"1", @"A", nil];
    return array;
}

+ (NSString *)primaryAspect {
    return EOSelectedTitleAspect;
}

+ (NSArray *)objectKeysTaken {
    static NSArray *array = nil;
    if (!array)
        array = [[NSArray alloc] initWithObjects:@"target", nil];
    return array;
}

+ (NSArray *)associationClassesSuperseded
{
    return [NSArray arrayWithObject:[EOControlAssociation class]];
}

- (void)establishConnection
{
    id object = [self object];
    [super establishConnection];

    // Hack cast to get rid of warning about multiple declarations. 
    [(NSControl *)object setTarget:self];
    [object setAction:@selector(controlActed:)];
}

+ (void)initialize
{
    if (self == [InsertionPopUpAssociation class])
        [EOPopUpAssociation setVersion:POPUPASSOC_CURRENTVERSION];
}

- initWithObject:object
{
    [super initWithObject:object];
    _tagValueForOther = -1;

    return self;
}

- (void)setTagValueForOther:(int)newValue {
    _tagValueForOther = newValue;
}
- (int)tagValueForOther {
    return _tagValueForOther;
}

- (void)setAutomaticallyInsertsObject:(BOOL)yn {
    _autoInsertsObject = yn;
}
- (BOOL)automaticallyInsertsObject {
    return _autoInsertsObject;
}

- (NSArray *)titleArray {
    EODisplayGroup *dest = [self displayGroupForAspect:EOTitlesAspect];
    return [dest displayedObjects];
}

- (NSArray *)valueArray {
    EODisplayGroup *dest = [self displayGroupForAspect:EOTitlesAspect];
    if (dest)
        return [dest displayedObjects];
    else
        return [self titleArray];
}

- (int)indexForTag:(int)tag
// helper method, NSPopUpButton does not support tags
{
    NSPopUpButton *popup = [self object];
    NSArray   *cells = [popup itemArray];
    unsigned  i = [cells count];

    while (i--) {
        id anObject = [cells objectAtIndex:i];
        if ([anObject tag] == tag)
            return i;
    }
    return -1;
}

- (BOOL)autoPrependingOtherItem {
    return [self displayGroupForAspect:EOTitlesAspect] && _autoInsertsObject;
}

- (BOOL)indexForOtherItem {
    int index = [self indexForTag:_tagValueForOther];
    if ((index == -1) || [self autoPrependingOtherItem])
        index = 0;
    return index;
}

- (int)objectIndexForPopupIndex:(int)index {
    if ([self autoPrependingOtherItem])
        return index-1;
    return index;
}

- (void)controlActed:sender
{
    // get currently selected item from itemlist and push it
    // to our value
    NSPopUpButton *popup = [self object];
    int index = [popup indexOfSelectedItem];
    EODisplayGroup *selectedObjectDisplayGroup, *selectedTitleDisplayGroup, *selectedTagDisplayGroup, *activeDisplayGroup;

    if (index == -1)
        [NSException raise:NSInternalInconsistencyException format:@"popup sent action message but has no row selected!"];

    selectedTagDisplayGroup = [self displayGroupForAspect:EOSelectedTagAspect];
    selectedObjectDisplayGroup = [self displayGroupForAspect:EOSelectedObjectAspect];
    selectedTitleDisplayGroup = [self displayGroupForAspect:EOSelectedTitleAspect];	

    activeDisplayGroup = (selectedTitleDisplayGroup) ? selectedTitleDisplayGroup :
        ((selectedObjectDisplayGroup) ? selectedObjectDisplayGroup : selectedTagDisplayGroup);
    
    // If we selected the first item, delete the object from the controller
    if (index == [self indexForOtherItem]) {
        [activeDisplayGroup delete:nil];
        return;
    }
    
    // If we selected and item an there's no object, insert one
    // Set the related EO into the relationship property of the EO at the
    // selected object aspect
    if ([activeDisplayGroup selectedObject]==nil)
        [activeDisplayGroup insert:nil];
    
    if (selectedObjectDisplayGroup) {
        id valueEO, sourceEO;
        NSArray * valueArray = [self valueArray];

        index = [self objectIndexForPopupIndex:index];
        if (index >= [valueArray count]) {
            if (_addedTempItem && (index == [valueArray count]))
                return;  // just reselected the current value

            [NSException raise:NSInternalInconsistencyException format:@"Popup index: %d past end of value array of length: %d", index, [valueArray count]];
        }

        valueEO = [valueArray objectAtIndex:index];

        sourceEO = [selectedObjectDisplayGroup selectedObject];
        [sourceEO addObject:valueEO toBothSidesOfRelationshipWithKey:[self displayGroupKeyForAspect:EOSelectedObjectAspect]];
    }

    // set the title into the selectedTitle aspect
    if (selectedTitleDisplayGroup) {
        [self setValue:[popup stringValue] forAspect:EOSelectedTitleAspect];
    }
    
    // set the tag into the selectedTag aspect
    if (selectedTagDisplayGroup) {
        [self setValue:[NSNumber numberWithInt:[[popup selectedItem] tag]] forAspect:EOSelectedTagAspect];
    }
}

- (void)subjectChanged
{
    // We have to update our itemlist, and them select and item in
    // it corresponding to our value
    id 		   	eoValue;
    NSPopUpButton  	*popup = [self object];
    EODisplayGroup 	*titlesDisplayGroup = [self displayGroupForAspect:EOTitlesAspect];
    unsigned 	      	valueIndex;
    EODisplayGroup 	*selectedObjectDisplayGroup, *selectedTitleDisplayGroup, *selectedTagDisplayGroup;
    NSArray 	  	*titleEOs;
    NSString       	*titleKey;
    BOOL 		enabled = NO;
    int index;
    
    if (_addedTempItem) {
        [popup removeItemAtIndex:[popup numberOfItems]-1];
        _addedTempItem = NO;
    }

    if (titlesDisplayGroup) {
        titleEOs = [titlesDisplayGroup displayedObjects];
        titleKey = [self displayGroupKeyForAspect:EOTitlesAspect];

        if (([popup numberOfItems] != [titleEOs count]) || [titlesDisplayGroup contentsChanged])
        {
            NSArray *titles = [titleEOs arrayContainingValuesForKey:titleKey];
            if (_autoInsertsObject) {
                // prepend "None" item
                titles = [[NSArray arrayWithObject:@"None"] arrayByAddingObjectsFromArray:titles];
            }
            [popup removeAllItems];
            [popup addItemsWithTitles:titles];
        }
    }

    // We could either be trying to match on EO, or title depending on which aspect
    // was wired up
    if (titlesDisplayGroup && (selectedObjectDisplayGroup = [self displayGroupForAspect:EOSelectedObjectAspect])) {
        // select the item in the list based on the current value
        eoValue = [self valueForAspect:EOSelectedObjectAspect];

        if (eoValue) {
            valueIndex = [titleEOs indexOfObjectIdenticalTo:eoValue];

            if (valueIndex == NSNotFound) {
                // add this item temporarily
                id eoValueTitle = [eoValue valueForKeyPath:titleKey];
                if (!eoValueTitle) eoValueTitle = @"";
                [popup setTitle:eoValueTitle];

                if (![eoValueTitle isEqual:@""])
                    _addedTempItem = YES;
            } else {
                // select the item
                [popup selectItemAtIndex:valueIndex];
            }
        } else {
            [popup selectItemAtIndex:[self indexForOtherItem]];
        }
        enabled = (eoValue != nil) || _autoInsertsObject;
    } else if ((selectedTitleDisplayGroup = [self displayGroupForAspect:EOSelectedTitleAspect])) {
        eoValue = [self valueForAspect:EOSelectedTitleAspect];
        if (eoValue || !_autoInsertsObject) {
            enabled = (eoValue != nil);
            if (!eoValue) eoValue = @"";

            if (([popup indexOfItemWithTitle:eoValue] == -1) && ![eoValue isEqual:@""])
                _addedTempItem = YES;
            [popup setTitle:eoValue]; // setTitle automatically adds item if title is new
        } else {
            [popup selectItemAtIndex:[self indexForOtherItem]];
            enabled = YES;
        }
    }
    else if ((selectedTagDisplayGroup = [self displayGroupForAspect:EOSelectedTagAspect])) {
        eoValue = [self valueForAspect:EOSelectedTagAspect];
        if (eoValue || !_autoInsertsObject) {
            enabled = (eoValue != nil);
            index = [self indexForTag:[eoValue intValue]];
            if (index == -1) index = [self indexForOtherItem];
            [popup selectItemAtIndex:index];
        } else {
            [popup selectItemAtIndex:[self indexForOtherItem]];
            enabled = YES;
        }
    }
    
    if (enabled && [self displayGroupForAspect:EOEnabledAspect])
        enabled = [[self valueForAspect:EOEnabledAspect] intValue];
    if (enabled != [popup isEnabled])
        [popup setEnabled:enabled];
}

- (void)encodeWithCoder:(NSCoder *)coder
{
    [super encodeWithCoder:coder];

    [coder encodeValuesOfObjCTypes:"ii", &_tagValueForOther, &_autoInsertsObject];
}

- (id)initWithCoder:(NSCoder *)coder
{
    int version = [coder versionForClassName:@"EOInsertionPopUpAssociation"];

    self = [super initWithCoder:coder];

    if (version == POPUPASSOC_CURRENTVERSION) {
        [coder decodeValuesOfObjCTypes:"ii", &_tagValueForOther, &_autoInsertsObject];
    }

    return self;
}

@end


