// 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 <OmniFoundation/OFScheduler.h>

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

#import <OmniFoundation/NSDate-OFExtensions.h>
#import <OmniFoundation/NSMutableArray-OFExtensions.h>
#import <OmniFoundation/NSArray-OFExtensions.h>
#import <OmniFoundation/OFDedicatedThreadScheduler.h>
#import <OmniFoundation/OFInvocation.h>
#import <OmniFoundation/OFScheduledEvent.h>
#import <OmniFoundation/OFController.h>
#import <OmniFoundation/NSThread-OFExtensions.h>

#import "OFChildScheduler.h"
#import "OFRunLoopScheduler.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniFoundation/Scheduling.subproj/OFScheduler.m,v 1.20 2000/08/29 23:54:15 bungi Exp $")

@interface OFScheduler (Private)
+ (void)setDebug:(BOOL)newDebug;
- (void)invokeEvents:(NSArray *)events;
- (void)controllerWillTerminate:(NSNotification *)note;
@end

@implementation OFScheduler

+ (OFScheduler *)mainScheduler;
{
    // This used to return a run loop scheduler, but that seems to be buggy in DP4.
    return [OFDedicatedThreadScheduler dedicatedThreadScheduler];
}

+ (OFScheduler *)dedicatedThreadScheduler;
{
    return [OFDedicatedThreadScheduler dedicatedThreadScheduler];
}

// Init and dealloc

- init;
{
    // +[NSNotificationCenter defaultCenter] might be different in different threads.
    // Want to make sure that all the schedulers invoke fireOnTermination events.
    OBPRECONDITION([NSThread inMainThread]);
    
    if (![super init])
	return nil;

    scheduleQueue = [[NSMutableArray alloc] init];
    scheduleLock = [[NSRecursiveLock alloc] init];

    [[OFController sharedController] addObserver: self];

    return self;
}

- (void)dealloc;
{
    // +[NSNotificationCenter defaultCenter] might be different in different threads.
    // Want to make sure that all the schedulers invoke fireOnTermination events.
    OBPRECONDITION([NSThread inMainThread]);

    [scheduleQueue release];
    [scheduleLock release];
    [[NSNotificationCenter defaultCenter] removeObserver: self];
    [super dealloc];
}


// Public API

- (void)scheduleEvent: (OFScheduledEvent *) event;
{
    // If we've already recieved the termination notification, invoke any on-termination
    // events immediately.
    if (terminationSignaled && [event fireOnTermination]) {
        [self invokeEvents: [NSArray arrayWithObject: event]];
        return;
    }
    
    [scheduleLock lock];
    [scheduleQueue insertObject:event inArraySortedUsingSelector:@selector(compare:)];
    if ([scheduleQueue objectAtIndex:0] == event) {
        [self scheduleEvents];
    }
    [scheduleLock unlock];
}

/*" Removes the specified event from the receiver's schedule, if present.  If the event was present (and thus was removed), returns YES.  Otherwise (if, for example, the event has fired already), NO is returned. "*/
- (BOOL)abortEvent:(OFScheduledEvent *)event;
{
    BOOL eventWasFirstInQueue;
    BOOL wasFound = NO;
    
    if (event == nil)
        return wasFound;
        
    [scheduleLock lock];
    eventWasFirstInQueue = [scheduleQueue count] != 0 && [scheduleQueue objectAtIndex:0] == event;
    if (eventWasFirstInQueue) {
        wasFound = YES;
        [scheduleQueue removeObjectAtIndex:0];
	[self scheduleEvents];
    } else {
        unsigned index;
        
        index = [scheduleQueue indexOfObject:event inArraySortedUsingSelector:@selector(compare:)];
        if (index != NSNotFound) {
            wasFound = YES;
            [scheduleQueue removeObjectAtIndex: index];
        }
    }
    [scheduleLock unlock];
    
    return wasFound;
}

- (void)abortSchedule;
{
    [scheduleLock lock];
    [self cancelScheduledEvents];
    [scheduleQueue removeAllObjects];
    [scheduleLock unlock];
}

- (OFScheduler *)subscheduler;
{
    return [[[OFChildScheduler alloc] initWithParentScheduler:self] autorelease];
}

- (OFScheduler *)subScheduler;
{
    // This method is deprecated, which is why it isn't declared in the header.  It's here for backwards compatibility until we get all our code calling -subscheduler instead.
    return [self subscheduler];
}

- (NSDate *)dateOfFirstEvent;
{
    NSDate *dateOfFirstEvent;

    [scheduleLock lock];
    if ([scheduleQueue count] != 0) {
        OFScheduledEvent *firstEvent;

        firstEvent = [scheduleQueue objectAtIndex:0];
        dateOfFirstEvent = [[firstEvent date] retain];
    } else {
        dateOfFirstEvent = nil;
    }
    [scheduleLock unlock];
    return [dateOfFirstEvent autorelease];
}

