//
//  Bubbles.m
//  rainbow
//
//  Created by Doha Tubaileh on 9/26/19.
//  Copyright © 2019 Facebook. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import "Bubbles.h"
#import <React/RCTLog.h>
#import "Rainbow/Rainbow.h"
#import "RNRainbowRn.h"
#import "IEventEmmiter.h"
#import "Room+DictRepresentation.h"
#import "Conversation+DictRepresentation.h"
#import "Contact+DictRepresentation.h"
#import "BuubleEventsNames.h"
#import <Photos/Photos.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "NSData+Base64String.h"
#import <Rainbow/Room.h>
#import <Rainbow/Participant.h>
#import "Conversations.h"
#import "ConferenceEventsNames.h"
#import "RTCCall+DictRepresentation.h"

#define ADD_PARTICIPANT_ERROR @"ADD_PARTICIPANT_ERROR"
#define CREATE_OPEN_INVITE_ID_FAILURE @"CREATE_OPEN_INVITE_ID_FAILURE"
#define SUCCESS @"success"
#define FAILURE @"failure"

@interface Bubbles ()
@property (nonatomic,strong) RoomsService *roomService;
@property (nonatomic,strong) ContactsManagerService *contactsManagerService;
@property (nonatomic,strong) ConferencesManagerService *conferenceService;
@property (strong, nonatomic) RTCService *rtcService;
@property (nonatomic,strong) NSData *selectedImageData;
@property (strong, nonatomic) IEventEmmiter *eventEmitter;
@property (nonatomic, weak) RCTBridge *bridge;

@end

@implementation Bubbles

RCT_EXPORT_MODULE();
- (instancetype)init {
    self = [super init];
    if (self) {
        [self registerRoomEvents];
        _roomService = [ServicesManager sharedInstance].roomsService;
        _contactsManagerService = [ServicesManager sharedInstance].contactsManagerService;
        _conferenceService = [ServicesManager sharedInstance].conferencesManagerService;
        _rtcService = [ServicesManager sharedInstance].rtcService;
    }
    return self;
}

- (void)dealloc {
    _roomService = nil;
    _contactsManagerService = nil;
    [self unRegisterRoomEvents];
}

#pragma mark - React-Native Method

RCT_EXPORT_METHOD(getBubbles) {
    NSLog(@"[Bubbles] getBubbles");
    [self sendBubbleArray];
}


RCT_EXPORT_METHOD(getInvitedBubbles) {
    NSMutableArray *invitedBubbles = [[NSMutableArray alloc] init];
    NSArray *rooms = _roomService.rooms.copy;
    for (Room *room in rooms) {
        if (room.myStatusInRoom == ParticipantStatusInvited && (room != nil || room.jid != nil)) {
            [invitedBubbles addObject:room.roomDictionaryRepresentation];
        }
    }
    NSLog(@"[Bubbles] get My Bubbles size: %lu",(unsigned long)invitedBubbles.count);
    [self sendEventWithName:GetInvitedBubbles eventBody:invitedBubbles];
}

RCT_EXPORT_METHOD(getBubbleById: (NSString*)bubbleId) {
    NSLog(@"%@ with bubbleId:%@", NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        [self sendEventWithName:GetBubbleByIdResult eventBody: foundedRoom.roomDictionaryRepresentation];
    }
    else {
        NSLog(@"%@ Error: Could NOT find the Bubble with jid: %@", NSStringFromSelector(_cmd),bubbleId);
        [self sendEventWithName:GetBubbleByIdResult eventBody: NSNull.null];
    }
}

#pragma mark - Bubble Participant React-Native methods

RCT_EXPORT_METHOD(getBubbleOrganizer: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    NSMutableArray<NSDictionary*> *organizers = [[NSMutableArray alloc] init];
    NSString *ownerJId = foundedRoom.creator.jid;
    for (Participant *participant in foundedRoom.participants) {
        if (participant.privilege == ParticipantPrivilegeOwner || participant.privilege == ParticipantPrivilegeModerator) {
            NSDictionary *contactDic = participant.contact.contactDictionaryRepresentation;
            [organizers addObject:contactDic];
        }
    }
    NSLog(@"getBubbleOrganizer with count %lu with ownerJid: %@",(unsigned long)organizers.count, ownerJId);
    [self sendEventWithName:GetBubbleOrganizer eventBody:@{@"organizers":organizers,@"ownerJId":ownerJId}];
}

