// Copyright 1997-1999 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 "OFQueueProcessor.h"

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

#import "NSThread-OFExtensions.h"
#import "OFInvocation.h"
#import "OFMessageQueue.h"

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniFoundation/Scheduling.subproj/OFQueueProcessor.m,v 1.8 1999/03/30 21:25:49 kc Exp $")

@interface OFQueueProcessor (Private)
- (BOOL)shouldProcessQueueEnd;
- (void)processQueueInThread;
@end

BOOL OFQueueProcessorDebug = NO;

@implementation OFQueueProcessor

static NSLock *detachThreadLock;
static OFQueueProcessor *detachingQueueProcessor;

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
	return;
    initialized = YES;

    detachThreadLock = [[NSLock alloc] init];
    detachingQueueProcessor = nil;

    // This will trigger +[NSPort initialize], which registers for the NSBecomingMultiThreaded notification and avoids a race condition between NSThread and NSPort.
    [NSPort class];
}

- initForQueue:(OFMessageQueue *)aQueue;
{
    if (![super init])
	return nil;

    messageQueue = [aQueue retain];
    currentInvocationLock = [[NSLock alloc] init];

    return self;
}

- (void)dealloc;
{
    [messageQueue release];
    [currentInvocationLock release];
    [super dealloc];
}

- (void)processQueueUntilEmpty:(BOOL)onlyUntilEmpty;
{
    OFInvocation *retainedInvocation;
    BOOL waitForMessages = !onlyUntilEmpty;
    BOOL setThreadNames = !onlyUntilEmpty;
    NSAutoreleasePool *autoreleasePool;

    autoreleasePool = [[NSAutoreleasePool alloc] init];
    
    if (detachingQueueProcessor == self) {
        detachingQueueProcessor = nil;
        [detachThreadLock unlock];
    }

    // This should be #ifndef PRODUCTION_BUILD, except we don't have a symbol for that right now.
    if (setThreadNames)
        [[NSThread currentThread] setName:@"(new queue)"];
    
    while ((retainedInvocation = [messageQueue nextRetainedInvocationWithBlock:waitForMessages])) {
        [currentInvocationLock lock];
        currentInvocation = retainedInvocation;
        [currentInvocationLock unlock];
        
        // This should be #ifndef PRODUCTION_BUILD, except we don't have a symbol for that right now.
        if (setThreadNames)
            [[NSThread currentThread] setName:[currentInvocation shortDescription]];

        if (OFQueueProcessorDebug) {
            NSLog(@"%@: invoking %@", [self shortDescription], [currentInvocation shortDescription]);
            if (!onlyUntilEmpty)
                [[NSThread currentThread] setName:[currentInvocation shortDescription]];
        }

	[currentInvocation invoke];

        if (OFQueueProcessorDebug) {
	    NSLog(@"%@: finished %@", [self shortDescription], [currentInvocation shortDescription]);
        }

        [currentInvocationLock lock];
        [currentInvocation release];
        currentInvocation = nil;
        [currentInvocationLock unlock];

        if (setThreadNames)
            [[NSThread currentThread] setName:@"(idle)"];
        
        if (waitForMessages) {
	    [autoreleasePool release];
	    autoreleasePool = [[NSAutoreleasePool alloc] init];
	} else if ([self shouldProcessQueueEnd])
	    break;
    }

    if (setThreadNames)
        [[NSThread currentThread] setName:@"(exiting)"];
    
    [autoreleasePool release];
}

- (void)processQueueUntilEmpty;
{
    [self processQueueUntilEmpty:YES];
}

- (void)processQueueForever;
{
    [self processQueueUntilEmpty:NO];
}

- (void)startProcessingQueueInNewThread;
{
    [detachThreadLock lock];
    [NSThread detachNewThreadSelector:@selector(processQueueInThread) toTarget:self withObject:nil];
}

- (OFInvocation *)retainedCurrentInvocation;
{
    OFInvocation *invocation;
    
    [currentInvocationLock lock];
    invocation = [currentInvocation retain];
    [currentInvocationLock unlock];

    return invocation;
}

// Debugging

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

    debugDictionary = [super debugDictionary];
    [debugDictionary setObject:messageQueue forKey:@"messageQueue"];
    return debugDictionary;
}

@end

@implementation OFQueueProcessor (Private)

- (BOOL)shouldProcessQueueEnd;
{
    return NO;
}

- (void)processQueueInThread;
{
    detachingQueueProcessor = self;
    for (;;) {
        NS_DURING {
            [self processQueueForever];
        } NS_HANDLER {
            NSLog(@"%@", [localException reason]);
        } NS_ENDHANDLER;
    }
}

@end
