// 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 "OHForm.h"

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

#import "OHHTMLView.h"
#import "OHFormArchive.h"
#import "OHFormElementArchive.h"
#import "OHFormInputButton.h"
#import "OHFormText.h"
#import "OHFormValue.h"
#import "OHHTMLDocument.h"
#import "OHHTMLPageView.h"
#import "OWScriptEventHandlerHolder.h"

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

static NSString *endOfLineString  = @"\r\n";

@interface OHForm (Private)
- (void)actuallyReset;
- (void)actuallySubmit;
- (void)submitMultipart;
- (NSString *)newMIMEMultipartBoundary;
- (void)submitDataStream:(OWDataStream *)dataStream withBoundaryString:(NSString *)boundaryString;
- (void)submitURL;
- (void)submitDataString:(NSString *)dataString;
@end

@implementation OHForm

- init;
{
    if (![super init])
	return nil;

    address = nil;
    elements = [[NSMutableArray alloc] init];
    firstTextElement = nil;
    lastTextElement = nil;
    nonretainedHtmlDocument = nil;
    selectedRadioButtonsByName = [[NSMutableDictionary alloc] init];
    eventHandlers = [[OWScriptEventHandlerHolder alloc] init];

    return self;
}

- (void)dealloc;
{
    [eventHandlers release];
    [address release];
    [encodingType release];
    [formName release];
    [selectedRadioButtonsByName release];
    [elements release];
    [super dealloc];
}

// Declared methods

- (void)setAddress:(OWAddress *)anAddress;
{
    if (address == anAddress)
	return;
    [address release];
    address = [anAddress retain];
}

- (void)setEncodingType:(NSString *)anEncodingType;
{
    if (encodingType == anEncodingType)
	return;
    [encodingType release];
    encodingType = [anEncodingType retain];
}

- (void)setStringEncoding:(NSStringEncoding)newStringEncoding;
{
    stringEncoding = newStringEncoding;
}

- (void)setName:(NSString *)newName
{
    [formName autorelease];  /* formName should always be nil at this point */
    formName = [newName copy];
}

- (void)setHTMLDocument:(OHHTMLDocument *)newHTMLDocument;
{
    if (!nonretainedHtmlDocument && firstTextElement == lastTextElement && [firstTextElement respondsToSelector:@selector(setAutoSubmit)]) {
        [firstTextElement setAutoSubmit];
    }
    nonretainedHtmlDocument = newHTMLDocument;
}

- (OHHTMLDocument *)htmlDocument
{
    return nonretainedHtmlDocument;
}

- (void)addFormElement:(id <OHFormElement>)formElement;
{
    if ([formElement respondsToSelector:@selector(setAutoSubmit)]) {
        lastTextElement = formElement;
        if (!firstTextElement)
            firstTextElement = formElement;
    }
    if ([formElement respondsToSelector:@selector(setNextKeyViewCell:)]) {
	[lastKeyElement setNextKeyViewCell:formElement];
        lastKeyElement = formElement;
        if (!firstKeyElement)
            firstKeyElement = formElement;
        [lastKeyElement setNextKeyViewCell:firstKeyElement];
    }
    [elements addObject:formElement];
}

- (void)reset;
{
    OWScriptEvent *reset;

    reset = [[OWScriptEvent alloc] initWithType:OWSE_Reset];
    [reset setTarget:self action:@selector(actuallyReset)];
    [reset setInstigator:self];
    if (eventHandlers)
        [reset addResponder:eventHandlers];
    [reset deliverAsynchronously];
    [reset release];
}

- (void)submit
{
    OWScriptEvent *submit;

    submit = [[OWScriptEvent alloc] initWithType:OWSE_Submit];
    [submit setTarget:self action:@selector(actuallySubmit)];
    [submit setInstigator:self];
    if (eventHandlers)
        [submit addResponder:eventHandlers];
    [submit deliverAsynchronously];
    [submit release];
}