RCT_EXPORT_METHOD(getBubbleParticipants: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        NSArray *roomParticipants = foundedRoom.participants;
        NSMutableArray<NSDictionary*> *processedRoomMembers = [[NSMutableArray alloc] init];
        for (Participant *participant in roomParticipants) {
            NSDictionary *contactDitionary = participant.contact.contactDictionaryRepresentation;
            [processedRoomMembers addObject: contactDitionary];
        }
        [self sendEventWithName:GetBubbleParticipants eventBody:processedRoomMembers];
    }
    else {
        NSLog(@"[Bubbles] %@, Cant find room with jid: %@",NSStringFromSelector(_cmd),bubbleId);
    }
}

RCT_EXPORT_METHOD(giveOwnerShip: (NSString*)bubbleId contactJid:(NSString*)contactJid) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.contact.jid == %@",contactJid];
    if (foundedRoom) {
        Participant *participant = [foundedRoom.participants filteredArrayUsingPredicate:predicate].firstObject;
        [_roomService changeOwnerCheck:foundedRoom withParticipant:participant completionBlock:^(NSError * _Nullable error, BOOL bubblePromoteMemberIssue, BOOL webRTCConferenceAllowedIssue, BOOL bubblePSTNDialInIssue, BOOL bubbleParticipantCountIssue, int maxParticipantsCount) {
            if (error == nil) {
                NSLog(@"[Bubbles] %@, SUCCESS",NSStringFromSelector(_cmd));
                [self-> _roomService changeOwner:foundedRoom withParticipant:participant completionHandler:^(NSError * _Nullable error) {
                    if (error == nil) {
                        NSLog(@"[Bubbles] %@, changeOwner: SUCCESS",NSStringFromSelector(_cmd));

                    }
                    else{
                        NSLog(@"[Bubbles] %@, Failed with ERROR: %@",NSStringFromSelector(_cmd),error);

                    }
                }];
            }
            else {
                NSLog(@"[Bubbles] %@, Failed with ERROR: %@",NSStringFromSelector(_cmd),error);
                [self sendEventWithName:PromoteBubbleOwnerResult eventBody: @"failure"];
            }
        }];
     
    }
    else {
        NSLog(@"[Bubbles] %@, Cant find room with jid: %@",NSStringFromSelector(_cmd),bubbleId);
    }
}

RCT_EXPORT_METHOD(promoteContactFromBubble:(NSString*)bubbleId _:(NSString*)contactJID) {
    NSLog(@"%@ with bubbleId:%@", NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.contact.jid == %@",contactJID];
    if (foundedRoom) {
        Participant *participant = [foundedRoom.participants filteredArrayUsingPredicate:predicate].firstObject;
        [_roomService updateParticipant:participant withPrivilege:ParticipantPrivilegeModerator inRoom:foundedRoom withCompletionBlock:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"%@ ERROR: %@", NSStringFromSelector(_cmd),error);
            }
        }];
    }
}

RCT_EXPORT_METHOD(demoteContactFromBubble:(NSString*)bubbleId _:(NSString*)contactJID) {
    NSLog(@"%@ with bubbleId:%@", NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.contact.jid == %@",contactJID];
    if (foundedRoom) {
        Participant *participant = [foundedRoom.participants filteredArrayUsingPredicate:predicate].firstObject;
        [_roomService updateParticipant:participant withPrivilege:ParticipantPrivilegeUser inRoom:foundedRoom withCompletionBlock:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"%@ ERROR: %@", NSStringFromSelector(_cmd),error);
            }
        }];
    }
}

RCT_EXPORT_METHOD(removeContactFromBubble:(NSString*)bubbleId _:(NSString*)contactJID) {
    NSLog(@"%@ with bubbleId:%@", NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        Participant *removedParticipant = [self findRoomParticipantByContactJid:contactJID room:foundedRoom];
        if (removedParticipant) {
            [_roomService deleteParticipant:removedParticipant fromRoom:foundedRoom completionHandler:^(NSError * _Nullable error) {
                if(error){
                    NSLog(@"Failed to deleteParticipant with jid");

                }
            }];
        }
        else {
            NSLog(@"%@: ERROR: Cant remove participant with jid: %@, this participant do do NOT exsit", NSStringFromSelector(_cmd),bubbleId);
        }
    }
    else {
        NSLog(@"%@: Failed to find bubble with jid:%@", NSStringFromSelector(_cmd),bubbleId);
    }
}

