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

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

// mutex_alloc and mutex_free are #defines that use calloc() and free()
#import <OmniBase/mallocmap.h>

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniFoundation/DataStructures.subproj/OFQueue.m,v 1.8 1999/03/31 20:38:08 kc Exp $")

DEFINE_NSSTRING(OFQueueIsClosed);

/*
I don't see how a NSConditionLock can be used to implement an overlapping state structure like this as efficiently as mutex/condition.  There are three states, empty, full and partially full.  Enqueue should block only on full, dequeue should only block on empty.  If you try to represent the states as not-empty and not-full, you have no way to represent the partially-full state.
*/

@implementation OFQueue

- initWithCount:(unsigned int)maxCount;
{
    closed = NO;
    max = maxCount;
    count = 0;
    head = 0;
    tail = 0;
    objects = (id *)NSZoneMalloc([self zone], sizeof(id) * max);

    mutex_init(&mutex);
    condition_init(&condition);

    return self;
}

- (void)dealloc;
{
    condition_clear(&condition);
    mutex_clear(&mutex);
    NSZoneFree([self zone], objects);
    [super dealloc];
}

- (unsigned int)maxCount;
{
    return max;
}

- (unsigned int)count;
{
    unsigned int ret;

    mutex_lock(&mutex);
    ret = count;
    mutex_unlock(&mutex);

    return ret;
}

- (void)close;
{
    mutex_lock(&mutex);
    closed = YES;
    condition_signal(&condition); /* in case anyone is trying to dequeue on a empty queue */
    mutex_unlock(&mutex);
}

- (BOOL)isClosed;
{
    BOOL ret;

    mutex_lock(&mutex);
    ret = closed;
    mutex_unlock(&mutex);

    return ret;
}

- (id)dequeueShouldWait:(BOOL)wait;
{
    id ret;

    mutex_lock(&mutex);

    while (!count && wait && !closed) {
        condition_wait(&condition, &mutex);
    }

    if (count) {
        ret = [objects[head] autorelease]; /* might want an exception handler here */
        count--;
        head++;
        if (head == max)
            head = 0;
        condition_signal(&condition);
    } else {
        ret = nil;
        /* don't signal, nothing happened */
    }

    mutex_unlock(&mutex);

    return ret;
}

- (BOOL)enqueue:(id)anObject shouldWait:(BOOL)wait;
{
    BOOL ret;

    mutex_lock(&mutex);

    if (closed) {
        mutex_unlock(&mutex);
        [NSException raise:OFQueueIsClosed format:@"Attempt to enqueue on a closed queue"];
    }

    while (count == max && wait) {
        condition_wait(&condition, &mutex);
    }

    if (count == max) {
        ret = NO;
        /* don't signal, nothing happened */
    } else {
        ret = YES;

        objects[tail] = [anObject retain]; /* might want an exception handler here */
        count++;
        tail++;
        if (tail == max)
            tail = 0;

        condition_signal(&condition); /* awake any readers */
    }

    mutex_unlock(&mutex);

    return ret;
}

@end
