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

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

#ifndef sun
#import <mach/cthreads.h>
#endif

#import "OFMessageQueue.h"

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniFoundation/OpenStepExtensions.subproj/NSThread-OFExtensions.m,v 1.12 1999/03/30 21:25:46 kc Exp $")

@implementation NSThread (OFExtensions)

static NSThread *mainThread = nil;
static NSConditionLock *mainThreadInterlock = nil;
static unsigned int threadsWaiting;
static unsigned int recursionCount;
static NSThread *substituteMainThread = nil;

enum {
    THREADS_WAITING, NO_THREADS_WAITING
};

+ (void)didLoad;
{
    [self setMainThread];
}

+ (void)setMainThread;
{
    mainThread = [[NSThread currentThread] retain];

    mainThreadInterlock = [[NSConditionLock alloc] init];
    [mainThreadInterlock lock];
    threadsWaiting = 0;
    recursionCount = 0;
}

+ (NSThread *)mainThread;
{
    if (!mainThread) {
        NSLog(@"Warning: +[NSThread setMainThread] not called early enough!");
        [self setMainThread];
    }

    return mainThread;
}

+ (BOOL)inMainThread;
{
    if (!mainThread) {
        NSLog(@"Warning: +[NSThread setMainThread] not called early enough!");
        [self setMainThread];
    }
    return [self currentThread] == mainThread;
}

+ (BOOL)mainThreadOpsOK;
{
    return ([self inMainThread] || ([self currentThread] == substituteMainThread));
}
    
+ (void)lockMainThread;
{
    if ([self inMainThread])
        return;

    if ([self currentThread] == substituteMainThread) {
        recursionCount ++;
        return;
    }
    
    threadsWaiting++;
    [[OFMessageQueue mainQueue] queueSelectorOnce:@selector(yield) forObject:mainThread];
    [mainThreadInterlock lock];
    OBASSERT(substituteMainThread == nil);
    substituteMainThread = [self currentThread];
    recursionCount = 1;
}

+ (void)unlockMainThread;
{
    if ([self inMainThread])
        return;

    OBASSERT(substituteMainThread == [self currentThread]);
    
    if (--recursionCount)
        return;
    
    substituteMainThread = nil;

    if (--threadsWaiting > 0)
        [mainThreadInterlock unlockWithCondition:THREADS_WAITING];
    else
        [mainThreadInterlock unlockWithCondition:NO_THREADS_WAITING];
}

- (void)yield;
{
    if (self == mainThread) {
        if (threadsWaiting > 0) {
            [mainThreadInterlock unlockWithCondition:THREADS_WAITING];
            [mainThreadInterlock lockWhenCondition:NO_THREADS_WAITING];
        } else {
            cthread_yield();
        }
    } else {
#if !defined(NeXT_PDO) && !defined(sun)
        cthread_yield();
#endif
    }
}

- (void)setName:(NSString *)aName
{
// #ifdef DEBUG
#if !defined(NeXT_PDO) && !defined(sun) && OBOperatingSystemMajorVersion < 5
#warning -[NSThread setName:] should be disabled for production builds.
    char *buf;
    NSData *newNameData;
    
    // These gyrations are necessary because the cthread library does not copy the name; it merely remembers the pointer.

    buf = alloca([aName cStringLength] + 1);
    [aName getCString:buf];
    newNameData = [[NSData alloc] initWithBytes:buf length:1 + strlen(buf)];
    cthread_set_name(cthread_self(), [newNameData bytes]);
    // this next line also deallocates the old name buffer if any
    [[self threadDictionary] setObject:newNameData forKey:@"nameData"];
    [newNameData release];
#endif
    
// #endif /* DEBUG */
}

@end