RCT_EXPORT_METHOD(hangupParticipantFromConference: (NSString*)bubbleId contactJid:(NSString*)contactJid ) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom.conference) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.contact.jid == %@",contactJid];
        ConferenceParticipant *contactParticipant = [foundedRoom.conference.participants filteredArrayUsingPredicate:predicate].firstObject;
        // Check if the user is not my user
        if ( contactParticipant.getContact != [ServicesManager sharedInstance].myUser.contact) {
            [_conferenceService hangup:contactParticipant room:foundedRoom completionHandler:^(NSError * _Nullable error) {
                if (error){
                    NSLog(@"%@ disconnectParticipant ERROR: %@",NSStringFromSelector(_cmd),error);
                    [self sendEventWithName:HangUpParticipantFailed eventBody:error.localizedDescription];
                }
                else {
                    NSLog(@"%@ disconnectParticipant SUCCESS",NSStringFromSelector(_cmd));
                }
            }];
        }
        else {// Hangup my user from the active conference
            if([ServicesManager sharedInstance].rtcService.calls.count>0){
                RTCCall *call = [[ServicesManager sharedInstance].rtcService.calls firstObject];
                [[ServicesManager sharedInstance].rtcService hangupCall:call];
            }
        }
    }
    else{
        NSLog(@"%@ ERROR: Could not find bubble with jid: %@",NSStringFromSelector(_cmd),bubbleId);
    }
}

RCT_EXPORT_METHOD(getBubbleMembers: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    NSMutableArray<NSDictionary*> *members = [[NSMutableArray alloc] init];
    for (Participant *participant in foundedRoom.participants) {
        if (participant.privilege == ParticipantPrivilegeUser || participant.privilege == ParticipantPrivilegeGuest) {
            NSDictionary *contactDic = participant.contact.contactDictionaryRepresentation;
            [members addObject:contactDic];
        }
    }
    [self sendEventWithName:GetBubbleMembers eventBody:members];
}

RCT_EXPORT_METHOD(addParticipants: (NSString*)bubbleId participantIds:(NSArray<NSString*>*)participantIds) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    for (NSString *jid in participantIds) {
        [_contactsManagerService searchRainbowContactWithJid:jid withCompletionHandler:^(Contact *foundContact) {
            [self->_roomService inviteContact:foundContact inRoom:foundedRoom completionHandler:^(NSError * _Nullable error) {
                if(error)
                NSLog(@"addParticipants failed %@", error);

            }];
        }];
    }
}

RCT_EXPORT_METHOD(inviteParticipantToBubble: (NSString*)roomId contactJid:(NSString*)contactJid asModerator:(BOOL)asModerator withInvitation:(BOOL)withInvitation) {
    NSLog(@"%@",NSStringFromSelector(_cmd));
    Room *foundedRoom = [_roomService getRoomByRainbowID:roomId];
    if(foundedRoom){
        if (withInvitation == NO) {
            [_roomService updateRoom:foundedRoom withAutoRegister:YES withCompletionBlock:nil];
        }
        [_contactsManagerService searchRainbowContactWithJid:contactJid withCompletionHandler:^(Contact *foundContact) {
            [self->_roomService inviteContact:foundContact inRoom:foundedRoom completionHandler:^(NSError * _Nullable error) {
                if(error)   NSLog(@"%@ inviteParticipantToBubble ERROR: %@", NSStringFromSelector(_cmd),error);
            }];

            if (asModerator) {
                NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.contact.jid == %@",contactJid];
                Participant *participant = [foundedRoom.participants filteredArrayUsingPredicate:predicate].firstObject;
                [self->_roomService updateParticipant:participant withPrivilege:ParticipantPrivilegeModerator inRoom:foundedRoom withCompletionBlock:^(NSError * _Nullable error) {
                    if (error) {
                        NSLog(@"%@ ERROR: %@", NSStringFromSelector(_cmd),error);
                    }
                }];
            }
        }];
    }
}

