//
//  RNCCClient.m
//  RNContextCenter
//
//  Created by Shaffiulla Khan on 07/01/19.
//  Copyright © 2019 Impekable LLC. All rights reserved.
//

#import "RNCCClient.h"
#import "RNCCManager.h"
#import <React/RCTUtils.h>
#import <React/RCTEventDispatcher.h>
#import "RCTConvert+ContextCenter.h"

@interface RNCCClient() <TwilioChatClientDelegate>
@end

@implementation RNCCClient
@synthesize bridge = _bridge;
@synthesize twilioClient;

#pragma mark Shared / Singleton

- (NSArray<NSString *> *)supportedEvents {
    return @[];
}

+ (id)shared {
    static RNCCClient *sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedClient = [[self alloc] init];
    });
    return sharedClient;
}

RCT_EXPORT_MODULE(CCClient);
RCT_REMAP_METHOD(setupClient, token:(NSString*)token properties:(NSDictionary *)properties resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
    TwilioChatClientProperties *props = nil;
    if (properties.count > 0) {
        props = [[TwilioChatClientProperties alloc] init];
    }
    RNCCClient *ccClient = [RNCCClient shared];
    [TwilioChatClient chatClientWithToken:token properties:props delegate:self completion:^(TCHResult *result, TwilioChatClient *chatClient) {
        if(result.isSuccessful){
            ccClient.twilioClient = chatClient;
            resolve( [RCTConvert twilioChatClient:chatClient]);
        }else{
            reject(@"setupClient", @"Error occured while setting up twilio chat client", result.error);
        }
    }];
}

RCT_EXPORT_METHOD(updateToken:(NSString *)token) {
    RNCCClient *ccClient = [RNCCClient shared];
    [ccClient.twilioClient updateToken:token completion:^(TCHResult * _Nonnull result) {
        if (!result.isSuccessful) {
            NSLog(@"error updating token: %@", result.error);
        }
    }];
}

RCT_REMAP_METHOD(userInfo, userInfo_resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    RNCCClient *ccClient = [RNCCClient shared];
    resolve([RCTConvert user:ccClient.twilioClient.user]);
}

RCT_EXPORT_METHOD(synchronizationStatus:(RCTResponseSenderBlock)resolve) {
    RNCCClient *ccClient = [RNCCClient shared];
    resolve(@[@(ccClient.twilioClient.synchronizationStatus)]);
}

RCT_EXPORT_METHOD(twilioVersion:(RCTResponseSenderBlock)resolve) {
    resolve(@[twilioClient.version]);
}

RCT_EXPORT_METHOD(registerClient:(NSString *)token) {
    RNCCClient *ccClient = [RNCCClient shared];
    [ccClient.twilioClient registerWithNotificationToken:[RCTConvert dataWithHexString:token] completion:^(TCHResult *result) {}];
}

RCT_EXPORT_METHOD(unregisterClient:(NSString *)token) {
    RNCCClient *ccClient = [RNCCClient shared];
    [ccClient.twilioClient deregisterWithNotificationToken:[RCTConvert dataWithHexString:token] completion:^(TCHResult *result) {}];
}

RCT_EXPORT_METHOD(handleNotification:(NSDictionary *)notification) {
    RNCCClient *ccClient = [RNCCClient shared];
    [ccClient.twilioClient handleNotification:notification completion:^(TCHResult *result) {}];
}

RCT_EXPORT_METHOD(close) {
    RNCCClient *ccClient = [RNCCClient shared];
    [[ccClient twilioClient] shutdown];
}

RCT_EXPORT_METHOD(enableLogLevel:(TCHLogLevel)logLevel) {
    [TwilioChatClient setLogLevel:logLevel];
}

RCT_EXPORT_METHOD(twilioLogLevel:(RCTResponseSenderBlock)resolve) {
    resolve(@[@(TwilioChatClient.logLevel)]);
}

RCT_REMAP_METHOD(setFriendlyName, name:(NSString *)name friendlyname_resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    RNCCClient *ccClient = [RNCCClient shared];
    [[[ccClient twilioClient] user] setFriendlyName:name completion:^(TCHResult *result) {
        if (result.isSuccessful) {
            resolve(@[@TRUE]);
        }
        else {
            reject(@"setFriendlyNameError", @"Error occured setting friendly name for the twilio user.", result.error);
        }
    }];
}

RCT_REMAP_METHOD(setUserAttributes, attributes:(NSDictionary *)attributes user_attributes_resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    RNCCClient *ccClient = [RNCCClient shared];
    [[[ccClient twilioClient] user] setAttributes:attributes completion:^(TCHResult *result) {
        if (result.isSuccessful) {
            resolve(@[@TRUE]);
        }
        else {
            reject(@"setAttributesError", @"Error occured setting attributes for the twilio user.", result.error);
        }
    }];
}

RCT_REMAP_METHOD(getConstants, constant_resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    resolve([RCTConvert constant]);
}

#pragma mark TwilioChatClientDelegate
/** Called when the client connection state changes.
 
 @param client The chat client.
 @param state The current connection state of the client.
 */