- (void)radioButtonSelected:(OHFormInputButton *)selectedButton;
{
    NSString *name;
    OHFormInputButton *lastSelectedButton;
    
    name = [selectedButton name];
    if (!name)
	return;
    lastSelectedButton = [selectedRadioButtonsByName objectForKey:name];
    if (lastSelectedButton == selectedButton)
	return;
    [lastSelectedButton clear];
    [selectedRadioButtonsByName setObject:selectedButton forKey:name];
}

- (IBAction)reset:(id)sender;
{
    [self reset];
}

- (IBAction)delayedSubmit:(id)sender;
{
    [self queueSelector:@selector(submit)];
}

- (OWAddress *)address
{
    return address;
}

- (NSString *)encodingType
{
    return encodingType;
}

- (NSString *)name
{
    return formName;
}

- (NSArray *)elements
{
    return [NSArray arrayWithArray:elements];
}

- (OHViewCell *)firstKeyElement;
{
    return firstKeyElement;
}

- (OHViewCell *)lastKeyElement;
{
    return lastKeyElement;
}

- (OWScriptEventHandlerHolder *)eventHandlers
{
    return eventHandlers;
}

@end

@implementation OHForm (Private)

- (void)actuallyReset;
{
    NSEnumerator *elementEnumerator;
    id <OHFormElement> element;

    elementEnumerator = [elements objectEnumerator];
    while ((element = [elementEnumerator nextObject]))
        [element reset];
}

- (void)actuallySubmit;
{
    ASSERT_MAIN_THREAD_OPS_OK(@"AppKit calls must be from main thread!");

    if ([encodingType isEqualToString:@"multipart/form-data"])
        [self submitMultipart];
    else
        [self submitURL];
}

- (void)submitMultipart;
{
    OWDataStream *dataStream;
    NSString *boundaryString;
    NSMutableString *valueHeaders;
    NSEnumerator *elementEnumerator;
    id <OHFormElement> element;

    elementEnumerator = [elements objectEnumerator];
    dataStream = [[OWDataStream alloc] init];
    boundaryString = [self newMIMEMultipartBoundary];

    // elements
    while ((element = [elementEnumerator nextObject])) {
        NSEnumerator *formValuesEnumerator;
        NSArray *formValues;
        OHFormValue *formValue;
        OWContentType *contentType;

        formValues = [element formValues];
        formValuesEnumerator = [formValues objectEnumerator];

        while ((formValue = [formValuesEnumerator nextObject])) {
            // boundary
            valueHeaders = [NSMutableString stringWithFormat:@"--%@%@", boundaryString, endOfLineString];

            // content-disposition
            [valueHeaders appendFormat:@"Content-Disposition: form-data; name=\"%@\"", [formValue name]];
            if ([formValue type] == OHFormValueFilename)
                [valueHeaders appendFormat:@"; filename=\"%@\"", [[formValue valueString] lastPathComponent]];
            [valueHeaders appendString:endOfLineString];

            // content-type
            if ((contentType = [formValue contentType]))
                [valueHeaders appendFormat:@"Content-Type: %@%@", [contentType contentTypeString], endOfLineString];

            // blank line to end headers
            [valueHeaders appendString:endOfLineString];

            // write our headers to the stream
            [dataStream writeString:valueHeaders];

            // write out the value
            if ([formValue type] == OHFormValueFilename)
                [dataStream writeData:[NSData dataWithContentsOfFile:[formValue valueString]]];
            else
                [dataStream writeString:[formValue valueString]];
                    
            [dataStream writeString:endOfLineString];
        }
    }

    // ending boundary
    [dataStream writeString:[NSMutableString stringWithFormat:@"--%@--%@", boundaryString, endOfLineString]];
    [dataStream dataEnd];

    [self submitDataStream:dataStream withBoundaryString:boundaryString];
}

- (NSString *)newMIMEMultipartBoundary;
{
    // we're seeding the random number generator in +initialize
    return [NSString stringWithFormat:@"#%09d#multipart#boundary#%09d#", OFRandomNext(), OFRandomNext()];
}