RCT_EXPORT_METHOD(leaveBubble: (NSString*)bubbleId) {
    // make a leave without archive
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        Contact *myUser = [ServicesManager sharedInstance].myUser.contact;
        [_roomService deleteParticipant:[foundedRoom participantFromContact:myUser] fromRoom:foundedRoom];
    }
    else{
        NSLog(@"%@ ERROR: Cant find room with jid: %@", NSStringFromSelector(_cmd),bubbleId);
    }
}

RCT_EXPORT_METHOD(archiveBubble: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        [_roomService archiveRoom:foundedRoom];
    }
    else {
        NSLog(@"%@ ERROR: Cant find room with jid: %@", NSStringFromSelector(_cmd),bubbleId);
    }
}

RCT_EXPORT_METHOD(checkIfBubbleHasActiveConference: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if(foundedRoom){
        BOOL hasActiveConference = foundedRoom.hasActiveWebRTCConference && foundedRoom.conference;
        NSLog(@"[Bubbles] %@: %d",NSStringFromSelector(_cmd),hasActiveConference);
        NSMutableDictionary *eventData = [[NSMutableDictionary alloc] init];
        [eventData setValue:bubbleId forKey:@"bubbleId"];
        [eventData setValue:[NSNumber numberWithBool:hasActiveConference] forKey:@"hasActiveConference"];
        [eventData setValue:foundedRoom.roomDictionaryRepresentation forKey:@"bubble"];
        Conversation *conversation = [self findConversationByJid:bubbleId];
        if (conversation) {
            [eventData setValue:conversation.conversationDictionaryRepresentation forKey:@"conversation"];
        }
        [self sendEventWithName:BubbleHasActiveConference eventBody:eventData];
    }


}

RCT_EXPORT_METHOD(deleteBubble: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    [_roomService deleteRoom:foundedRoom withCompletionBlock:^(NSError * _Nullable error) {
        if (error == nil) {
            [self sendEventWithName:DeleteBubbleResult eventBody:@"Success : bubble deleted successfully"];
        }
        else{
            NSString *errorMsg = [[NSString alloc] initWithFormat:@"Failure: %@", error.localizedDescription];
            [self sendEventWithName:DeleteBubbleResult eventBody:errorMsg];
        }
    }];

}

RCT_EXPORT_METHOD(acceptBubbleInvitation: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    [_roomService acceptInvitation:foundedRoom completionBlock:^(NSError * _Nullable error) {
        if(error){
            NSLog(@"[acceptBubbleInvitation] ERROR: %@",error);
            [self sendEventWithName:InvitedBubblesResult eventBody:FAILURE];
        } else {
            [self sendEventWithName:InvitedBubblesResult eventBody:SUCCESS];
            
        }
    }];
}

RCT_EXPORT_METHOD(declineBubbleInvitation: (NSString*)bubbleId) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    [_roomService declineInvitation:foundedRoom completionBlock:^(NSError * _Nullable error) {
        if(error){
            NSLog(@"[acceptBubbleInvitation] ERROR: %@",error);
            [self sendEventWithName:InvitedBubblesResult eventBody:FAILURE];
        } else {
            [self sendEventWithName:InvitedBubblesResult eventBody:SUCCESS];
        }
    }];
}

RCT_EXPORT_METHOD(sendBubbleChatByEmail: (NSString*)bubbleId ) {
    NSLog(@"[Conversations] sendConversationByEmail with Jid: %@",bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        Conversation *foundedConversation = [Conversations findConversationWithJId: foundedRoom.jid];
        if (foundedConversation) {
            [ServicesManager.sharedInstance.conversationsManagerService sendConversationByMail:foundedConversation completionHandler:^(NSError * _Nullable error) {
                NSLog(@"[Conversations] sendConversationByEmail filed: %@",error);

            }];
        }
    }
   
}