// OBObject subclass

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

    debugDictionary = [super debugDictionary];
    if (scheduleQueue)
        [debugDictionary setObject:scheduleQueue forKey:@"scheduleQueue"];
    if (scheduleLock)
        [debugDictionary setObject:scheduleLock forKey:@"scheduleLock"];
    return debugDictionary;
}

@end

@implementation OFScheduler (OFConvenienceMethods)

- (OFScheduledEvent *)scheduleInvocation:(OFInvocation *)anInvocation atDate:(NSDate *)date;
{
    OFScheduledEvent *event;

    event = [[[OFScheduledEvent alloc] initWithInvocation:anInvocation atDate:date] autorelease];
    [self scheduleEvent: event];
    return event;
}

- (OFScheduledEvent *)scheduleInvocation:(OFInvocation *)anInvocation afterTime:(NSTimeInterval)time;
{
    return [self scheduleInvocation:anInvocation atDate:[NSDate dateWithTimeIntervalSinceNow:time]];
}

- (OFScheduledEvent *)scheduleSelector:(SEL)selector onObject:anObject withObject:anArgument atDate:(NSDate *)date;
{
    OFInvocation *invocation;
    OFScheduledEvent *event;

    invocation = [[OFInvocation alloc] initForObject:anObject selector:selector withObject:anArgument];
    event = [self scheduleInvocation:invocation atDate:date];
    [invocation release];
    return event;
}

- (OFScheduledEvent *)scheduleSelector:(SEL)selector onObject:anObject withObject:anArgument afterTime:(NSTimeInterval)time;
{
    return [self scheduleSelector:selector onObject:anObject withObject:anArgument atDate:[NSDate dateWithTimeIntervalSinceNow:time]];
}

@end

@implementation OFScheduler (SubclassesOnly)

- (void)invokeScheduledEvents;
{
    NSDate *currentDate;
    unsigned int remainingEventCount;
    NSMutableArray *eventsToInvokeByCurrentDate;

    eventsToInvokeByCurrentDate = [[NSMutableArray alloc] init];
    [scheduleLock lock];
    remainingEventCount = [scheduleQueue count];
    currentDate = [NSDate date];
    while (remainingEventCount--) {
        OFScheduledEvent *event;

        event = [scheduleQueue objectAtIndex:0];
        if ([[event date] isAfterDate:currentDate]) {
            [self scheduleEvents];
            remainingEventCount = 0;
        } else {
            [eventsToInvokeByCurrentDate addObject:event];
            [scheduleQueue removeObjectAtIndex:0];
        }
    }
    [scheduleLock unlock];
    [self invokeEvents:eventsToInvokeByCurrentDate];
    [eventsToInvokeByCurrentDate release];
}

- (void)scheduleEvents;
{
    // Subclasses must override this method
    OBRequestConcreteImplementation(self, _cmd);
}

- (void)cancelScheduledEvents;
{
    // Subclasses must override this method
    OBRequestConcreteImplementation(self, _cmd);
}

@end

@implementation OFScheduler (Private)

+ (void)setDebug:(BOOL)newDebug;
{
    OFSchedulerDebug = newDebug;
}

- (void)invokeEvents:(NSArray *)events;
{
    unsigned int eventIndex, eventCount;

    eventCount = [events count];
    for (eventIndex = 0; eventIndex < eventCount; eventIndex++) {
        OFScheduledEvent *event;

        event = [events objectAtIndex:eventIndex];
        NS_DURING {
            OMNI_POOL_START {
                if (OFSchedulerDebug)
                    NSLog(@"%@: invoking %@", [self shortDescription], [event shortDescription]);
                [event invoke];
            } OMNI_POOL_END;
        } NS_HANDLER {
            NSLog(@"%@: exception raised in %@: %@", [self shortDescription], [event shortDescription], [localException reason]);
        } NS_ENDHANDLER;
    }
}

- (void)controllerWillTerminate:(NSNotification *)note;
{
    unsigned int remainingEventCount;
    NSMutableArray *terminationEvents;
        
    terminationSignaled = YES;

    if (OFSchedulerDebug)
        NSLog(@"%@: Processing termination events", [self shortDescription]);

    terminationEvents = [[NSMutableArray alloc] init];
    [scheduleLock lock];
    remainingEventCount = [scheduleQueue count];
    while (remainingEventCount--) {
        OFScheduledEvent *event;

        event = [scheduleQueue objectAtIndex:remainingEventCount];
        if ([event fireOnTermination]) {
            [terminationEvents addObject:event];
            [scheduleQueue removeObjectAtIndex:remainingEventCount];
        }
    }
    [scheduleLock unlock];
    
    if (OFSchedulerDebug)
        NSLog(@"Invoking termination events: %@", terminationEvents);
    [self invokeEvents:terminationEvents];
    [terminationEvents release];
}

@end

BOOL OFSchedulerDebug = NO;