- (void)chatClient:(TwilioChatClient *)client connectionStateUpdated:(TCHClientConnectionState)state{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:connectionStateUpdated" body:@(state)];
}

/** Called when the client synchronization state changes during startup.
 
 @param client The chat client.
 @param status The current synchronization status of the client.
 */
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:synchronizationStatusUpdated" body:@(status)];
}

/** Called when the current user has a channel added to their channel list.
 
 @param client The chat client.
 @param channel The channel.
 */
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channelAdded" body:[RCTConvert channel:channel]];
}

/** Called when one of the current users channels is changed.
 
 @param client The chat client.
 @param channel The channel.
 @param updated An indication of what changed on the channel.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel updated:(TCHChannelUpdate)updated{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:updated"
                                                 body: @{ @"channelSid": channel.sid, @"updated": @(updated), @"channel": [RCTConvert channel:channel]}];
}

/** Called when a channel the current the client is aware of changes synchronization state.
 
 @param client The chat client.
 @param channel The channel.
 @param status The current synchronization status of the channel.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel synchronizationStatusUpdated:(TCHChannelSynchronizationStatus)status{
    NSString *channelSid = channel ? channel.sid : RCTNullIfNil(nil);
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:synchronizationStatusUpdated"
                                                 body: @{ @"channelSid": channelSid,@"status": @(status)}];
}

/** Called when one of the current users channels is deleted.
 
 @param client The chat client.
 @param channel The channel.
 */
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channelDeleted" body: [RCTConvert channel:channel]];
}

/** Called when a channel the current user is subscribed to has a new member join.
 
 @param client The chat client.
 @param channel The channel.
 @param member The member.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel memberJoined:(TCHMember *)member{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:memberJoined"
                                                 body: @{@"channelSid":channel.sid, @"member": [RCTConvert member:member]}];
}

/** Called when a channel the current user is subscribed to has a member modified.
 
 @param client The chat client.
 @param channel The channel.
 @param member The member.
 @param updated An indication of what changed on the member.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel member:(TCHMember *)member updated:(TCHMemberUpdate)updated{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:member:updated"
                                                 body: @{@"channelSid": channel.sid, @"updated": @(updated),@"member": [RCTConvert member:member]}];
}

/** Called when a channel the current user is subscribed to has a member leave.
 
 @param client The chat client.
 @param channel The channel.
 @param member The member.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel memberLeft:(TCHMember *)member{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:memberLeft" body: @{@"channelSid": channel.sid, @"member": [RCTConvert member:member]}];
}

/** Called when a channel the current user is subscribed to receives a new message.
 
 @param client The chat client.
 @param channel The channel.
 @param message The message.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel messageAdded:(TCHMessage *)message{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:messageAdded"
                                                 body: @{@"channelSid": channel.sid,@"message": [RCTConvert message:message]}];
}

/** Called when a message on a channel the current user is subscribed to is modified.
 
 @param client The chat client.
 @param channel The channel.
 @param message The message.
 @param updated An indication of what changed on the message.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel message:(TCHMessage *)message updated:(TCHMessageUpdate)updated{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:message:updated"
                                                 body: @{@"channelSid": channel.sid, @"updated": @(updated),@"message": [RCTConvert message:message]}];
}

/** Called when a message on a channel the current user is subscribed to is deleted.
 
 @param client The chat client.
 @param channel The channel.
 @param message The message.
 */
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel messageDeleted:(TCHMessage *)message{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:channel:messageDeleted"
                                                 body: @{@"channelSid": channel.sid,@"message": [RCTConvert message:message]}];
}

/** Called when an error occurs.
 
 @param client The chat client.
 @param error The error.
 */
- (void)chatClient:(TwilioChatClient *)client errorReceived:(TCHError *)error{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:errorReceived" body:@{@"error": [error localizedDescription], @"userInfo": [error userInfo]}];
}

/** Called when a member of a channel starts typing.
 
 @param client The chat client.
 @param channel The channel.
 @param member The member.
 */
- (void)chatClient:(TwilioChatClient *)client typingStartedOnChannel:(TCHChannel *)channel member:(TCHMember *)member{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:typingStartedOnChannel:member"
                                                 body: @{@"channelSid": channel.sid,@"member": [RCTConvert member:member]}];
}

/** Called when a member of a channel ends typing.
 
 @param client The chat client.
 @param channel The channel.
 @param member The member.
 */
- (void)chatClient:(TwilioChatClient *)client typingEndedOnChannel:(TCHChannel *)channel member:(TCHMember *)member{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:typingEndedOnChannel:member"
                                                 body: @{@"channelSid": channel.sid,@"member": [RCTConvert member:member]}];
}

/** Called as a result of TwilioChatClient's handleNotification: method being invoked.  `handleNotification:` parses the push payload and extracts the channel and message for the push notification then calls this delegate method.
 
 @param client The chat client.
 @param channelSid The channel sid for the push notification.
 @param messageIndex The index of the new message.
 */