RCT_EXPORT_METHOD(updateBubbleInfo:(NSString*)bubbleId name:(NSString*)name topic:(NSString*)topic ) {
    NSLog(@"[%@] %@",[self class],NSStringFromSelector(_cmd));
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        [_roomService updateRoom:foundedRoom name:name topic:topic block:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"[%@] %@ Failed with ERROR: %@",[self class],NSStringFromSelector(_cmd), error.description);
                [self sendEventWithName:UpdateBubbleResult eventBody:NSNull.null];
            }
            else {
                [self sendEventWithName:UpdateBubbleResult eventBody:SUCCESS];
            }
        }];
    }
    else {
        NSLog(@"[%@] %@ Failed: Cannot find bubble with id: %@",[self class],NSStringFromSelector(_cmd), bubbleId);
    }
   
}

RCT_EXPORT_METHOD(getActiveConferenceForBubble:(NSString*)bubbleId ) {
    NSLog(@"[Bubbles] getActiveConferenceForBubble: with room jid: %@",bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.peer.jid == %@",foundedRoom.jid];
        RTCCall *bubbleConferenceCall = [_rtcService.calls filteredArrayUsingPredicate:predicate].firstObject;
        if (bubbleConferenceCall) {
            [self sendEventWithName:GetActiveConferenceForBubbleResult eventBody:bubbleConferenceCall.conferenceCallToDictionaryRepresentation];
        }
    }
    else {
        NSLog(@"[Bubbles] getActiveConferenceForBubble: ERROR: could not found room with jid: %@",bubbleId);
    }
}

RCT_EXPORT_METHOD(createBubble:(NSString*)name description:(NSString*)description ) {
    NSLog(@"[Bubbles] createBubble withName: %@ and description: %@",name,description);

    Room *createdRoom = [_roomService createRoom:name withTopic:description];

    if (createdRoom == nil) {
        //This means there was an error with room creation
        NSLog(@"[Bubbles] createBubble failed with create room %@",name);
        [self sendEventWithName:CreateBubbleResult eventBody:CREATE_OPEN_INVITE_ID_FAILURE];
    }
    else {
        [self getBubbles];
        [self sendEventWithName:CreateBubbleResult eventBody:nil];
    }

}

RCT_EXPORT_METHOD(createBubbleWithParticipants:(NSString*)name description:(NSString*)description participantIds:(NSArray<NSString*>*)participantIds) {

    NSLog(@"[Bubbles] createBubbleWithParticipants withName: %@, description: %@ and participants count: %lu",name,description,(unsigned long)participantIds.count);

    Room *createdRoom =  [_roomService createRoom:name withTopic:description];

    if (createdRoom == nil) {
        //This means there was an error with room creation
        NSLog(@"[Bubbles] createBubbleWithParticipants failed with create room %@",name);
        [self sendEventWithName:CreateBubbleResult eventBody:CREATE_OPEN_INVITE_ID_FAILURE];
        return;
    }

    for (NSString *jid in participantIds) {
        [_contactsManagerService searchRainbowContactWithJid:jid withCompletionHandler:^(Contact *foundContact) {
            [self->_roomService inviteContact:foundContact inRoom:createdRoom error:nil];
        }];
    }
    //TODO: make sure how to add participant succes
    [self sendEventWithName:AddParticipantResult eventBody:nil];

}

RCT_EXPORT_METHOD(uploadNewPhoto) {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self uploadNewPhoto];
        });
        return;
    }

    NSLog(@"[Messages] atttachFile");
    if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad )
    {
        return; /* Device is iPad */
    }
    __weak __typeof__(self) weakSelf = self;
    UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
    UIAlertAction *uploadImageFromLibraryAction = [UIAlertAction actionWithTitle: @"Gallery" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
        [weakSelf didTapUploadImageAction:action];
    }];

    [actionSheet addAction:uploadImageFromLibraryAction];

    UIAlertAction* uploadiCloudAction = [UIAlertAction actionWithTitle: @"iCloud" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
        [weakSelf didTapUploadiCloudAction:action];
    }];
    [actionSheet addAction:uploadiCloudAction];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
    [actionSheet addAction:cancelAction];
    [[[RNRainbowRn sharedManager] navigationController] presentViewController:actionSheet animated:YES completion:nil];
}

RCT_EXPORT_METHOD(updateBubblePhoto:(NSString*)bubbleId ) {
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    [_roomService updateAvatar:foundedRoom withPhotoData:self.selectedImageData withCompletionBlock:^(NSError * _Nullable error) {
        if (error != nil) {
            NSLog(@"updateBubblePhoto ERROR: %@",error);
            [self sendEventWithName:UpdateBubbleImageResult eventBody:FAILURE];
        }
        else {
            [self sendEventWithName:UpdateBubbleImageResult eventBody:SUCCESS];
        }
    }];
}

