// Copyright 1998-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 "OWScriptEvent.h"

#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OWF/OWF.h>
#import <OmniAppKit/OmniAppKit.h>

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Scripting.subproj/OWScriptEvent.m,v 1.8 2000/01/20 00:27:57 kc Exp $")

@interface OWScriptEvent (Temporary)
- (NSString *)logDescription;
@end

static BOOL OWScriptEventTracing = NO;

@implementation OWScriptEvent

+ (NSString *)nameForType:(OWScriptEventType)type
{
    switch (type) {
        case OWSE_Abort:	return @"Abort";
        case OWSE_Blur: 	return @"Blur";
        case OWSE_Click:	return @"Click";
        case OWSE_Change:	return @"Change";
        case OWSE_DblClick:	return @"DoubleClick";
        case OWSE_DragDrop:	return @"DragDrop";
        case OWSE_Error:	return @"Error";
        case OWSE_Focus:	return @"Focus";
        case OWSE_KeyDown:	return @"KeyDown";
        case OWSE_KeyPress:	return @"KeyPress";
        case OWSE_KeyUp:	return @"KeyUp";
        case OWSE_Load: 	return @"Load";
        case OWSE_MouseDown:	return @"MouseDown";
        case OWSE_MouseMove:	return @"MouseMove";
        case OWSE_MouseExited:	return @"MouseExited";
        case OWSE_MouseEntered:	return @"MouseEntered";
        case OWSE_MouseUp:	return @"MouseUp";
        case OWSE_Move: 	return @"Move";
        case OWSE_Reset:	return @"Reset";
        case OWSE_Resize:	return @"Resize";
        case OWSE_Select:	return @"Select";
        case OWSE_Submit:	return @"Submit";
        case OWSE_Timeout:	return @"Timeout";
        case OWSE_Unload:	return @"Unload";
        default:
            return [NSString stringWithFormat:@"(Event %d)", type];
    }
}

+ (void)sendBackgroundEvent:(OWScriptEventType)type instigator:anObject responder:(id <OWScriptResponder>)aResponder
{
    OWScriptEvent *hermes = [[self alloc] initWithType:type];
    [hermes setInstigator:anObject];
    [hermes addResponder:aResponder];
    [hermes deliverAsynchronously];
    [hermes release];
}

+ (void)sendBackgroundEvent:(OWScriptEventType)type fromEvent:(NSEvent *)uiEvent instigator:anObject responder:(id <OWScriptResponder>)aResponder;
{
    OWScriptEvent *hermes = [[self alloc] initWithType:type fromEvent:uiEvent];
    [hermes setInstigator:anObject];
    [hermes addResponder:aResponder];
    [hermes deliverAsynchronously];
    [hermes release];
}

- init
{
    OBASSERT(NO);
    return nil;
}

- initWithType:(OWScriptEventType)type
{
    return [self initWithType:type fromEvent:nil];
}

- initWithType:(OWScriptEventType)type fromEvent:(NSEvent *)anEvent
{
    if (!(self = [super init])) return nil;

    eventType = type;
    event = [anEvent retain];

    possibleResponders = [[NSMutableArray alloc] init];
    nextPossibleResponder = 0;

    actionInvocation = nil;
    actInMainThread = NO;

    if (OWScriptEventTracing)
        NSLog(@"%@ created", [self logDescription]);

    return self;
}

- (void)dealloc
{
    if (OWScriptEventTracing)
        NSLog(@"%@ deallocating", [self logDescription]);
    [event release];
    event = nil;
    [possibleResponders release];
    possibleResponders = nil;
    [actionInvocation release];
    [instigator release];
    [super dealloc];
}

/* Event setup */

- (void)setInstigator:myCreator
{
    [instigator autorelease];
    instigator = [myCreator retain];
}

- (void)setTarget:finalReceiver action:(SEL)finalAction
{
    [self setTarget:finalReceiver action:finalAction cancelAction:NULL];
}

- (void)setTarget:finalReceiver action:(SEL)finalAction cancelAction:(SEL)cancelAction
{
    OFInvocation *newAction;

    OBASSERT(finalReceiver); /* actually, it's OK if finalReceiver is nil, but it's kind of pointless */

    if (finalAction) {
        newAction = [[OFInvocation alloc] initForObject:finalReceiver
                                               selector:finalAction
                                             withObject:self];
        [self setFinalAction:newAction inMainThread:YES];
        [newAction release];
    }

    if (cancelAction) {
        newAction = [[OFInvocation alloc] initForObject:finalReceiver
                                               selector:cancelAction
                                             withObject:self];
        [self setCancelAction:newAction inMainThread:YES];
        [newAction release];
    }
}

