#import <jni.h>
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <UserNotifications/UserNotifications.h>

static JavaVM *gJvm = NULL;
static jclass gBridgeClass = NULL;
static jmethodID gActivatedMethod = NULL;
static jmethodID gRepliedMethod = NULL;
static id<UNUserNotificationCenterDelegate> gDelegate = nil;

static const jint STATUS_UNKNOWN = -1;
static const jint STATUS_NOT_DETERMINED = 0;
static const jint STATUS_DENIED = 1;
static const jint STATUS_AUTHORIZED = 2;
static const jint STATUS_PROVISIONAL = 3;
static const jint STATUS_EPHEMERAL = 4;
static const jint DELIVERED = 0;
static const jint FAILED = -1;

@interface WireShareNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
@end

static JNIEnv *WireShareNotificationEnv(void) {
    if (gJvm == NULL) {
        return NULL;
    }
    JNIEnv *env = NULL;
    jint result = (*gJvm)->GetEnv(gJvm, (void **)&env, JNI_VERSION_1_6);
    if (result == JNI_EDETACHED) {
        if ((*gJvm)->AttachCurrentThread(gJvm, (void **)&env, NULL) != JNI_OK) {
            return NULL;
        }
    } else if (result != JNI_OK) {
        return NULL;
    }
    return env;
}

static NSString *NSStringFromJString(JNIEnv *env, jstring value, NSString *fallback) {
    if (value == NULL) {
        return fallback;
    }
    const char *utf = (*env)->GetStringUTFChars(env, value, NULL);
    if (utf == NULL) {
        return fallback;
    }
    NSString *string = [NSString stringWithUTF8String:utf];
    (*env)->ReleaseStringUTFChars(env, value, utf);
    return string == nil ? fallback : string;
}

static jint WireShareAuthorizationStatus(UNAuthorizationStatus status) {
    switch (status) {
        case UNAuthorizationStatusNotDetermined:
            return STATUS_NOT_DETERMINED;
        case UNAuthorizationStatusDenied:
            return STATUS_DENIED;
        case UNAuthorizationStatusAuthorized:
            return STATUS_AUTHORIZED;
        case UNAuthorizationStatusProvisional:
            return STATUS_PROVISIONAL;
        default:
            return STATUS_UNKNOWN;
    }
}