RCT_EXPORT_METHOD(getPendingBubbleInvitation) {
    [self sendEventWithName:BubblePendingInvitationsCounter eventBody:[NSNumber numberWithInteger:_roomService.numberOfPendingRoomInvitations]];
}

#pragma mark - Bubble Custom Date Methodes

RCT_EXPORT_METHOD(updateCustomDataForBubble: (NSString*)bubbleId jsonObj:(NSDictionary*)obj) {
    NSLog(@"[Bubbles] %@ with roomID: %@",NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        [_roomService updateRoom:foundedRoom withCustomData:obj completionBlock:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"[Bubbles] %@ ERROR: %@",NSStringFromSelector(_cmd),error);
            }
            else {
                NSLog(@"[Bubbles] %@ SUCCESS ",NSStringFromSelector(_cmd));
            }
        }];
    }
    else {
        NSLog(@"[Bubbles] %@ ERROR:%@",NSStringFromSelector(_cmd),@"Could not find room with id");
    }
}

RCT_EXPORT_METHOD(deleteCustomDataForBubble: (NSString*)bubbleId ) {
    NSLog(@"[Bubbles] %@ with roomID: %@",NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        [_roomService updateRoom:foundedRoom withCustomData:@{} completionBlock:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"[Bubbles] %@ ERROR: %@",NSStringFromSelector(_cmd),error);
            }
            else {
                NSLog(@"[Bubbles] %@ SUCCESS ",NSStringFromSelector(_cmd));
            }
        }];
    }
    else {
        NSLog(@"[Bubbles] %@ ERROR:%@",NSStringFromSelector(_cmd),@"Could not find room with id");
    }
}

RCT_EXPORT_METHOD(getCustomDataFromBubble: (NSString*)bubbleId ket:(NSString*)key) {
    NSLog(@"[Bubbles] %@ with roomID: %@",NSStringFromSelector(_cmd),bubbleId);
    Room *foundedRoom = [_roomService getRoomByRainbowID:bubbleId];
    if (foundedRoom) {
        NSDictionary *customData = foundedRoom.customData;
        if (customData != nil && [customData objectForKey:key]) {
            id value = [customData objectForKey:key];
            NSLog(@"[Bubbles] %@ with Value: %@",NSStringFromSelector(_cmd),value);
            [self sendEventWithName:GetBubbleCustomData eventBody:value];
        }
    }
    else {
        NSLog(@"[Bubbles] %@ ERROR:%@",NSStringFromSelector(_cmd),@"Could not find room with id");
    }
}

#pragma mark - Auto Accept Mathodes

RCT_EXPORT_METHOD(createAutoAcceptedBubble: (NSString*)name description:(NSString*)description) {
    NSLog(@"[Bubbles] %@ with name: %@",NSStringFromSelector(_cmd),name);
    Room *createdRoom = [_roomService createAutoAcceptedRoom:name withTopic:description error:nil];
    if (createdRoom == nil) {
        //This means there was an error with room creation
        NSLog(@"[Bubbles] %@ failed with create room with name: %@",NSStringFromSelector(_cmd),name);
        [self sendEventWithName:CreateBubbleResult eventBody:CREATE_OPEN_INVITE_ID_FAILURE];
    }
    else {
        [self sendEventWithName:CreateBubbleResult eventBody: nil];
    }
}

#pragma mark - Room Events
- (void) didUpdateRoom:(NSNotification *) notification {
    NSLog(@"[Bubbles] didUpdateRoom");
    NSDictionary *obj = (NSDictionary *)notification.object;
    Room *updatedRoom = (Room*)[obj objectForKey:kRoomKey];

    // trigger the updated bubble
    [self sendEventWithName:OnBubbleUpdated eventBody:updatedRoom.roomDictionaryRepresentation];

    [self sendBubbleArray];
    if (updatedRoom.myStatusInRoom == ParticipantStatusInvited) {
        [self getPendingBubbleInvitation];
        [self getInvitedBubbles];
    }
    if ( [[obj[kRoomChangedAttributesKey] firstObject] isEqual:@"users"] || [[obj[kRoomChangedAttributesKey] firstObject] isEqual:@"creator"]) {
        //Room participants update
        [self getBubbleOrganizer:updatedRoom.jid];
        [self getBubbleMembers:updatedRoom.jid];
        //changes on the participant
    }
}

