// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHFormSelect.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>

#import "OHForm.h"
#import "OHFormSelectMultiple.h"
#import "OHFormSelectPopup.h"
#import "OHFormSelectOption.h"
#import "OHFormValue.h"
#import "OWScriptEventHandlerHolder.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Forms.subproj/OHFormSelect.m,v 1.17 2000/01/20 00:27:52 kc Exp $")

@implementation OHFormSelect

+ (OHFormSelect *)selectWithForm:(OHForm *)aForm name:(NSString *)aName options:(NSArray *)someOptions visibleRows:(int)aRowCount allowMultiple:(BOOL)shouldAllowMultiple;
{
    if (shouldAllowMultiple || aRowCount > 1 || [someOptions count] > 100)
	return [[[OHFormSelectMultiple alloc] initWithForm:aForm name:aName options:someOptions visibleRows:aRowCount allowMultiple:shouldAllowMultiple] autorelease];

    return [[[OHFormSelectPopup alloc] initWithForm:aForm name:aName options:someOptions] autorelease];
}

- initWithForm:(OHForm *)aForm name:(NSString *)aName options:(NSArray *)someOptions;
{
    if (![super init])
        return nil;

    form = aForm;
    name = [aName retain];
    options = [someOptions retain];
    selectedOptions = [[NSMutableArray alloc] init];
    eventHandlers = [[OWScriptEventHandlerHolder alloc] init];
    elementStateLock = [[NSRecursiveLock alloc] init];

    return self;
}

- (void)dealloc;
{
    [name release];
    [options release];
    [selectedOptions release];
    [eventHandlers release];
    [elementStateLock release];
    [super dealloc];
}

#if 0  /* this is bad API --- text doesn't uniquely identify an option */
- (OHFormSelectOption *)optionWithText:(NSString *)someText;
{
    OHFormSelectOption *option;
    [elementStateLock lock];
    option = [[self l_optionWithText:someText] retain];
    [elementStateLock unlock];
    return [option autorelease];
}
#endif

- (NSArray *)options
{
    NSArray *retainedOptions;
    [elementStateLock lock];
    retainedOptions = [options retain];
    [elementStateLock unlock];
    return [retainedOptions autorelease];
}

- (NSArray *)selectedOptions
{
    NSArray *copiedOptions;
    [elementStateLock lock];
    if (![selectedOptions count])
        copiedOptions = nil;
    else
        copiedOptions = [NSArray arrayWithArray:selectedOptions];
    [elementStateLock unlock];
    return copiedOptions;
}

- (void)resetSelectedOptions:(NSArray *)newOptions
{
    [elementStateLock lock];
    [selectedOptions removeAllObjects];
    /* Should we check that all options in the new array are in our options array? */
    [selectedOptions addObjectsFromArray:newOptions];
    [elementStateLock unlock];
    [self mainThreadPerformSelectorOnce:@selector(updateUI)];
}

- (OHForm *)form
{
    return [[form retain] autorelease];
}

// OHViewCell subclass

- (void)setNextKeyViewCell:(OHViewCell *)nextKeyViewCell;
{
    [self _setNextKeyViewCell:nextKeyViewCell];
}

// OHFormElement protocol

- (void)reset
{
    [self resetFromFormValues:[self defaultFormValues]];
}

- (void)clear;
{
}

- (NSString *)name;
{
    return name;
}

- (NSString *)type;
{
    return @"Select-One";
}

- (NSArray *)formValues;
{
    int optionCount, optionIndex;
    NSMutableArray *formValues;

    if (!name)
	return nil;

    [elementStateLock lock];
    optionCount = [selectedOptions count];
    formValues = [[NSMutableArray alloc] initWithCapacity:optionCount];
    [formValues autorelease];
    for(optionIndex = 0; optionIndex < optionCount; optionIndex ++) {
        OHFormValue *aValue;
        OHFormSelectOption *anOption;
        id extraState;

        anOption = [selectedOptions objectAtIndex:optionIndex];
        extraState = [NSArray arrayWithObjects:[NSNumber numberWithInt:[options indexOfObjectIdenticalTo:anOption]], [NSNumber numberWithInt:[options count]], nil];
        aValue = [OHFormValue formValueWithName:name
                                    valueString:[anOption tag]
                                     extraState:extraState];
        [formValues addObject:aValue];
    }
    [elementStateLock unlock];
    return formValues;
}