- (void)submitDataStream:(OWDataStream *)dataStream withBoundaryString:(NSString *)boundaryString;
{
    NSString *methodString;
    OWAddress *submitAddress;
    NSMutableDictionary *methodDictionary;

    // This only supports multipart POST
    methodString = [address methodString];
    if ([methodString isEqualToString:@"POST"]) {
	methodDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
	[methodDictionary setObject:@"multipart/form-data" forKey:@"Content-Type"];
	[methodDictionary setObject:boundaryString forKey:@"Boundary"];
	[methodDictionary setObject:[dataStream bufferedData] forKey:@"Content-Data"];
	submitAddress = [address addressWithMethodString:methodString methodDictionary:methodDictionary forceAlwaysUnique:YES];
	[methodDictionary release];
        [[nonretainedHtmlDocument htmlPageView] displayDocumentAtAddress:submitAddress];
    }
}

#define escapeFormString(str) [OWURL encodeURLString:(str) encoding:stringEncoding asQuery:YES leaveSlashes:NO leaveColons:NO]

- (void)submitURL;
{
    NSMutableArray *formValues;
    NSString *dataString;
    NSEnumerator *elementEnumerator;
    id <OHFormElement> element;

    elementEnumerator = [elements objectEnumerator];
    formValues = [NSMutableArray arrayWithCapacity:[elements count]];

    while ((element = [elementEnumerator nextObject]))
        [formValues addObjectsFromArray:[element formValues]];

    // Special case --- if there's only one thing being submitted and its name is ISINDEX, we drop the ISINDEX=. This is to be like Mosaic, since some depend on that particular feature(?).
    if ([formValues count] == 1 && [[[(OHFormValue *)[formValues objectAtIndex:0] name] lowercaseString] isEqualToString:@"isindex"] && ![[address methodString] isEqualToString:@"POST"]) {
        dataString = escapeFormString([[formValues objectAtIndex:0] valueString]);
    } else {
        NSMutableString *formString = nil;
        NSEnumerator *formValuesEnumerator;
        OHFormValue *formValue;

        formValuesEnumerator = [formValues objectEnumerator];
        while ((formValue = [formValuesEnumerator nextObject])) {
            NSString *name, *value;

            name = [formValue name];
            value = [formValue valueString];
            if (formString)
                [formString appendString:@"&"];
            else
                formString = [NSMutableString stringWithCapacity:[name length] + [value length] + 10];
            [formString appendFormat:@"%@=%@", escapeFormString(name), escapeFormString(value)];
        }
        dataString = formString;
    }
    [self submitDataString:dataString ? dataString : @""];
}

- (void)submitDataString:(NSString *)dataString;
{
    NSString *methodString;
    OWAddress *submitAddress;

    methodString = [address methodString];
    if ([methodString isEqualToString:@"GET"]) {
	submitAddress = [address addressWithGetQuery:dataString];
    } else if ([methodString isEqualToString:@"POST"]) {
	NSMutableDictionary *methodDictionary;

	methodDictionary = [[NSMutableDictionary alloc] initWithCapacity:2];
	[methodDictionary setObject:@"application/x-www-form-urlencoded" forKey:@"Content-Type"];
	[methodDictionary setObject:dataString forKey:@"Content-String"];
	submitAddress = [address addressWithMethodString:methodString methodDictionary:methodDictionary forceAlwaysUnique:YES];
	[methodDictionary release];
    } else {
	NSLog(@"Form: %@: Unknown submission method %@, trying GET instead", [address addressString], methodString);
	submitAddress = [address addressWithGetQuery:dataString];
    }
    [[nonretainedHtmlDocument htmlPageView] displayDocumentAtAddress:submitAddress];
}

// Debugging

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

    debugDictionary = [super debugDictionary];
    if (address)
        [debugDictionary setObject:address forKey:@"address"];
    if (encodingType)
        [debugDictionary setObject:encodingType forKey:@"encodingType"];
    if (elements)
        [debugDictionary setObject:elements forKey:@"elements"];
    if (firstTextElement)
        [debugDictionary setObject:firstTextElement forKey:@"firstTextElement"];
    if (lastTextElement)
        [debugDictionary setObject:lastTextElement forKey:@"lastTextElement"];
    if (formName)
        [debugDictionary setObject:formName forKey:@"name"];
    if (eventHandlers)
        [debugDictionary setObject:eventHandlers forKey:@"eventHandlers"];
    return debugDictionary;
}

@end