- (void)setFinalAction:(OFInvocation *)anAction inMainThread:(BOOL)inMain
{
    [actionInvocation autorelease];
    actionInvocation = [anAction retain];
    if (OWScriptEventTracing)
        NSLog(@"%@ finalAction=%@", [self logDescription], [anAction shortDescription]);
    actInMainThread = inMain;
}

- (void)setCancelAction:(OFInvocation *)anAction inMainThread:(BOOL)inMain
{
    [cancelInvocation autorelease];
    cancelInvocation = [anAction retain];
    if (OWScriptEventTracing)
        NSLog(@"%@ cancellAction=%@", [self logDescription], [anAction shortDescription]);
    cancelInMainThread = inMain;
}

- (void)addResponder:(id <OWScriptResponder>)newResponder
{
    /* addResponder: should only be called before the event is delivered, at which point nextPossibleResponder will still be 0 */
    OBASSERT(nextPossibleResponder == 0);
    [possibleResponders insertObject:newResponder atIndex:0];
}

/* Event information */
- (OWScriptEventType)type
{
    return eventType;
}

- instigator
{
    return instigator;
}

#if 0
- (NSPoint)positionOnPage
{
    return pagePosition;
}
#endif

- (NSEvent *)nsEvent
{
    return event;
}

- (id <OWScriptResponder>)_getNextResponder:(BOOL)andPop
{
    id <OWScriptResponder> nextResponder;

    if (!possibleResponders ||
        [possibleResponders count] <= nextPossibleResponder)
        return nil;

    nextResponder = [possibleResponders objectAtIndex:nextPossibleResponder];
    if (andPop)
        nextPossibleResponder ++;

    OBASSERT([(id <NSObject>)nextResponder conformsToProtocol:@protocol(OWScriptResponder)]);

    return nextResponder;
}

- (void)deliverAsynchronously
{
    id <OWScriptResponder> nextResponder = [self _getNextResponder:NO];

    if ((nextResponder && [nextResponder handleHTMLEventsInMainThread]) || (!nextResponder && actInMainThread)) {
        if([NSThread inMainThread]) {
            [self deliverSynchronously];
        } else {
            [self mainThreadPerformSelector:@selector(deliverSynchronously)];
        }
    } else
        [[OWProcessor processorQueue] queueSelector:@selector(deliverSynchronously) forObject:self];
}

- (BOOL)deliverSynchronously
{
    BOOL shouldHappen, happenInMainThread;
    OFInvocation *makeHappen;

    if (OWScriptEventTracing)
        NSLog(@"%@ invoking handlers", [self logDescription]);

    shouldHappen = [self invokeNextHandler];

    if (OWScriptEventTracing)
        NSLog(@"%@ done invoking handlers", [self logDescription]);

    if (shouldHappen) {
        makeHappen = actionInvocation;
        happenInMainThread = actInMainThread;
    } else {
        makeHappen = cancelInvocation;
        happenInMainThread = cancelInMainThread;
    }

    if (makeHappen) {
        if (happenInMainThread && ![NSThread mainThreadOpsOK])
            [[OFMessageQueue mainQueue] addQueueEntry:makeHappen];
        else
            [makeHappen invoke];
    }

    /* We have to release our actions early, to break the retain cycle. This means that an OWScriptEvent can't be delivered more than once. */
    [actionInvocation release];
    actionInvocation = nil;
    [cancelInvocation release];
    cancelInvocation = nil;

    if (OWScriptEventTracing)
        NSLog(@"%@ done", [self logDescription]);

    return shouldHappen;
}

- (BOOL)invokeNextHandler
{
    id <OWScriptResponder> nextResponder = [self _getNextResponder:YES];
    BOOL retval;

    if (!nextResponder)
        return YES;
    
    if ([nextResponder handleHTMLEventsInMainThread] &&
        ![NSThread mainThreadOpsOK]) {
        [NSThread lockMainThread];
        NS_DURING {
            retval = [nextResponder handleHTMLEvent:self];
        } NS_HANDLER {
            [NSThread unlockMainThread];
            [localException raise];
        } NS_ENDHANDLER
        [NSThread unlockMainThread];
    } else {
        retval = [nextResponder handleHTMLEvent:self];
    }

    return retval;
}

- (NSString *)logDescription
{
    return [NSString stringWithFormat:@"<%@ 0x%x type=%@>",
        [[self class] description], (unsigned int)self,
        [[self class] nameForType:eventType]];
}

@end

