//
//  WakeupPlugin.m
//
//  Created by Brad Kammin on 4/29/14.
//
//

#import "WakeupPlugin.h"

static NSString * const kWakeupPluginJSONAlarmsKey = @"alarms";
static NSString * const kWakeupPluginJSONTypeKey = @"type";
static NSString * const kWakeupPluginJSONAlarmTypeKey = @"alarm_type";
static NSString * const kWakeupPluginJSONExtraKey = @"extra";
static NSString * const kWakeupPluginJSONTimeKey = @"time";
static NSString * const kWakeupPluginJSONMessageKey = @"message";
static NSString * const kWakeupPluginJSONActionKey = @"action";
static NSString * const kWakeupPluginJSONSoundKey = @"sound";
static NSString * const kWakeupPluginJSONDaysKey = @"days";
static NSString * const kWakeupPluginJSONHourKey = @"hour";
static NSString * const kWakeupPluginJSONMinuteKey = @"minute";
static NSString * const kWakeupPluginJSONSecondsKey = @"seconds";
static NSString * const kWakeupPluginJSONAlarmDateKey = @"alarm_date";

static NSString * const kWakeupPluginJSONWakeupValue = @"wakeup";
static NSString * const kWakeupPluginJSONOneTimeValue = @"onetime";
static NSString * const kWakeupPluginJSONDaylistValue = @"daylist";
static NSString * const kWakeupPluginJSONSetValue = @"set";

static NSString * const kWakeupPluginJSONDaySundayValue = @"sunday";
static NSString * const kWakeupPluginJSONDayMondayValue = @"monday";
static NSString * const kWakeupPluginJSONDayTuesdayValue = @"tuesday";
static NSString * const kWakeupPluginJSONDayWednesdayValue = @"wednesday";
static NSString * const kWakeupPluginJSONDayThursdayValue = @"thursday";
static NSString * const kWakeupPluginJSONDayFridayValue = @"friday";
static NSString * const kWakeupPluginJSONDaySaturdayValue = @"saturday";

static NSString * const kWakeupPluginAlarmSettingsFile = @"alarmsettings.plist";

@interface WakeupPlugin ()

@property NSString * callbackId;

@end

@implementation WakeupPlugin

- (void)pluginInitialize
{
    // watch for local notification
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wup_onLocalNotification:) name:@"CDVLocalNotification" object:nil]; // if app is in foreground

    [UIDevice currentDevice].batteryMonitoringEnabled = YES; // required to determine if device is charging
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wup_onBatteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil];
    [self wup_onBatteryStateDidChange:nil];

    NSLog(@"Wakeup Plugin initialized");
}

#pragma mark Plugin methods

- (void)bind:(CDVInvokedUrlCommand*)command
{
    NSLog(@"bind");

    self.callbackId = command.callbackId;

    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [pluginResult setKeepCallbackAsBool:YES];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)wakeup:(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = nil;
    NSDictionary * options = [command.arguments objectAtIndex:0];
    NSArray * alarms;

    if ([options objectForKey:kWakeupPluginJSONAlarmsKey]) {
        alarms = [options objectForKey:kWakeupPluginJSONAlarmsKey];
    } else {
        alarms = [NSArray array]; // empty means cancel all
    }

    NSLog(@"scheduling wakeups...");

    [self wup_saveToPrefs:alarms];

    [self wup_setAlarms:alarms cancelAlarms:true];

    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [pluginResult setKeepCallbackAsBool:YES];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

#pragma mark Preference storage methods

- (void)wup_saveToPrefs:(NSArray *)alarms
{
    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:alarms options:0 error:&error];

    if (!jsonData) {
        NSLog(@"error converting NSDictionary to JSON string: %@", error);
    } else {
        NSMutableDictionary *settings = [self wup_preferences];
        NSString *alarmsJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        [settings setValue:alarmsJson forKey:kWakeupPluginJSONAlarmsKey];
        NSString * prefsFile = [self wup_prefsFilePath];

        if (![settings writeToFile:prefsFile atomically:YES]) {
            NSLog(@"failed to save preferences to file!");
        } else {
            [self wup_addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:prefsFile]];
        }
    }
}

