//
//  PSAlarms.m
//  Pester
//
//  Created by Nicholas Riley on Fri Oct 11 2002.
//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
//

#import "PSAlarms.h"
#import "PSAlarm.h"

NSString * const PSAlarmsDidChangeNotification = @"PSAlarmsDidChangeNotification";
NSString * const PSAlarmsNextAlarmDidChangeNotification = @"PSAlarmsNextAlarmDidChangeNotification";

static NSString * const PSPendingAlarms = @"Pester pending alarms"; // NSUserDefaults key

static PSAlarms *PSAlarmsAllAlarms = nil;

@interface PSAlarms (Private)

- (void)_updateNextAlarm;

@end

@implementation PSAlarms

+ (void)setUp;
{
    if (PSAlarmsAllAlarms == nil) {
        PSAlarmsAllAlarms = [[self alloc] init];
        [PSAlarmsAllAlarms _updateNextAlarm]; // only generate notifications after singleton established
    }
}

+ (PSAlarms *)allAlarms;
{
    NSAssert(PSAlarmsAllAlarms != nil, @"Attempt to use +[PSAlarms allAlarms] before setup complete");
    return PSAlarmsAllAlarms;
}

- (void)_updateNextAlarm;
{
    NSEnumerator *e;
    PSAlarm *alarm, *oldNextAlarm = nextAlarm;
    [nextAlarm release];
    nextAlarm = nil;
    // sort alarms so earliest is first
    [alarms sortUsingSelector: @selector(compareDate:)];
    // find first un-expired alarm
    e = [alarms objectEnumerator];
    while ( (alarm = [e nextObject]) != nil) {
        if ([alarm isValid]) {
            nextAlarm = [alarm retain];
            break;
        }
    }
    if (oldNextAlarm != nextAlarm)
        [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsNextAlarmDidChangeNotification object: nextAlarm];
}

- (id)init;
{
    if ( (self = [super init]) != nil) {
        alarms = [[NSMutableArray alloc] init];
        NS_DURING
            NSArray *alarmsData = [[NSUserDefaults standardUserDefaults] arrayForKey: PSPendingAlarms];
            NSEnumerator *e = [alarmsData objectEnumerator];
            NSData *alarmData;
            PSAlarm *alarm;
            while ( (alarmData = [e nextObject]) != nil) {
                alarm = [NSUnarchiver unarchiveObjectWithData: alarmData];
                if (alarm != nil)
                    [alarms addObject: alarm];
            }
        NS_HANDLER
            // XXX need better error handling here, don't stomp on data
            NSLog(@"An error occurred while attempting to restore the alarm list: %@", localException);
        NS_ENDHANDLER
        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerSet:) name: PSAlarmTimerSetNotification object: nil];
        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerExpired:) name: PSAlarmTimerExpiredNotification object: nil];
    }
    return self;
}

- (void)dealloc;
{
    [alarms release];
    [[NSNotificationCenter defaultCenter] removeObserver: self];
    [super dealloc];
}

- (void)_changed;
{
    NSMutableArray *alarmsData = [[NSMutableArray alloc] init];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSEnumerator *e;
    PSAlarm *alarm;
    [self _updateNextAlarm];
    // NSLog(@"PSAlarms _changed:\n%@", alarms);
    // archive
    e = [alarms objectEnumerator];
    while ( (alarm = [e nextObject]) != nil) {
        [alarmsData addObject: [NSArchiver archivedDataWithRootObject: alarm]];
    }
    [defaults setObject: alarmsData forKey: PSPendingAlarms];
    [defaults synchronize];
    [alarmsData release];
    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsDidChangeNotification object: self];
}

- (void)_alarmTimerExpired:(NSNotification *)notification;
{
    [alarms removeObject: [notification object]];
    [self _changed];
}

- (void)_alarmTimerSet:(NSNotification *)notification;
{
    [alarms addObject: [notification object]];
    [self _changed];
}

- (PSAlarm *)nextAlarm;
{
    return nextAlarm;
}

- (NSArray *)alarms;
{
    return alarms;
}

- (int)alarmCount;
{
    return [alarms count];
}

- (PSAlarm *)alarmAtIndex:(int)index;
{
    return [alarms objectAtIndex: index];
}

- (void)removeAlarmAtIndex:(int)index;
{
    [(PSAlarm *)[alarms objectAtIndex: index] cancelTimer];
    [alarms removeObjectAtIndex: index];
}

- (void)removeAlarmsAtIndices:(NSArray *)indices;
{
    NSEnumerator *e = [indices objectEnumerator];
    NSNumber *n;
    int indexCount = [indices count], i = 0, alarmIndex;
    int *indexArray = (int *)malloc(indexCount * sizeof(int));
    NS_DURING
        while ( (n = [e nextObject]) != nil) {
            alarmIndex = [n intValue];
            [(PSAlarm *)[alarms objectAtIndex: alarmIndex] cancelTimer];
            indexArray[i] = alarmIndex;
            i++;
        }
        [alarms removeObjectsFromIndices: indexArray numIndices: indexCount];
        free(indexArray); indexArray = NULL;
        [self _changed];
    NS_HANDLER
        free(indexArray);
        [self _changed];
        [localException raise];
    NS_ENDHANDLER
}

- (void)removeAlarms:(NSSet *)alarmsToRemove;
{
    NSEnumerator *e = [alarms objectEnumerator];
    PSAlarm *alarm;
    NSMutableArray *indices = [NSMutableArray arrayWithCapacity: [alarmsToRemove count]];
    int alarmIndex = 0;

    while ( (alarm = [e nextObject]) != nil) {
        if ([alarmsToRemove containsObject: alarm])
            [indices addObject: [NSNumber numberWithInt: alarmIndex]];
        alarmIndex++;
    }
    [self removeAlarmsAtIndices: indices];
}

@end
