//
//  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"
#import "PSTimer.h"
#import "NSDictionary-NJRExtensions.h"

NSString * const PSAlarmImportException = @"PSAlarmImportException";

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

// NSUserDefaults key
static NSString * const PSPendingAlarms = @"Pester pending alarms"; // 1.0 Ð 1.1a3
static NSString * const PSAllAlarms = @"Pester alarms"; // 1.1a4 Ð 

// property list keys
static NSString * const PLAlarmsPending = @"pending";
static NSString * const PLAlarmsExpired = @"expired";

static PSAlarms *PSAlarmsAllAlarms = nil;

@interface PSAlarms (Private)

- (void)_updateNextAlarm;

@end

@implementation PSAlarms

+ (void)setUp;
{
    [PSTimer setUp];
    if (PSAlarmsAllAlarms == nil) {
        NSDictionary *plAlarms = [[NSUserDefaults standardUserDefaults] objectForKey: PSAllAlarms];
        if (plAlarms == nil) {
            PSAlarmsAllAlarms = [[self alloc] init];
        } else {
            PSAlarmsAllAlarms = [[self alloc] initWithPropertyList: plAlarms];
        }
        [PSAlarmsAllAlarms _updateNextAlarm]; // only generate notifications after singleton established
    }
}

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

#pragma mark private

- (void)_changed;
{
    NSMutableArray *alarmsData = [[NSMutableArray alloc] init];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [self _updateNextAlarm];
    // NSLog(@"PSAlarms _changed:\n%@", alarms);
    [defaults setObject: [self propertyListRepresentation] forKey: PSAllAlarms];
    [defaults synchronize];
    [alarmsData release];
    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsDidChangeNotification object: self];
}

- (void)_alarmTimerExpired:(NSNotification *)notification;
{
    PSAlarm *alarm = [notification object];
    NSLog(@"timer expired: %@ retainCount %d", alarm, [alarm retainCount]);
    [expiredAlarms addObject: alarm];
    NSLog(@"expired alarms: %@", [expiredAlarms description]);
    [alarms removeObject: alarm];
    [self _changed];
}

- (void)_alarmTimerSet:(NSNotification *)notification;
{
    PSAlarm *alarm = [notification object];
    NSLog(@"timer set: %@ retainCount %d", alarm, [alarm retainCount]);
    [alarms addObject: alarm];
    [expiredAlarms removeObject: alarm];
    [self _changed];
}

- (void)_alarmDied:(NSNotification *)notification;
{
    PSAlarm *alarm = [notification object];
    // NSLog(@"alarm died: %@ retainCount %d", alarm, [alarm retainCount]);
    [alarms removeObject: alarm];
    [expiredAlarms removeObject: alarm];
    [self _changed];
}

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

- (void)_setUpNotifications;
{
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerSet:) name: PSAlarmTimerSetNotification object: nil];
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerExpired:) name: PSAlarmTimerExpiredNotification object: nil];
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmDied:) name: PSAlarmDiedNotification object: nil];
}

#pragma mark initialize-release

- (id)init;
{
    if ( (self = [super init]) != nil) {
        alarms = [[NSMutableArray alloc] init];
        expiredAlarms = [[NSMutableSet alloc] init];
        [self _setUpNotifications];
    }
    return self;
}

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

#pragma mark accessing

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

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

- (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;
    unsigned indexCount = [indices count], i = 0, alarmIndex;
    unsigned *indexArray = (unsigned *)malloc(indexCount * sizeof(unsigned));
    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];
}

- (void)restoreAlarms:(NSSet *)alarmsToRestore;
{
    [alarmsToRestore makeObjectsPerformSelector: @selector(resetTimer)];
}

- (BOOL)alarmsExpiring;
{
    return [expiredAlarms count] != 0;
}

#pragma mark printing

- (NSString *)description;
{
    return [NSString stringWithFormat: @"%@ pending %@\n%@\n",
        [super description], alarms,
        [expiredAlarms count] > 0 ? [NSString stringWithFormat: @"expired %@\n", expiredAlarms]
                                  : @""];
}

#pragma mark property list serialization (Pester 1.1)

- (NSDictionary *)propertyListRepresentation;
{
    NSMutableArray *plPendingAlarms = [[NSMutableArray alloc] initWithCapacity: [alarms count]];
    NSMutableArray *plExpiredAlarms = [[NSMutableArray alloc] initWithCapacity: [expiredAlarms count]];
    NSDictionary *plAllAlarms, *plAlarm;
    NSEnumerator *e;
    PSAlarm *alarm;

    e = [alarms objectEnumerator];
    while ( (alarm = [e nextObject]) != nil) {
        plAlarm = [alarm propertyListRepresentation];
        if (plAlarm != nil)
            [plPendingAlarms addObject: plAlarm];
    }

    e = [expiredAlarms objectEnumerator];
    while ( (alarm = [e nextObject]) != nil) {
        plAlarm = [alarm propertyListRepresentation];
        if (plAlarm != nil)
            [plExpiredAlarms addObject: plAlarm];
    }
    
    plAllAlarms = [NSDictionary dictionaryWithObjectsAndKeys:
        plPendingAlarms, PLAlarmsPending, plExpiredAlarms, PLAlarmsExpired, nil];
    [plPendingAlarms release];
    [plExpiredAlarms release];

    return plAllAlarms;
}

- (id)initWithPropertyList:(NSDictionary *)dict;
{
    if ( (self = [super init]) != nil) {
        NSArray *plPendingAlarms = [dict objectForRequiredKey: PLAlarmsPending];
        NSArray *plExpiredAlarms = [dict objectForRequiredKey: PLAlarmsExpired];
        NSEnumerator *e;
        NSDictionary *plAlarm;
        PSAlarm *alarm;

        alarms = [[NSMutableArray alloc] initWithCapacity: [plPendingAlarms count]];
        e = [plPendingAlarms objectEnumerator];
        while ( (plAlarm = [e nextObject]) != nil) {
	    alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm];
            [alarms addObject: alarm];
	    [alarm release];
        }

        e = [plExpiredAlarms objectEnumerator];
        while ( (plAlarm = [e nextObject]) != nil) {
            // expired alarms may be ready for deletion, or may repeat - if the latter, PSAlarm will reschedule the alarm so the repeat interval begins at restoration time. 
            if ( (alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm]) != nil) {
                [alarms addObject: alarm];
		[alarm release];
	    }
        }
        expiredAlarms = [[NSMutableSet alloc] init];
        
        [self _setUpNotifications];
    }
    return self;
}

#pragma mark archiving (Pester 1.0)

- (unsigned)countOfVersion1Alarms;
{
    return [[[NSUserDefaults standardUserDefaults] objectForKey: PSPendingAlarms] count];
}

- (void)discardVersion1Alarms;
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObjectForKey: PSPendingAlarms];
    [defaults synchronize];
}

- (void)importVersion1Alarms;
{
    NSArray *alarmsData = [[NSUserDefaults standardUserDefaults] arrayForKey: PSPendingAlarms];
    NSEnumerator *e = [alarmsData objectEnumerator];
    NSData *alarmData;
    PSAlarm *alarm;
    while ( (alarmData = [e nextObject]) != nil) {
        NS_DURING
            alarm = [NSUnarchiver unarchiveObjectWithData: alarmData];
        NS_HANDLER
            alarm = nil;
            // XXX
        NS_ENDHANDLER
        if (alarm != nil)
            [alarms addObject: alarm];
    }
}

@end