- (NSMutableDictionary *) wup_preferences
{
    NSMutableDictionary *prefs;

    if ([[NSFileManager defaultManager] fileExistsAtPath: [self wup_prefsFilePath]]) {
        prefs = [[NSMutableDictionary alloc] initWithContentsOfFile: [self wup_prefsFilePath]];
    } else {
        prefs = [[NSMutableDictionary alloc] initWithCapacity: 10];
        /* set default values */
        [prefs setObject:@{} forKey:kWakeupPluginJSONAlarmsKey];
    }

    return prefs;
};

- (NSString *) wup_prefsFilePath
{
    NSString *cacheDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    NSString *prefsFilePath = [cacheDirectory stringByAppendingPathComponent: kWakeupPluginAlarmSettingsFile];
    return prefsFilePath;
}

// prevent backup to the Cloud
- (BOOL)wup_addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    BOOL success = false;

    if ([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]) {
        NSError *error = nil;
        success = [URL setResourceValue: [NSNumber numberWithBool: YES] forKey: NSURLIsExcludedFromBackupKey error: &error];

        if (!success) {
            NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
        }
    }

    return success;
}

#pragma mark Alarm configuration methods

- (void)wup_setNotification:(NSString*)type alarmDate:(NSDate*)alarmDate extra:(NSDictionary*)extra message:(NSString*)message action:(NSString*)action  sound:(NSString*)sound repeatInterval:(int)repeatInterval
{
    if (alarmDate) {
        UILocalNotification* alarm = [[UILocalNotification alloc] init];

        if (alarm) {
            alarm.fireDate = alarmDate;
            alarm.timeZone = [NSTimeZone defaultTimeZone];
            alarm.repeatInterval = repeatInterval;

            if (sound != nil) {
                alarm.soundName = sound;
            } else {
                alarm.soundName = UILocalNotificationDefaultSoundName;
            }

            if (message != nil){
                alarm.alertBody = message;
            } else {
                alarm.alertBody = @"Wake up!";
            }

            if (action != nil){
                alarm.alertAction = action;
            }

            NSError *error;
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:extra options:0 error:&error];

            NSString *json = @"{}"; // default empty

            if (jsonData) {
                json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            }

            alarm.userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
                              kWakeupPluginJSONWakeupValue, kWakeupPluginJSONTypeKey,
                              type, kWakeupPluginJSONAlarmTypeKey,
                              json,  kWakeupPluginJSONExtraKey, nil];

            NSLog(@"scheduling a new alarm local notification for %@", alarm.fireDate);

            UIApplication * app = [UIApplication sharedApplication];
            [app scheduleLocalNotification:alarm];

            NSTimeInterval time = [alarmDate timeIntervalSince1970];
            NSNumber *timeMs = [NSNumber numberWithDouble:(time * 1000)];

            if (self.callbackId != nil) {
                CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{kWakeupPluginJSONTypeKey: kWakeupPluginJSONSetValue, kWakeupPluginJSONAlarmTypeKey: type, kWakeupPluginJSONAlarmDateKey: timeMs}];
                [pluginResult setKeepCallbackAsBool:YES];
                [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
            }
        }
    }
}

- (void)wup_setAlarms:(NSArray *)alarms cancelAlarms:(BOOL)cancelAlarms
{
    BOOL backgroundSupported = [self wup_isBackgroundSupported];

    if (cancelAlarms) {
        [self wup_cancelAlarms];
    }

    if (backgroundSupported) {
        for (int i = 0; i < [alarms count]; i++){
            NSDictionary * alarm = alarms[i];

            NSString * type = [alarm valueForKeyPath:kWakeupPluginJSONTypeKey];
            NSDictionary * time = [alarm valueForKeyPath:kWakeupPluginJSONTimeKey];
            NSDictionary * extra = [alarm valueForKeyPath:kWakeupPluginJSONExtraKey];
            NSString * message = [alarm valueForKeyPath:kWakeupPluginJSONMessageKey];
            NSString * action = [alarm valueForKeyPath:kWakeupPluginJSONActionKey];
            NSString * sound = [alarm valueForKeyPath:kWakeupPluginJSONSoundKey];

            if (type == nil) {
                type = kWakeupPluginJSONOneTimeValue;
            }

            // other types to add support for: weekly, daily, weekday, weekend
            if ([type isEqualToString:kWakeupPluginJSONOneTimeValue]) {
                NSDate * alarmDate = [self wup_getOneTimeAlarmDate:time];
                [self wup_setNotification:type alarmDate:alarmDate extra:extra message:message action:action sound:sound repeatInterval:0];
            } else if ([type isEqualToString:kWakeupPluginJSONDaylistValue]) {
                NSArray * days = [alarm valueForKeyPath:kWakeupPluginJSONDaysKey];

                for (int j = 0; j < [days count]; j++) {
                    NSDate * alarmDate = [self wup_getAlarmDate:time day:[self wup_dayOfWeekIndex:[days objectAtIndex:j]]];
                    [self wup_setNotification:type alarmDate:alarmDate extra:extra message:message action:action sound:sound repeatInterval:NSWeekCalendarUnit];
                }
            }

            NSLog(@"setting alarm...");
        }
    }
}