- (NSArray *)defaultFormValues
{
    int optionIndex, optionCount;
    OHFormSelectOption *currentOption;
    BOOL allowMultiple;
    NSArray *theOptions, *extraState;
    NSMutableArray *valueArray = [[NSMutableArray alloc] init];

    [elementStateLock lock];
    theOptions = [options retain];
    [elementStateLock unlock];
    [valueArray autorelease];
    allowMultiple = [self allowsMultipleSelection];
    optionCount = [theOptions count];
    for (optionIndex = 0; optionIndex < optionCount; optionIndex++) {
        currentOption = [theOptions objectAtIndex:optionIndex];

        if ([currentOption initiallySelected]) {
            extraState = [NSArray arrayWithObjects:[NSNumber numberWithInt:optionIndex], [NSNumber numberWithInt:optionCount], nil];
            [valueArray addObject:[OHFormValue formValueWithName:name valueString:[currentOption tag] extraState:extraState]];
            if (!allowMultiple)
                break;
        }
    }

    if (!allowMultiple && ![valueArray count]) {
        /* non-multiple implies that one must be selected; ideally we'd have another method to indicate that, but currently this is always true */
        if ([theOptions count]) {
            currentOption = [theOptions objectAtIndex:0];
            [valueArray addObject:[OHFormValue formValueWithName:name valueString:[currentOption tag]]];
        }
    }
    [theOptions release];

    return valueArray;
}

- (void)resetFromFormValues:(NSArray *)values;
{
    unsigned int valueIndex, valueCount;
    unsigned int optionIndex, optionCount;
    NSMutableArray *newSelection;
    
    if (![self allowsMultipleSelection] && [values count] != 1)
        return;

    [elementStateLock lock];

    newSelection = [[NSMutableArray alloc] init];

    optionCount = [options count];
    valueCount = [values count];
    for (valueIndex = 0; valueIndex < valueCount; valueIndex ++) {
        OHFormValue *formValue;
        NSString *optionTag;
        OHFormSelectOption *selectedOption;
        NSArray *extraState;

        formValue = [values objectAtIndex:valueIndex];
        optionTag = [formValue valueString];
        extraState = [formValue extraState];

        /* If the extraState isn't valid, don't use it. */
        /* (We may get invalid extraState after e.g. a page reload. */
        if (extraState &&
            (![extraState isKindOfClass:[NSArray class]] ||
             !([extraState count] == 2) ||
             !([[extraState objectAtIndex:1] intValue] == optionCount)))
            extraState = nil;

        selectedOption = nil;
        
        /* If the extraState *is* valid, use it. */
        if (extraState) {
            selectedOption = [options objectAtIndex:[[extraState objectAtIndex:0] intValue]];
            if (![[selectedOption tag] isEqualToString:optionTag])
                selectedOption = nil; /* bogus extraState */
        }

        /* Fall back to linear search through possibly non-unique keys */
        if (!selectedOption) {
            for (optionIndex = 0; optionIndex < optionCount; optionIndex++) {
                OHFormSelectOption *currentOption;

                currentOption = [options objectAtIndex:optionIndex];
                if ([[currentOption tag] isEqualToString:optionTag]) {
                    selectedOption = currentOption;
                }
            }
        }

        if (selectedOption)
            [newSelection addObject:selectedOption];
    }
    
    // Default to selecting the first option (if any)
    if (![self allowsMultipleSelection] &&
        ![newSelection count] && optionCount > 0)
        [newSelection addObject:[options objectAtIndex:0]];

    [self resetSelectedOptions:newSelection];
    [elementStateLock unlock];
    [newSelection release];
}

// Script event handling

- (OWScriptEventHandlerHolder *)eventHandlers
{
    return eventHandlers;
}

// Methods private to OHFormSelect (and subclasses)

- (void)userDidChangeSelection
{
    /* TODO: Allow script to cancel the effect? */
    [OWScriptEvent sendBackgroundEvent:OWSE_Change
                            instigator:self
                             responder:[self eventHandlers]];
}

- (BOOL)allowsMultipleSelection
{
    return NO;
}

#if 0  /* this is bad API --- text doesn't uniquely identify an option */
- (OHFormSelectOption *)l_optionWithText:(NSString *)someText;
{
    unsigned int optionIndex, optionCount;
    OHFormSelectOption *option;
    NSString *optionText;

    optionCount = [options count];
    for (optionIndex = 0; optionIndex < optionCount; optionIndex++) {
        option = [options objectAtIndex:optionIndex];
        optionText = [option text];
        if (!someText && !optionText)
            return option;
        if (!someText || !optionText)
            continue;
        if ([someText isEqualToString:optionText])
            return option;
    }
    return nil;
}
#endif

// Debugging

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    if (name)
	[debugDictionary setObject:name forKey:@"name"];
    if (options)
	[debugDictionary setObject:options forKey:@"options"];
    if (selectedOptions)
        [debugDictionary setObject:selectedOptions forKey:@"selectedOptions"];
    if (eventHandlers)
        [debugDictionary setObject:eventHandlers forKey:@"eventHandlers"];

    return debugDictionary;
}

@end