- (void) didRemoveRoom:(NSNotification *) notification {
    NSLog(@"[Bubbles] didRemoveRoom");
    [self sendBubbleArray];
}

- (void) didInviteRoom:(NSNotification *) notification {
    NSLog(@"[Bubbles] didInviteRoom");
    [self getInvitedBubbles];
}

- (void) didFinishLoadingPendingInvitations:(NSNotification *) notification {
    NSLog(@"[Bubbles] didFinishLoadingPendingInvitations");
    if ([notification.name isEqual:kRoomsServiceDidFinishLoadingPendingInvitations]) {
        [self sendBubbleArray];
    }
    [self getPendingBubbleInvitation];
}



#pragma mark - Register Events
- (void) registerRoomEvents{
    //Listen to for each contact changes
    NSLog(@"[Bubbles] registerRoomEvents");
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRoom:) name:kRoomsServiceDidUpdateRoom object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRemoveRoom:) name:kRoomsServiceDidRemoveRoom object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInviteRoom:) name:kRoomsServiceDidReceiveRoomInvitation object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInviteRoom:) name:kRoomsServiceDidAddRoom object:nil];//
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInviteRoom:) name:kRoomContainersManagerDidAddRoomContainer object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didFinishLoadingPendingInvitations:) name:kRoomsServiceDidFinishLoadingPendingInvitations object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didFinishLoadingPendingInvitations:) name:kRoomsServiceDidReceiveRoomInvitation object:nil];
}

-(void) unRegisterRoomEvents{
    NSLog(@"[Bubbles] unRegisterRoomEvents");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kRoomsServiceDidUpdateRoom object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kRoomsServiceDidRemoveRoom object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kRoomsServiceDidFinishLoadingPendingInvitations object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kRoomsServiceDidReceiveRoomInvitation object:nil];
}

- (void)didTapUploadImageAction:(UIAlertAction *)sender {
    [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if(status == PHAuthorizationStatusAuthorized) {
                // Already authorized
                UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
                imagePickerController.delegate = self;
                imagePickerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
                if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
                    imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
                else
                    imagePickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;

                imagePickerController.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie, (NSString *)kUTTypeImage, nil];

                [[[RNRainbowRn sharedManager] navigationController] presentViewController:imagePickerController animated:YES completion:nil];
            } else {
                [self showAllowAccessPhotoAndCameraPopup];
            }
        });
    }];
}

- (void)didTapUploadiCloudAction:(UIAlertAction *)sender {
    dispatch_async(dispatch_get_main_queue(), ^{
        UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.data"] inMode:UIDocumentPickerModeImport];
        documentPicker.delegate = self;
        documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
        [[[RNRainbowRn sharedManager] navigationController] presentViewController:documentPicker animated:YES completion:nil];
    });
}

-(void) showAllowAccessPhotoAndCameraPopup {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction* gotoSettingsAction = [UIAlertAction actionWithTitle:@"Go to Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
    }];
    [alert addAction:gotoSettingsAction];
    // show the alert.
    [[[RNRainbowRn sharedManager] navigationController] presentViewController:alert animated:YES completion:nil];
}

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    NSURL *assetURL;
    if([info objectForKey:UIImagePickerControllerReferenceURL])
        assetURL = info[UIImagePickerControllerReferenceURL];
    else if([info objectForKey:UIImagePickerControllerMediaURL])
        assetURL = info[UIImagePickerControllerMediaURL];
    PHFetchResult<PHAsset *> *asset = nil;
    __block NSData *dataToSend = nil;
    __block NSURL *assetUrl = nil;
    if(assetURL){
        asset = [PHAsset fetchAssetsWithALAssetURLs:@[assetURL] options:nil];
        if(asset.count > 0){
            NSURL *cacheURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@",
                                                      [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject], [[NSUUID UUID] UUIDString]]];
            dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
            [self downloadAsset:asset.firstObject toURL:cacheURL completion:^(NSURL *url, NSData *data) {
                assetUrl = url;
                dataToSend = data;
                dispatch_semaphore_signal(semaphore);
            }];
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            self.selectedImageData = dataToSend;
            [self sendEventWithName:NewBubbleImage eventBody:cacheURL.path];
        }
    }
    [picker dismissViewControllerAnimated:YES completion:nil];
}