- (void) wup_cancelAlarms
{
    UIApplication * app = [UIApplication sharedApplication];
    NSArray *localNotifications = [app scheduledLocalNotifications];

    for (UILocalNotification *not in localNotifications) {
        NSString * type = [not.userInfo objectForKey:kWakeupPluginJSONTypeKey];

        if (type && [type isEqualToString:kWakeupPluginJSONWakeupValue]) {
            NSLog(@"cancelling existing alarm notification");
            [app cancelLocalNotification:not];
        }
    }
}

- (BOOL) wup_isBackgroundSupported
{
    UIDevice* device = [UIDevice currentDevice];
    BOOL backgroundSupported = NO;

    if ([device respondsToSelector:@selector(isMultitaskingSupported)]) {
        backgroundSupported = device.multitaskingSupported;
    }

    return backgroundSupported;
}

- (NSDate*) wup_getOneTimeAlarmDate:(NSDictionary*)time
{
    NSDate *alarmDate = nil;
    NSDate * now = [NSDate date];
    int hour = [time objectForKey:kWakeupPluginJSONHourKey] != nil ? [[time objectForKey:kWakeupPluginJSONHourKey] intValue] : -1;
    int minute = [time objectForKey:kWakeupPluginJSONMinuteKey] != nil ? [[time objectForKey:kWakeupPluginJSONMinuteKey] intValue] : 0;
    NSCalendar * gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *nowComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now]; // set to current day
    [nowComponents setHour:hour];
    [nowComponents setMinute:minute];
    [nowComponents setSecond:0];
    alarmDate = [gregorian dateFromComponents:nowComponents];

    if ([alarmDate compare:now] == NSOrderedAscending) {
        NSDateComponents * addDayComponents = [[NSDateComponents alloc] init];
        [addDayComponents setDay:1];
        alarmDate = [gregorian dateByAddingComponents:addDayComponents toDate:alarmDate options:0];
    }

    return alarmDate;
}

- (NSDate*) wup_getAlarmDate:(NSDictionary*)time day:(int)dayOfWeek
{
    NSDate *alarmDate = nil;
    NSDate * now = [NSDate date];
    unsigned nowSeconds=[self wup_secondOfTheDay:now];

    int hour = [time objectForKey:kWakeupPluginJSONHourKey] != nil ? [[time objectForKey:kWakeupPluginJSONHourKey] intValue] : -1;
    int minute = [time objectForKey:kWakeupPluginJSONMinuteKey] != nil ? [[time objectForKey:kWakeupPluginJSONMinuteKey] intValue] : 0;

    if (hour >= 0 && dayOfWeek >= 0) {
        NSCalendar * gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
        NSDateComponents *nowComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now];
        [nowComponents setHour:hour];
        [nowComponents setMinute:minute];
        [nowComponents setSecond:0];

        gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
        NSDateComponents *weekdayComponents =[gregorian components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
        NSInteger currentDayOfWeek = [weekdayComponents weekday]; // 1-7 = Sunday-Saturday
        currentDayOfWeek--; // make zero-based

        // add number of days until 'dayOfWeek' occurs
        alarmDate = [gregorian dateFromComponents:nowComponents];
        unsigned alarmSeconds=[self wup_secondOfTheDay:alarmDate];

        long daysUntilAlarm = 0;

        if (currentDayOfWeek > dayOfWeek) {
            // currentDayOfWeek=thursday (4); alarm=monday (1) -- add 4 days
            daysUntilAlarm = (6 - currentDayOfWeek) + dayOfWeek + 1; // (days until the end of week) + dayOfWeek + 1
        } else if(currentDayOfWeek < dayOfWeek) {
            // example: currentDayOfWeek=monday (1); dayOfWeek=thursday (4) -- add three days
            daysUntilAlarm = dayOfWeek - currentDayOfWeek;
        } else {
            if (alarmSeconds > nowSeconds) {
                daysUntilAlarm = 0;
            } else {
                daysUntilAlarm = 7;
            }
        }

        NSDateComponents * addDayComponents = [[NSDateComponents alloc] init];
        [addDayComponents setDay:(daysUntilAlarm)];
        alarmDate = [gregorian dateByAddingComponents:addDayComponents toDate:alarmDate options:0];
    }

    return alarmDate;
}