static void WireShareCallActivated(NSString *identifier) {
    if (identifier == nil || gBridgeClass == NULL || gActivatedMethod == NULL) {
        return;
    }
    [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
    JNIEnv *env = WireShareNotificationEnv();
    if (env == NULL) {
        return;
    }
    jstring id = (*env)->NewStringUTF(env, [identifier UTF8String]);
    if (id == NULL) {
        return;
    }
    (*env)->CallStaticVoidMethod(env, gBridgeClass, gActivatedMethod, id);
    (*env)->DeleteLocalRef(env, id);
}

static void WireShareCallReplied(NSString *identifier, NSString *text) {
    if (identifier == nil || gBridgeClass == NULL || gRepliedMethod == NULL) {
        return;
    }
    JNIEnv *env = WireShareNotificationEnv();
    if (env == NULL) {
        return;
    }
    jstring id = (*env)->NewStringUTF(env, [identifier UTF8String]);
    jstring reply = (*env)->NewStringUTF(env, [text == nil ? @"" : text UTF8String]);
    if (id == NULL || reply == NULL) {
        if (id != NULL) {
            (*env)->DeleteLocalRef(env, id);
        }
        if (reply != NULL) {
            (*env)->DeleteLocalRef(env, reply);
        }
        return;
    }
    (*env)->CallStaticVoidMethod(env, gBridgeClass, gRepliedMethod, id, reply);
    (*env)->DeleteLocalRef(env, id);
    (*env)->DeleteLocalRef(env, reply);
}

@implementation WireShareNotificationDelegate

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler {
    NSString *identifier = response.notification.request.identifier;
    if ([response.actionIdentifier isEqualToString:@"WIRESHARE_REPLY"] &&
        [response isKindOfClass:[UNTextInputNotificationResponse class]]) {
        UNTextInputNotificationResponse *reply = (UNTextInputNotificationResponse *)response;
        WireShareCallReplied(identifier, reply.userText);
    } else {
        WireShareCallActivated(identifier);
    }
    completionHandler();
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
    UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound;
#if defined(__MAC_11_0)
    options |= UNNotificationPresentationOptionBanner;
    options |= UNNotificationPresentationOptionList;
#else
    options |= UNNotificationPresentationOptionAlert;
#endif
    completionHandler(options);
}

@end

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    gJvm = vm;
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL Java_org_limewire_ui_compose_integration_MacUserNotificationBridge_nativeInitialize
    (JNIEnv *env, jclass clazz) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    if (gBridgeClass == NULL) {
        gBridgeClass = (jclass)(*env)->NewGlobalRef(env, clazz);
        gActivatedMethod = (*env)->GetStaticMethodID(
            env,
            gBridgeClass,
            "notificationActivated",
            "(Ljava/lang/String;)V"
        );
        gRepliedMethod = (*env)->GetStaticMethodID(
            env,
            gBridgeClass,
            "notificationReplied",
            "(Ljava/lang/String;Ljava/lang/String;)V"
        );
    }

    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    if (gDelegate == nil) {
        gDelegate = [[WireShareNotificationDelegate alloc] init];
    }
    center.delegate = gDelegate;

    UNNotificationAction *openAction = [UNNotificationAction
        actionWithIdentifier:@"WIRESHARE_OPEN"
        title:@"Open"
        options:UNNotificationActionOptionForeground
    ];
    UNNotificationCategory *defaultCategory = [UNNotificationCategory
        categoryWithIdentifier:@"WIRESHARE_DEFAULT"
        actions:@[openAction]
        intentIdentifiers:@[]
        options:UNNotificationCategoryOptionCustomDismissAction
    ];
    UNTextInputNotificationAction *replyAction = [UNTextInputNotificationAction
        actionWithIdentifier:@"WIRESHARE_REPLY"
        title:@"Reply"
        options:UNNotificationActionOptionNone
        textInputButtonTitle:@"Send"
        textInputPlaceholder:@"Reply"
    ];
    UNNotificationCategory *chatCategory = [UNNotificationCategory
        categoryWithIdentifier:@"WIRESHARE_CHAT"
        actions:@[replyAction, openAction]
        intentIdentifiers:@[]
        options:UNNotificationCategoryOptionCustomDismissAction
    ];
    [center setNotificationCategories:[NSSet setWithObjects:defaultCategory, chatCategory, nil]];
    [pool release];
}

JNIEXPORT jint JNICALL Java_org_limewire_ui_compose_integration_MacUserNotificationBridge_nativeAuthorizationStatus
    (JNIEnv *env, jclass clazz) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    __block jint result = STATUS_UNKNOWN;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
        result = WireShareAuthorizationStatus(settings.authorizationStatus);
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [pool release];
    return result;
}

JNIEXPORT jint JNICALL Java_org_limewire_ui_compose_integration_MacUserNotificationBridge_nativeRequestAuthorization
    (JNIEnv *env, jclass clazz) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    __block jint result = STATUS_UNKNOWN;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
    [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError *error) {
        if (!granted || error != nil) {
            result = STATUS_DENIED;
        } else {
            result = STATUS_AUTHORIZED;
        }
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [pool release];
    return result;
}

JNIEXPORT jint JNICALL Java_org_limewire_ui_compose_integration_MacUserNotificationBridge_nativeDeliver
    (JNIEnv *env,
     jclass clazz,
     jstring identifierValue,
     jstring titleValue,
     jstring bodyValue,
     jstring replyPlaceholderValue,
     jboolean playSound,
     jboolean allowReply) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *identifier = NSStringFromJString(env, identifierValue, @"wireshare-notification");
    NSString *title = NSStringFromJString(env, titleValue, @"WireShare");
    NSString *body = NSStringFromJString(env, bodyValue, @"");

    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    content.title = title;
    content.body = body;
    content.categoryIdentifier = allowReply == JNI_TRUE ? @"WIRESHARE_CHAT" : @"WIRESHARE_DEFAULT";
    if (playSound == JNI_TRUE) {
        content.sound = [UNNotificationSound defaultSound];
    }

    UNNotificationRequest *request = [UNNotificationRequest
        requestWithIdentifier:identifier
        content:content
        trigger:nil
    ];

    __block jint result = DELIVERED;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error) {
        if (error != nil) {
            result = FAILED;
        }
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    [content release];
    [pool release];
    return result;
}