- (void)chatClient:(TwilioChatClient *)client notificationNewMessageReceivedForChannelSid:(NSString *)channelSid messageIndex:(NSUInteger)messageIndex{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:notificationNewMessageReceivedForChannelSid:messageIndex"
                                                 body: @{@"channelSid": channelSid,@"index": @(messageIndex)}];
}

/** Called when a processed push notification has changed the application's badge count.  You should call:
 
 [[UIApplication currentApplication] setApplicationIconBadgeNumber:badgeCount]
 
 To ensure your application's badge updates when the application is in the foreground if Twilio is managing your badge counts.  You may disregard this delegate callback otherwise.
 
 @param client The chat client.
 @param badgeCount The updated badge count.
 */
- (void)chatClient:(TwilioChatClient *)client notificationUpdatedBadgeCount:(NSUInteger)badgeCount{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:notificationUpdatedBadgeCount" body: @(badgeCount)];
}

/** Called when the current user's or that of any subscribed channel member's user is updated.
 
 @param client The chat client.
 @param user The object for changed user.
 @param updated An indication of what changed on the user.
 */
- (void)chatClient:(TwilioChatClient *)client user:(TCHUser *)user updated:(TCHUserUpdate)updated{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:user:updated" body: @{@"user": [RCTConvert user:user], @"updated": @(updated)}];
}

/** Called when the client subscribes to updates for a given user.
 
 @param client The chat client.
 @param user The object for subscribed user.
 */
- (void)chatClient:(TwilioChatClient *)client userSubscribed:(TCHUser *)user{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:userSubscribed" body: @{@"user": [RCTConvert user:user]}];
}

/** Called when the client unsubscribes from updates for a given user.
 
 @param client The chat client.
 @param user The object for unsubscribed user.
 */
- (void)chatClient:(TwilioChatClient *)client userUnsubscribed:(TCHUser *)user{
    [self.bridge.eventDispatcher sendAppEventWithName:@"chatClient:userUnsubscribed" body: @{@"user": [RCTConvert user:user]}];
}


#pragma mark Enums

// - (NSDictionary *)description
// {
//     return @{ @"Constants": @{@"TCHClientSynchronizationStatus": @{@"Started" : @(TCHClientSynchronizationStatusStarted),
//                                                                    @"ChannelsListCompleted" : @(TCHClientSynchronizationStatusChannelsListCompleted),
//                                                                    @"Completed" : @(TCHClientSynchronizationStatusCompleted),
//                                                                    @"Failed" : @(TCHClientSynchronizationStatusFailed)},
//                               @"TCHChannelSynchronizationStatus": @{@"None" : @(TCHChannelSynchronizationStatusNone),
//                                                                     @"Identifier" : @(TCHChannelSynchronizationStatusIdentifier),
//                                                                     @"Metadata" : @(TCHChannelSynchronizationStatusMetadata),
//                                                                     @"All" : @(TCHChannelSynchronizationStatusAll),
//                                                                     @"Failed" : @(TCHChannelSynchronizationStatusFailed)},
//                               @"TCHChannelStatus": @{@"Invited": @(TCHChannelStatusInvited),
//                                                      @"Joined": @(TCHChannelStatusJoined),
//                                                      @"NotParticipating": @(TCHChannelStatusNotParticipating)},
//                               @"TCHChannelType": @{@"Public": @(TCHChannelTypePublic),@"Private": @(TCHChannelTypePrivate)},
//                               @"TCHUserUpdate": @{@"FriendlyName": @(TCHUserUpdateFriendlyName),
//                                                   @"Attributes": @(TCHUserUpdateAttributes),
//                                                   @"ReachabilityOnline": @(TCHUserUpdateReachabilityOnline),
//                                                   @"ReachabilityNotifiable": @(TCHUserUpdateReachabilityNotifiable)},
//                               @"TCHLogLevel": @{@"Fatal" : @(TCHLogLevelFatal),
//                                                 @"Critical" : @(TCHLogLevelCritical),
//                                                 @"Warning" : @(TCHLogLevelWarning),
//                                                 @"Info" : @(TCHLogLevelInfo),
//                                                 @"Debug" : @(TCHLogLevelDebug)},
//                               @"TCHChannelOption": @{@"Type" : TCHChannelOptionType,
//                                                      @"Attributes" : TCHChannelOptionAttributes,
//                                                      @"UniqueName" : TCHChannelOptionUniqueName,
//                                                      @"FriendlyName" : TCHChannelOptionFriendlyName},
//                               @"TCHClientConnectionState": @{@"Unknown" : @(TCHClientConnectionStateUnknown),
//                                                              @"Disconnected" : @(TCHClientConnectionStateDisconnected),
//                                                              @"Connected" : @(TCHClientConnectionStateConnected),
//                                                              @"Connecting" : @(TCHClientConnectionStateConnecting),
//                                                              @"Denied" : @(TCHClientConnectionStateDenied),
//                                                              @"Error" : @(TCHClientConnectionStateError)}
//                               }
//               };
// };
@end