- (NSDate*) wup_getTimeFromNow:(NSDictionary*)time
{
    NSDate *alarmDate = [NSDate date];

    int seconds = [time objectForKey:kWakeupPluginJSONSecondsKey]!=nil ? [[time objectForKey:kWakeupPluginJSONSecondsKey] intValue] : -1;

    if (seconds >= 0) {
        NSCalendar * gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
        NSDateComponents * addSeconds = [[NSDateComponents alloc] init];
        [addSeconds setSecond:seconds];
        alarmDate = [gregorian dateByAddingComponents:addSeconds toDate:alarmDate options:0];
    } else {
        alarmDate=nil;
    }

    return alarmDate;
}

- (unsigned) wup_secondOfTheDay:(NSDate*) time
{
    // extracts hour/minutes/seconds from NSDate, converts to seconds since midnight
    NSCalendar* curCalendar = [NSCalendar currentCalendar];
    const unsigned units    = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
    NSDateComponents* comps = [curCalendar components:units fromDate:time];
    int hour = (int)[comps hour];
    int min  = (int)[comps minute];
    int sec  = (int)[comps second];

    return ((hour * 60) + min) * 60 + sec;
}

- (int) wup_dayOfWeekIndex:(NSString*)day
{
    int dayIndex = -1;

    if ([day isEqualToString:kWakeupPluginJSONDaySundayValue]) {
        dayIndex = 0;
    } else if ([day isEqualToString:kWakeupPluginJSONDayMondayValue]) {
        dayIndex = 1;
    } else if ([day isEqualToString:kWakeupPluginJSONDayTuesdayValue]) {
        dayIndex = 2;
    } else if ([day isEqualToString:kWakeupPluginJSONDayWednesdayValue]) {
        dayIndex = 3;
    } else if ([day isEqualToString:kWakeupPluginJSONDayThursdayValue]) {
        dayIndex = 4;
    } else if ([day isEqualToString:kWakeupPluginJSONDayFridayValue]) {
        dayIndex = 5;
    } else if ([day isEqualToString:kWakeupPluginJSONDaySaturdayValue]) {
        dayIndex = 6;
    }

    return dayIndex;
}

#pragma mark Wakeup handlers

- (void)wup_onLocalNotification:(NSNotification *)notification
{
    NSLog(@"Wakeup Plugin received local notification while app is running");

    UILocalNotification* localNotification = [notification object];
    NSString * notificationType = [[localNotification userInfo] objectForKey:kWakeupPluginJSONTypeKey];

    if (notificationType != nil && [notificationType isEqualToString:kWakeupPluginJSONWakeupValue] && self.callbackId != nil) {
        NSLog(@"wakeup detected!");
        NSString * extra = [[localNotification userInfo] objectForKey:kWakeupPluginJSONExtraKey];
        CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{kWakeupPluginJSONTypeKey: kWakeupPluginJSONWakeupValue, kWakeupPluginJSONExtraKey: extra}];
        [pluginResult setKeepCallbackAsBool:YES];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
    }
}

- (void)wup_onBatteryStateDidChange:(NSNotification *)notification
{
    NSLog(@"Wakeup Plugin battery status changed");

    if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateCharging || [UIDevice currentDevice].batteryState == UIDeviceBatteryStateFull) {
        // device is charging - disable automatic screen-locking
        [UIApplication sharedApplication].idleTimerDisabled = YES;
    } else {
        // device is NOT charging - enable automatic screen-locking
        [UIApplication sharedApplication].idleTimerDisabled = NO;
    }
}

#pragma mark Cleanup

- (void)dispose
{
    NSLog(@"Wakeup Plugin disposing");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"CDVLocalNotification" object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryStateDidChangeNotification object:nil];
    [super dispose];
}

@end
