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

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

#import "NSMutableArray-OFExtensions.h"
#import "NSThread-OFExtensions.h"
#import "OFObject-Queue.h"
#import "OFInvocation.h"
#import "OFScheduledEvent.h"

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

@interface OFSubscheduler : OFScheduler
{
    OFScheduler *parent;
    OFScheduledEvent *nonretainedParentAlarmEvent;
}

- _initWithParent:(OFScheduler *)aParent;

@end


@interface OFScheduler (Private)
- (void)_resetAlarmToFirstEntryInMainThread;
- (void)_resetAlarmToFirstEntry;
- (void)_alarm;
- (void)_invalidateAlarm;
@end

@implementation OFScheduler

static OFScheduler *mainScheduler;
static BOOL OFSchedulerDebug = NO;

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

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

    mainScheduler = [[self alloc] init];
}

+ (OFScheduler *)mainScheduler;
{
    return mainScheduler;
}


// Init and dealloc

- init;
{
    if (![super init])
	return nil;

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

    return self;
}

- (void)dealloc;
{
    if ([scheduleQueue count])
	[self _invalidateAlarm];
    [scheduleQueue release];
    [scheduleLock release];
    [super dealloc];
}


// Public API

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

    event = [[[OFScheduledEvent alloc] initWithInvocation:anInvocation atDate:date] autorelease];

    [scheduleLock lock];
    [scheduleQueue insertObject:event inArraySortedUsingSelector:@selector(compare:)];
    if ([scheduleQueue objectAtIndex:0] == event) {
        [self _resetAlarmToFirstEntry];
    }
    [scheduleLock unlock];

    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]];
}

- (void)abortEvent:(OFScheduledEvent *)event;
{
    BOOL resetAlarm;

    if (!event)
        return;
    [scheduleLock lock];
    resetAlarm = ([scheduleQueue count] && ([scheduleQueue objectAtIndex:0] == event));
    [scheduleQueue removeObject:event fromArraySortedUsingSelector:@selector(compare:)];
    if (resetAlarm)
	[self _resetAlarmToFirstEntry];
    [scheduleLock unlock];    
}

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

- (OFScheduler *)subScheduler;
{
    return [[[OFSubscheduler alloc] _initWithParent:self] autorelease];
}

@end


@implementation OFScheduler (Private)

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


// Private API

- (void)_resetAlarmToFirstEntryInMainThread;
{
    NSDate *eventDate;
    double period;

    OBPRECONDITION([NSThread inMainThread]);
    [scheduleLock lock];

    [self _invalidateAlarm];

    if ([scheduleQueue count]) {
        eventDate = [[scheduleQueue objectAtIndex:0] date];
        period = [eventDate timeIntervalSinceNow];  // This may be <0, as NSTimer just substitutes >0

        alarmTimer = [[NSTimer timerWithTimeInterval:period target:self selector:@selector(_alarm) userInfo:nil repeats:NO] retain];
        [[NSRunLoop currentRunLoop] addTimer:alarmTimer forMode:NSDefaultRunLoopMode];

        if (OFSchedulerDebug)
            NSLog(@"%@: waiting until %@ (%@)", [self shortDescription], eventDate, alarmTimer);
    }
    
    [scheduleLock unlock];
}

- (void)_resetAlarmToFirstEntry;
{
    [self mainThreadPerformSelector:@selector(_resetAlarmToFirstEntryInMainThread)];
}

- (void)_alarm;
{
    NSDate *currentDate;
    unsigned int count;
    NSMutableArray *events;

    events = [[NSMutableArray alloc] init];
    [scheduleLock lock];
    count = [scheduleQueue count];
    currentDate = [NSDate date];

    while (count--) {
        OFScheduledEvent *event;

        event = [scheduleQueue objectAtIndex:0];
        if ([[event date] compare:currentDate] == NSOrderedDescending) {
            [self _resetAlarmToFirstEntry];
            break;
        }
        [events addObject:event];
        [scheduleQueue removeObjectAtIndex:0];
        if (OFSchedulerDebug)
            NSLog(@"%@: invoking %@", [self shortDescription], [event shortDescription]);
    }
    [scheduleLock unlock];
    [events makeObjectsPerformSelector:@selector(invoke)];
    [events release];
}

- (void)_invalidateAlarm;
{
    if (OFSchedulerDebug)
        NSLog(@"%@: invalidating alarm timer %@", [self shortDescription], alarmTimer);
    [alarmTimer invalidate];
    [alarmTimer release];
    alarmTimer = nil;
}


//

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

    debugDictionary = [super debugDictionary];

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

@end


@implementation OFSubscheduler

// Init and dealloc

- _initWithParent:(OFScheduler *)aParent;
{
    [super init];
    parent = [aParent retain];
    return self;
}


- (void)dealloc;
{
    if ([scheduleQueue count])
	[self _invalidateAlarm];
    [parent release];
    [super dealloc];
}


//

- (void)_resetAlarmToFirstEntry;
{
    [scheduleLock lock];

    /* just schedule with parent */
    [self _invalidateAlarm];

    if ([scheduleQueue count])
        nonretainedParentAlarmEvent = [parent scheduleSelector:@selector(_alarm) onObject:self withObject:nil atDate:[[scheduleQueue objectAtIndex:0] date]];

    [scheduleLock unlock];
}

- (void)_alarm;
{
    nonretainedParentAlarmEvent = nil;  // If we were called by our parent
    [super _alarm];
}

- (void)_invalidateAlarm;
{
    if (!nonretainedParentAlarmEvent)
        return;
    [parent abortEvent:nonretainedParentAlarmEvent];
    nonretainedParentAlarmEvent = nil;
}


//

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

    debugDictionary = [super debugDictionary];
    [debugDictionary setObject:[parent shortDescription] forKey:@"parent"];
    if (nonretainedParentAlarmEvent)
        [debugDictionary setObject:nonretainedParentAlarmEvent forKey:@"nonretainedParentAlarmEvent"];

    return debugDictionary;
}

@end