- (void)downloadAsset:(PHAsset *)asset toURL:(NSURL *)url completion:(void(^)(NSURL *assetVideoUrl, NSData *data))completion {
    if (asset.mediaType == PHAssetMediaTypeImage) {
        PHImageRequestOptions *options = [PHImageRequestOptions new];
        options.networkAccessAllowed = YES;
        options.version = PHImageRequestOptionsVersionCurrent;
        options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
        options.synchronous = YES;
        [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
            // Convert HEIC to JPEG for not iOS device compatibility
            if( [[url pathExtension] isEqualToString:@"HEIC"] || [[url pathExtension] isEqualToString:@"HEIF"]) {
                UIImage *im = [UIImage imageWithData:imageData];
                imageData = UIImageJPEGRepresentation(im, 0.9);
            }
            if ([info objectForKey:PHImageErrorKey] == nil && [[NSFileManager defaultManager] createFileAtPath:url.path contents:imageData attributes:nil]) {
                NSLog(@"downloaded photo:%@", url.path);
            }
            completion(nil, imageData);
        }];
    } else if (asset.mediaType == PHAssetMediaTypeVideo) {
        [[NSFileManager defaultManager] removeItemAtPath:url.path error:nil];
        PHVideoRequestOptions *options = [PHVideoRequestOptions new];
        options.networkAccessAllowed = YES;
        options.version = PHVideoRequestOptionsVersionCurrent;
        options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
        [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
            NSURL *videoUrl = [(AVURLAsset *)asset URL];
            NSData *videoData = [NSData dataWithContentsOfURL:videoUrl options:0 error:nil];
            [videoData writeToFile:url.path atomically:YES];
            completion(videoUrl, videoData);
        }];
    }
}

#pragma mark - Others

- (Conversation *) findConversationByJid :(NSString *)jid {
    NSArray<Conversation*>* conversationArray = [ServicesManager sharedInstance].conversationsManagerService.conversations;
    for (Conversation *item in conversationArray) {
        if ([item.peer.jid isEqual:jid]) {
            return item;
        }
    }
    return nil;
}

- (void) sendBubbleArray {
    NSMutableArray *reactBubbles = [[NSMutableArray alloc] init];
    NSArray *rooms = _roomService.rooms.copy;
    for (Room *room in rooms) {
        if (room == nil || room.jid == nil ) {continue;}
        if (  room.myStatusInRoom == ParticipantStatusAccepted ) {
            [reactBubbles addObject:room.roomDictionaryRepresentation];
        }
    }
    NSLog(@"[Bubbles].sendBubbleArray send all Bubbles with size: %lu",(unsigned long)_roomService.rooms.count);
    [self sendEventWithName:BubblesUpdated eventBody:reactBubbles];
}

-(Participant*) findRoomParticipantByContactJid: (NSString*)jid room:(Room*)room{
    for (Participant *participant in room.participants) {
        RainbowContact *rainbowContact = (RainbowContact*)participant.contact;
        if ([rainbowContact.jid isEqual:jid]) {
            return participant;
        }
    }
    return nil;
}
- ( IEventEmmiter *)eventEmitter {
    if (!_eventEmitter) {
        _eventEmitter = [self.bridge moduleForClass:[IEventEmmiter class]];
    }
    return _eventEmitter;
}

- (void)sendEventWithName:(NSString *)eventName eventBody:(id)eventBody {
    IEventEmmiter *eventEmitter = [self eventEmitter];
    if (eventEmitter) {
        NSLog(@" [Bubbles] sendEventWithName: %@", eventName);
        [eventEmitter sendEvent:eventName eventBody:eventBody];
    } else {
        NSLog(@"[Bubbles] sendEventWithName:  Bridge is not ready. Cannot send event: %@", eventName);
    }
}
+ (BOOL)requiresMainQueueSetup
{
    return NO;
}
@end
