//
//  TAPChatManager.m
//  TapTalk
//
//  Created by Dominic Vedericho on 15/08/18.
//  Copyright ยฉ 2018 Moselo. All rights reserved.
//

#import "TAPChatManager.h"
#import "TAPConnectionManager.h"
#import <TapTalk/Base64.h>

#define kCharacterLimit 1000
#define kMaximumRetryAttempt 10
#define kDelayTime 60.0f

@interface TAPChatManager () <TAPConnectionManagerDelegate>

- (void)sendMessage:(TAPMessageModel *)message notifyDelegate:(BOOL)notifyDelegate;
- (void)checkAndSendPendingMessage;
- (void)checkPendingBackgroundTask;
- (void)receiveMessageFromSocketWithEvent:(NSString *)eventName dataDictionary:(NSDictionary *)dataDictionary;
- (void)receiveOnlineStatusFromSocketWithDataDictionary:(NSDictionary *)dataDictionary;
- (void)receiveOfflineStatusFromSocketWithDataDictionary:(NSDictionary *)dataDictionary;
- (void)receiveContactUpdatedFromSocketWithDataDictionary:(NSDictionary *)dataDictionary;
- (void)receiveStartTypingFromSocketWithDataDictionary:(NSDictionary *)dataDictionary;
- (void)receiveStopTypingFromSocketWithDataDictionary:(NSDictionary *)dataDictionary;
- (void)stopTimerSaveNewMessage;
- (void)runSendMessageSequenceWithMessage:(TAPMessageModel *)message;
- (void)processMessageAsDelivered:(TAPMessageModel *)message;
- (void)setIsWaitingTypingNo;

@property (strong, nonatomic) NSMutableArray *delegatesArray;
@property (strong, nonatomic) NSMutableArray *pendingMessageArray;
@property (strong, nonatomic) NSMutableArray *incomingMessageArray;
@property (strong, nonatomic) NSMutableDictionary *waitingResponseDictionary;
@property (strong, nonatomic) NSMutableDictionary *waitingUploadDictionary;
@property (strong, nonatomic) NSMutableDictionary *typingDictionary;
@property (strong, nonatomic) NSTimer *saveNewMessageTimer;
@property (strong, nonatomic) __block NSTimer *backgroundSequenceTimer;
@property (nonatomic) NSInteger checkPendingBackgroundTaskRetryAttempt;
@property (nonatomic) BOOL isEnterBackgroundSequenceActive;
@property (nonatomic) BOOL isShouldRefreshOnlineStatus;
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
@property (nonatomic) BOOL isWaitingSendTyping;

@end

@implementation TAPChatManager

#pragma mark - Lifecycle
+ (TAPConnectionManager *)sharedManager {
    static TAPConnectionManager *sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[self alloc] init];
    });
    
    return sharedManager;
}

- (id)init {
    self = [super init];
    
    if (self) {
        //Add delegate to Connection Manager here
        _delegatesArray = [[NSMutableArray alloc] init];
        _pendingMessageArray = [[NSMutableArray alloc] init];
        _incomingMessageArray = [[NSMutableArray alloc] init];
        _waitingResponseDictionary = [[NSMutableDictionary alloc] init];
        _waitingUploadDictionary = [[NSMutableDictionary alloc] init];
        _messageDraftDictionary = [[NSMutableDictionary alloc] init];
        _quotedMessageDictionary = [[NSMutableDictionary alloc] init];
        _quoteActionTypeDictionary = [[NSMutableDictionary alloc] init];
        _userInfoDictionary = [[NSMutableDictionary alloc] init];
        _filePathStoredDictionary = [[NSMutableDictionary alloc] init];
        _activeUser = [TAPDataManager getActiveUser];
        _checkPendingBackgroundTaskRetryAttempt = 0;
        _isEnterBackgroundSequenceActive = NO;
        _isShouldRefreshOnlineStatus = YES;
        _typingDictionary = [[NSMutableDictionary alloc] init];
        [[TAPConnectionManager sharedManager] addDelegate:self];
    }
    
    return self;
}

- (void)dealloc {
    //Remove Connection Manager delegate
    [[TAPConnectionManager sharedManager] removeDelegate:self];
}

#pragma mark - Delegate
#pragma mark TAPConnectionManager
- (void)connectionManagerDidReceiveNewEmit:(NSString *)eventName parameter:(NSDictionary *)dataDictionary {
    if ([eventName isEqualToString:kTAPEventNewMessage]) {
        [self receiveMessageFromSocketWithEvent:eventName dataDictionary:dataDictionary];
    }
    else if ([eventName isEqualToString:kTAPEventUpdateMessage]) {
        [self receiveMessageFromSocketWithEvent:eventName dataDictionary:dataDictionary];
    }
    else if ([eventName isEqualToString:kTAPEventStartTyping]) {
        [self receiveStartTypingFromSocketWithDataDictionary:dataDictionary];
    }
    else if ([eventName isEqualToString:kTAPEventStopTyping]) {
        [self receiveStopTypingFromSocketWithDataDictionary:dataDictionary];
    }
    else if ([eventName isEqualToString:kTAPEventUserOnline]) {
        [self receiveOnlineStatusFromSocketWithDataDictionary:dataDictionary];
    }
    else if ([eventName isEqualToString:kTAPEventUserUpdated]) {
        [self receiveContactUpdatedFromSocketWithDataDictionary:dataDictionary];
    }
}

- (void)connectionManagerDidConnected {
    //Send pending queue array
    [self checkAndSendPendingMessage];
}

- (void)connectionManagerDidReceiveError:(NSError *)error {
    _isTyping = NO;
    _isWaitingSendTyping = NO;
    _isShouldRefreshOnlineStatus = YES;
    _typingDictionary = [NSMutableDictionary dictionary];
}

- (void)connectionManagerDidDisconnectedWithCode:(NSInteger)code reason:(NSString *)reason cleanClose:(BOOL)clean {
    _isTyping = NO;
    _isWaitingSendTyping = NO;
    _isShouldRefreshOnlineStatus = YES;
    _typingDictionary = [NSMutableDictionary dictionary];
}

#pragma mark - Custom Method
- (void)connect {
    [[TAPConnectionManager sharedManager] connect];
}

- (void)disconnect {
    [[TAPConnectionManager sharedManager] disconnect];
}

- (void)openRoom:(TAPRoomModel *)room {
    _activeRoom = room;
}

- (void)closeActiveRoom {
    _activeRoom = nil;
}

- (void)startTyping {
    
    if (!self.isWaitingSendTyping) {
        _isTyping = NO;
    }
    
    if (self.isTyping || self.isWaitingSendTyping) {
        return;
    }
    
    _isTyping = YES;
    
    NSString *roomID = [TAPUtil nullToEmptyString:self.activeRoom.roomID];
    NSDictionary *parameterDictionary = @{@"roomID" : roomID};
    [[TAPConnectionManager sharedManager] sendEmit:kTAPEventStartTyping parameters:parameterDictionary];
    _isWaitingSendTyping = YES;
    [self performSelector:@selector(setIsWaitingTypingNo) withObject:nil afterDelay:10.0f];
}

- (void)stopTyping {
    if(!self.isTyping) {
        return;
    }
    
    _isTyping = NO;
    _isWaitingSendTyping = NO;
    
    NSString *roomID = [TAPUtil nullToEmptyString:self.activeRoom.roomID];
    NSDictionary *parameterDictionary = @{@"roomID" : roomID};
    [[TAPConnectionManager sharedManager] sendEmit:kTAPEventStopTyping parameters:parameterDictionary];
}

- (void)sendMessage:(TAPMessageModel *)message notifyDelegate:(BOOL)notifyDelegate {
//    Check if socket is connected
//    ConnectionManagerStatusTypeDisconnected = 0
//    ConnectionManagerStatusTypeConnecting = 1
//    ConnectionManagerStatusTypeConnected = 2
    
    if (notifyDelegate) {
        [self notifySendMessageToDelegate:message];
    }
    
    [self runSendMessageSequenceWithMessage:message];
}

- (void)notifySendMessageToDelegate:(TAPMessageModel *)message {
    for (id delegate in self.delegatesArray) {
        if ([delegate respondsToSelector:@selector(chatManagerDidSendNewMessage:)]) {
            [delegate chatManagerDidSendNewMessage:[message copyMessageModel]];
        }
    }
}

- (void)runSendMessageSequenceWithMessage:(TAPMessageModel *)message {
    TAPConnectionManagerStatusType statusType = [[TAPConnectionManager sharedManager] getSocketConnectionStatus];
    if (statusType != TAPConnectionManagerStatusTypeConnected) {
        //When socket is not connected
        [self.pendingMessageArray addObject:message];
        return;
    }
    else {
        //When socket is connected
        
        //Add message to waiting response array
        [self.waitingResponseDictionary setObject:message forKey:message.localID];
        
        //Encrypt message
        NSDictionary *encryptedParametersDictionary = [TAPEncryptorManager encryptToDictionaryFromMessageModel:message];

        //Convert CountryID from string to integer (because server only accept countryID as integer)
        NSMutableDictionary *parameterDictionary = [[NSMutableDictionary alloc] init];
        parameterDictionary = [encryptedParametersDictionary mutableCopy];
        NSMutableDictionary *userDictionary = [[parameterDictionary objectForKey:@"user"] mutableCopy];
        
        NSString *countryIDString = [userDictionary valueForKeyPath:@"countryID"];
        NSInteger countryIDInteger = [countryIDString integerValue];
        NSNumber *countryIDNumber = [NSNumber numberWithInteger:countryIDInteger];
        [userDictionary setObject:countryIDNumber forKey:@"countryID"];
        [parameterDictionary setObject:[userDictionary copy] forKey:@"user"];
        
        [[TAPConnectionManager sharedManager] sendEmit:kTAPEventNewMessage parameters:parameterDictionary];
    }
}

- (void)sendEmitFileMessage:(TAPMessageModel *)message {
    [self sendMessage:message notifyDelegate:NO];
    [[TAPChatManager sharedManager] removeQuotedMessageObjectWithRoomID:message.room.roomID];
}

- (void)sendTextMessage:(NSString *)textMessage {
    [[TAPChatManager sharedManager] sendTextMessage:textMessage room:[TAPChatManager sharedManager].activeRoom];
}

- (void)sendProductMessage:(TAPMessageModel *)message {
    [self sendMessage:message notifyDelegate:YES];
}

- (void)sendTextMessage:(NSString *)textMessage room:(TAPRoomModel *)room {
    
    //Check if forward message exist, send forward message
    [self checkAndSendForwardedMessageWithRoom:room];
    
    //Divide message if length more than character limit
    NSInteger characterLimit = kCharacterLimit;
    
    if ([textMessage length] > characterLimit) {
        NSInteger messageLength = [textMessage length];
        
        for (NSInteger startIndex = 0; startIndex < messageLength; startIndex += characterLimit) {
            //Copy current message model
            NSInteger substringLength = messageLength - startIndex;
            if (substringLength > characterLimit) {
                substringLength = characterLimit;
            }
            
            NSString *substringMessage = [textMessage substringWithRange:NSMakeRange(startIndex, substringLength)];
            TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:substringMessage type:TAPChatMessageTypeText];
            
            //Check if quote message available
            id quotedMessageObject = [[TAPChatManager sharedManager].quotedMessageDictionary objectForKey:room.roomID];
            if (quotedMessageObject != nil) {
                if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
                    
                    //if message quoted from message model then should construct quote and reply to model
                    TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
                    quotedMessage = [quotedMessage copy];

                    if (![quotedMessage.quote.imageURL isEqualToString:@""] || ![quotedMessage.quote.fileID isEqualToString:@""]) {
                        message.quote = [quotedMessage.quote copy];
                        message.quote.title = quotedMessage.user.fullname;
                        message.quote.content = quotedMessage.body;
                    }
                    else {
                        TAPQuoteModel *quote = [TAPQuoteModel new];
                        quote.title = quotedMessage.user.fullname;
                        quote.content = quotedMessage.body;
                        message.quote = [quote copy];
                    }
                    
                    TAPReplyToModel *replyTo = [TAPReplyToModel new];
                    replyTo.messageID = quotedMessage.messageID;
                    replyTo.localID = quotedMessage.localID;
                    replyTo.messageType = quotedMessage.type;
                    replyTo.fullname = quotedMessage.user.fullname;
                    replyTo.xcUserID = quotedMessage.user.xcUserID;
                    replyTo.userID = quotedMessage.user.userID;
                    message.replyTo = [replyTo copy];
                }
                else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
                     //if message quoted from quote model then should just construct quote model
                    TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
                    message.quote = [quotedMessage copy];
                }
            }
            
            //check if userInfo is available, if available add to data in message model
            //userInfo custom user information from client, used for custom quote click action
            id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
            if (userInfo != nil) {
                NSMutableDictionary *dataDictionary = message.data;
                if (dataDictionary == nil) {
                    dataDictionary = [[NSMutableDictionary alloc] init];
                }
                
                [dataDictionary setObject:userInfo forKey:@"userInfo"];
                message.data = dataDictionary;
            }
            
            [self sendMessage:message notifyDelegate:YES];
            
            [[TAPChatManager sharedManager] removeQuotedMessageObjectWithRoomID:room.roomID];
        }
    }
    else {
        TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:textMessage type:TAPChatMessageTypeText];
        
        //Check if quote message available
        id quotedMessageObject = [self.quotedMessageDictionary objectForKey:room.roomID];
        if (quotedMessageObject != nil) {
            if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
                //if message quoted from message model then should construct quote and reply to model
                TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
                quotedMessage = [quotedMessage copy];
                
                NSString *quoteImageUrl = [TAPUtil nullToEmptyString:quotedMessage.quote.imageURL];
                NSString *quoteFileID = [TAPUtil nullToEmptyString:quotedMessage.quote.fileID];
                
                if (![quoteImageUrl isEqualToString:@""] || ![quoteFileID isEqualToString:@""]) {
                    message.quote = [quotedMessage.quote copy];
                }
                else {
                    TAPQuoteModel *quote = [TAPQuoteModel new];
                    quote.title = quotedMessage.user.fullname;
                    quote.content = quotedMessage.body;
                    message.quote = [quote copy];
                }
                
                TAPReplyToModel *replyTo = [TAPReplyToModel new];
                replyTo.messageID = quotedMessage.messageID;
                replyTo.localID = quotedMessage.localID;
                replyTo.messageType = quotedMessage.type;
                replyTo.fullname = quotedMessage.user.fullname;
                replyTo.xcUserID = quotedMessage.user.xcUserID;
                replyTo.userID = quotedMessage.user.userID;
                message.replyTo = replyTo;
            }
            else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
                //if message quoted from quote model then should just construct quote model
                TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
                message.quote = [quotedMessage copy];
            }
        }
        
        //check if userInfo is available, if available add to data in message model
        //userInfo custom user information from client, used for custom quote click action
        id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
        if (userInfo != nil) {
            NSMutableDictionary *dataDictionary = message.data;
            if (dataDictionary == nil) {
                dataDictionary = [[NSMutableDictionary alloc] init];
            }
            
            [dataDictionary setObject:userInfo forKey:@"userInfo"];
            message.data = dataDictionary;
        }
        
        [self sendMessage:message notifyDelegate:YES];
        
        [[TAPChatManager sharedManager] removeQuotedMessageObjectWithRoomID:room.roomID];
    }
}

- (void)sendImageMessage:(UIImage *)image caption:(NSString *)caption room:(TAPRoomModel *)room {
    
    //Check if forward message exist, send forward message
    [self checkAndSendForwardedMessageWithRoom:room];
    
    caption = [TAPUtil nullToEmptyString:caption];
    
    NSString *messageBodyCaption = [NSString string];
    //Check contain caption or not
    if ([caption isEqualToString:@""]) {
        messageBodyCaption = NSLocalizedString(@"๐Ÿ–ผ Photo", @"");
    }
    else {
        messageBodyCaption = [NSString stringWithFormat:@"๐Ÿ–ผ %@", caption];
    }
    
    TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:messageBodyCaption type:TAPChatMessageTypeImage];
    
    NSMutableDictionary *dataDictionary = message.data;
    if (dataDictionary == nil) {
        dataDictionary = [[NSMutableDictionary alloc] init];
    }
#ifdef DEBUG
    NSLog(@"IMAGE BEFORE CACHE SIZE HEIGHT: %f, WIDTH: %f", image.size.height, image.size.width);
#endif
    
    NSNumber *imageHeight = [NSNumber numberWithFloat:image.size.height];
    NSNumber *imageWidth = [NSNumber numberWithFloat:image.size.width];
    
    [dataDictionary setObject:imageHeight forKey:@"height"];
    [dataDictionary setObject:imageWidth forKey:@"width"];
    [dataDictionary setObject:caption forKey:@"caption"];
    
    //check if userInfo is available, if available add to data in message model
    //userInfo custom user information from client, used for custom quote click action
    id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
    if (userInfo != nil) {
        [dataDictionary setObject:userInfo forKey:@"userInfo"];
    }
    
    message.data = [dataDictionary copy];
    
    //Check if quote message available
    id quotedMessageObject = [self.quotedMessageDictionary objectForKey:room.roomID];
    if (quotedMessageObject != nil) {
        if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
            //if message quoted from message model then should construct quote and reply to model
            TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
            quotedMessage = [quotedMessage copy];
            
            if ([quotedMessage.quote.fileType isEqualToString:[NSString stringWithFormat: @"%ld", TAPChatMessageTypeFile]]) {
                //TYPE FILE
                message.quote = quotedMessage.quote;
            }
            else if (![quotedMessage.quote.imageURL isEqualToString:@""] || ![quotedMessage.quote.fileID isEqualToString:@""]) {
                message.quote = [quotedMessage.quote copy];
                message.quote.title = quotedMessage.user.fullname;
                message.quote.content = quotedMessage.body;
            }
            else {
                TAPQuoteModel *quote = [TAPQuoteModel new];
                quote.title = quotedMessage.user.fullname;
                quote.content = quotedMessage.body;
                message.quote = [quote copy];
            }
            
            TAPReplyToModel *replyTo = [TAPReplyToModel new];
            replyTo.messageID = quotedMessage.messageID;
            replyTo.localID = quotedMessage.localID;
            replyTo.messageType = quotedMessage.type;
            replyTo.fullname = quotedMessage.user.fullname;
            replyTo.xcUserID = quotedMessage.user.xcUserID;
            replyTo.userID = quotedMessage.user.userID;
            message.replyTo = replyTo;
        }
        else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
            //if message quoted from quote model then should just construct quote model
            TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
            message.quote = [quotedMessage copy];
        }
    }
    
    //Save image to cache with localID key
    [TAPImageView saveImageToCache:image withKey:message.localID];
    
    //Add message to waiting upload file dictionary in ChatManager to prepare save to database
    [[TAPChatManager sharedManager] addToWaitingUploadFileMessage:message];
    
    [[TAPChatManager sharedManager] notifySendMessageToDelegate:message];
    [[TAPFileUploadManager sharedManager] sendFileWithData:message];
}

- (void)sendImageMessage:(UIImage *)image caption:(NSString *)caption {
    TAPRoomModel *room = [TAPChatManager sharedManager].activeRoom;
    [self sendImageMessage:image caption:caption room:room];
}

- (void)sendImageMessageWithPHAsset:(PHAsset *)asset caption:(NSString *)caption room:(TAPRoomModel *)room {
    //Check if forward message exist, send forward message
    [self checkAndSendForwardedMessageWithRoom:room];
    
    caption = [TAPUtil nullToEmptyString:caption];
    
    NSString *messageBodyCaption = [NSString string];
    //Check contain caption or not
    if ([caption isEqualToString:@""]) {
        messageBodyCaption = NSLocalizedString(@"๐Ÿ–ผ Photo", @"");
    }
    else {
        messageBodyCaption = [NSString stringWithFormat:@"๐Ÿ–ผ %@", caption];
    }
    
    TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:messageBodyCaption type:TAPChatMessageTypeImage];
    
    NSMutableDictionary *dataDictionary = message.data;
    if (dataDictionary == nil) {
        dataDictionary = [[NSMutableDictionary alloc] init];
    }
//#ifdef DEBUG
//    NSLog(@"IMAGE BEFORE CACHE SIZE HEIGHT: %f, WIDTH: %f", image.size.height, image.size.width);
//#endif
//
    
    CGFloat imageWidthFloat = (CGFloat)asset.pixelWidth;
    CGFloat imageHeightFloat = (CGFloat)asset.pixelHeight;
    
    NSNumber *imageHeight = [NSNumber numberWithFloat:imageHeightFloat];
    NSNumber *imageWidth = [NSNumber numberWithFloat:imageWidthFloat];
    
    NSString *assetIdentifier = asset.localIdentifier;

    //Save asset to dictionary
    [[TAPFileUploadManager sharedManager] saveToPendingUploadAssetDictionaryWithAsset:asset];
    
    [dataDictionary setObject:imageHeight forKey:@"height"];
    [dataDictionary setObject:imageWidth forKey:@"width"];
//    [dataDictionary setObject:asset forKey:@"asset"];
    [dataDictionary setObject:assetIdentifier forKey:@"assetIdentifier"];
    [dataDictionary setObject:caption forKey:@"caption"];
    
    //check if userInfo is available, if available add to data in message model
    //userInfo custom user information from client, used for custom quote click action
    id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
    if (userInfo != nil) {
        [dataDictionary setObject:userInfo forKey:@"userInfo"];
    }
    
    message.data = [dataDictionary copy];
    
    //Check if quote message available
    id quotedMessageObject = [self.quotedMessageDictionary objectForKey:room.roomID];
    if (quotedMessageObject != nil) {
        if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
            //if message quoted from message model then should construct quote and reply to model
            TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
            quotedMessage = [quotedMessage copy];
            if (![quotedMessage.quote.imageURL isEqualToString:@""] || ![quotedMessage.quote.fileID isEqualToString:@""]) {
                message.quote = [quotedMessage.quote copy];
                message.quote.title = quotedMessage.user.fullname;
                message.quote.content = quotedMessage.body;
            }
            else {
                TAPQuoteModel *quote = [TAPQuoteModel new];
                quote.title = quotedMessage.user.fullname;
                quote.content = quotedMessage.body;
                message.quote = [quote copy];
            }
            
            TAPReplyToModel *replyTo = [TAPReplyToModel new];
            replyTo.messageID = quotedMessage.messageID;
            replyTo.localID = quotedMessage.localID;
            replyTo.messageType = quotedMessage.type;
            replyTo.fullname = quotedMessage.user.fullname;
            replyTo.xcUserID = quotedMessage.user.xcUserID;
            replyTo.userID = quotedMessage.user.userID;
            message.replyTo = replyTo;
        }
        else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
            //if message quoted from quote model then should just construct quote model
            TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
            message.quote = [quotedMessage copy];
        }
    }
    
    //Add message to waiting upload file dictionary in ChatManager to prepare save to database
    [[TAPChatManager sharedManager] addToWaitingUploadFileMessage:message];

    [[TAPFileUploadManager sharedManager] sendFileAsAssetWithData:message];
    [[TAPChatManager sharedManager] notifySendMessageToDelegate:message];
}

- (void)sendImageMessageWithPHAsset:(PHAsset *)asset caption:(NSString *)caption {
    TAPRoomModel *room = [TAPChatManager sharedManager].activeRoom;
    [self sendImageMessageWithPHAsset:asset caption:caption room:room];
}

- (void)sendVideoMessageWithPHAsset:(PHAsset *)asset caption:(NSString *)caption thumbnailImageData:(NSData *)thumbnailImageData room:(TAPRoomModel *)room {
    //Check if forward message exist, send forward message
    [self checkAndSendForwardedMessageWithRoom:room];
    
    caption = [TAPUtil nullToEmptyString:caption];
    
    NSString *messageBodyCaption = [NSString string];
    //Check contain caption or not
    if ([caption isEqualToString:@""]) {
        messageBodyCaption = NSLocalizedString(@"๐ŸŽฅ Video", @"");
    }
    else {
        messageBodyCaption = [NSString stringWithFormat:@"๐ŸŽฅ %@", caption];
    }
    
    TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:messageBodyCaption type:TAPChatMessageTypeVideo];
    
    NSMutableDictionary *dataDictionary = message.data;
    if (dataDictionary == nil) {
        dataDictionary = [[NSMutableDictionary alloc] init];
    }
    
    CGFloat imageWidthFloat = (CGFloat)asset.pixelWidth;
    CGFloat imageHeightFloat = (CGFloat)asset.pixelHeight;
    
    NSNumber *imageHeight = [NSNumber numberWithFloat:imageHeightFloat];
    NSNumber *imageWidth = [NSNumber numberWithFloat:imageWidthFloat];
    
    NSTimeInterval videoDuration = ceil(asset.duration);
    NSInteger videoDurationInteger = videoDuration * 1000; // in miliseconds
    
    NSString *thumbnailImageBase64String = [thumbnailImageData base64EncodedString];
    
    NSString *assetIdentifier = asset.localIdentifier;
    
//    PHAsset *obtainedAsset = [[TAPFetchMediaManager sharedManager] getAssetFromUserPreferenceWithKey:assetKey];
    
//    PHFetchOptions *allMediaOptions = [[PHFetchOptions alloc] init];
//    allMediaOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
//    PHFetchResult *allMedia = [PHAsset fetchAssetsWithOptions:allMediaOptions];
//
//    [allMedia enumerateObjectsUsingBlock:^(PHAsset * _Nonnull resultAsset, NSUInteger idx, BOOL * _Nonnull stop) {
//
//        if([assetIdentifier isEqualToString:resultAsset.localIdentifier]) {
//            // asset here
//        }
//    }];
    
    //Save asset to dictionary
    [[TAPFileUploadManager sharedManager] saveToPendingUploadAssetDictionaryWithAsset:asset];
    
    [dataDictionary setObject:imageHeight forKey:@"height"];
    [dataDictionary setObject:imageWidth forKey:@"width"];
//    [dataDictionary setObject:asset forKey:@"asset"];
    [dataDictionary setObject:assetIdentifier forKey:@"assetIdentifier"];
    [dataDictionary setObject:thumbnailImageBase64String forKey:@"thumbnail"];
    [dataDictionary setObject:caption forKey:@"caption"];
    [dataDictionary setObject:[NSNumber numberWithInteger:videoDurationInteger] forKey:@"duration"];
    
    //check if userInfo is available, if available add to data in message model
    //userInfo custom user information from client, used for custom quote click action
    id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
    if (userInfo != nil) {
        [dataDictionary setObject:userInfo forKey:@"userInfo"];
    }
    
    message.data = [dataDictionary copy];
    
    //Check if quote message available
    id quotedMessageObject = [self.quotedMessageDictionary objectForKey:room.roomID];
    if (quotedMessageObject != nil) {
        if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
            //if message quoted from message model then should construct quote and reply to model
            TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
            quotedMessage = [quotedMessage copy];
            if (![quotedMessage.quote.imageURL isEqualToString:@""] || ![quotedMessage.quote.fileID isEqualToString:@""]) {
                message.quote = [quotedMessage.quote copy];
                message.quote.title = quotedMessage.user.fullname;
                message.quote.content = quotedMessage.body;
            }
            else {
                TAPQuoteModel *quote = [TAPQuoteModel new];
                quote.title = quotedMessage.user.fullname;
                quote.content = quotedMessage.body;
                message.quote = [quote copy];
            }
            
            TAPReplyToModel *replyTo = [TAPReplyToModel new];
            replyTo.messageID = quotedMessage.messageID;
            replyTo.localID = quotedMessage.localID;
            replyTo.messageType = quotedMessage.type;
            replyTo.fullname = quotedMessage.user.fullname;
            replyTo.xcUserID = quotedMessage.user.xcUserID;
            replyTo.userID = quotedMessage.user.userID;
            message.replyTo = replyTo;
        }
        else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
            //if message quoted from quote model then should just construct quote model
            TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
            message.quote = [quotedMessage copy];
        }
    }
    
    //Add message to waiting upload file dictionary in ChatManager to prepare save to database
    [[TAPChatManager sharedManager] addToWaitingUploadFileMessage:message];
    
    [[TAPFileUploadManager sharedManager] sendFileAsAssetWithData:message];
    [[TAPChatManager sharedManager] notifySendMessageToDelegate:message];
}

- (void)sendVideoMessageWithPHAsset:(PHAsset *)asset caption:(NSString *)caption thumbnailImageData:(NSData *)thumbnailImageData {
    TAPRoomModel *room = [TAPChatManager sharedManager].activeRoom;
    [self sendVideoMessageWithPHAsset:asset caption:caption thumbnailImageData:thumbnailImageData room:room];
}

- (void)sendLocationMessage:(CGFloat)latitude longitude:(CGFloat)longitude address:(NSString *)address {
    TAPRoomModel *room = [TAPChatManager sharedManager].activeRoom;
    [self sendLocationMessage:latitude longitude:longitude address:address room:room];
}
    
- (void)sendLocationMessage:(CGFloat)latitude longitude:(CGFloat)longitude address:(NSString *)address room:(TAPRoomModel *)room {
    
    //Check if forward message exist, send forward message
    [self checkAndSendForwardedMessageWithRoom:room];

    NSString *messageBodyString = NSLocalizedString(@"๐Ÿ“Location", @"");
    
    TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:messageBodyString type:TAPChatMessageTypeLocation];
    
    NSMutableDictionary *dataDictionary = message.data;
    if (dataDictionary == nil) {
        dataDictionary = [[NSMutableDictionary alloc] init];
    }
    
    [dataDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:@"latitude"];
    [dataDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:@"longitude"];
    [dataDictionary setObject:address forKey:@"address"];
    
    //check if userInfo is available, if available add to data in message model
    //userInfo custom user information from client, used for custom quote click action
    id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
    if (userInfo != nil) {
        [dataDictionary setObject:userInfo forKey:@"userInfo"];
    }
    
    message.data = [dataDictionary copy];
    
    //Check if quote message available
    id quotedMessageObject = [self.quotedMessageDictionary objectForKey:room.roomID];
    if (quotedMessageObject != nil) {
        if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
            //if message quoted from message model then should construct quote and reply to model
            TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
            quotedMessage = [quotedMessage copy];
            if ([quotedMessage.quote.fileType isEqualToString:[NSString stringWithFormat: @"%ld", TAPChatMessageTypeFile]]) {
                //TYPE FILE
                message.quote = quotedMessage.quote;
            }
            else if (![quotedMessage.quote.imageURL isEqualToString:@""] || ![quotedMessage.quote.fileID isEqualToString:@""]) {
                message.quote = [quotedMessage.quote copy];
                message.quote.title = quotedMessage.user.fullname;
                message.quote.content = quotedMessage.body;
            }
            else {
                TAPQuoteModel *quote = [TAPQuoteModel new];
                quote.title = quotedMessage.user.fullname;
                quote.content = quotedMessage.body;
                message.quote = [quote copy];
            }
            
            TAPReplyToModel *replyTo = [TAPReplyToModel new];
            replyTo.messageID = quotedMessage.messageID;
            replyTo.localID = quotedMessage.localID;
            replyTo.messageType = quotedMessage.type;
            replyTo.fullname = quotedMessage.user.fullname;
            replyTo.xcUserID = quotedMessage.user.xcUserID;
            replyTo.userID = quotedMessage.user.userID;
            message.replyTo = replyTo;
        }
        else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
            //if message quoted from quote model then should just construct quote model
            TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
            message.quote = [quotedMessage copy];
        }
    }
    
    [self sendMessage:message notifyDelegate:YES];
    
    [[TAPChatManager sharedManager] removeQuotedMessageObjectWithRoomID:room.roomID];
}

- (void)sentFileMessage:(TAPDataFileModel *)dataFile filePath:(NSString *)filePath {
    TAPRoomModel *room = [TAPChatManager sharedManager].activeRoom;
    [self sentFileMessage:dataFile filePath:filePath room:room];
}

- (void)sentFileMessage:(TAPDataFileModel *)dataFile filePath:(NSString *)filePath room:(TAPRoomModel *)room {
    //Check if forward message exist, send forward message
    [self checkAndSendForwardedMessageWithRoom:room];
    
    NSString *fileName = dataFile.fileName;
    fileName = [TAPUtil nullToEmptyString:fileName];
    
    NSString *mediaType = dataFile.mediaType;
    mediaType = [TAPUtil nullToEmptyString:mediaType];
    
    NSNumber *size = dataFile.size;
    
    NSString *messageBodyString = [NSString stringWithFormat:@"๐Ÿ“Ž %@", fileName];
    
    TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:messageBodyString type:TAPChatMessageTypeFile];
        
    NSMutableDictionary *dataDictionary = message.data;
    if (dataDictionary == nil) {
        dataDictionary = [[NSMutableDictionary alloc] init];
    }

    [dataDictionary setObject:filePath forKey:@"filePath"];
    [dataDictionary setObject:fileName forKey:@"fileName"];
    [dataDictionary setObject:mediaType forKey:@"mediaType"];
    [dataDictionary setObject:size forKey:@"size"];
    
    //check if userInfo is available, if available add to data in message model
    //userInfo custom user information from client, used for custom quote click action
    id userInfo = [[TAPChatManager sharedManager].userInfoDictionary objectForKey:room.roomID];
    if (userInfo != nil) {
        [dataDictionary setObject:userInfo forKey:@"userInfo"];
    }
    
    message.data = [dataDictionary copy];
    
    //Check if quote message available
    id quotedMessageObject = [self.quotedMessageDictionary objectForKey:room.roomID];
    if (quotedMessageObject != nil) {
        if ([quotedMessageObject isKindOfClass:[TAPMessageModel class]]) {
            //if message quoted from message model then should construct quote and reply to model
            TAPMessageModel *quotedMessage = (TAPMessageModel *)quotedMessageObject;
            quotedMessage = [quotedMessage copy];
            if ([quotedMessage.quote.fileType isEqualToString:[NSString stringWithFormat: @"%ld", TAPChatMessageTypeFile]]) {
                //TYPE FILE
                message.quote = quotedMessage.quote;
            }
            else if (![quotedMessage.quote.imageURL isEqualToString:@""] || ![quotedMessage.quote.fileID isEqualToString:@""]) {
                message.quote = [quotedMessage.quote copy];
                message.quote.title = quotedMessage.user.fullname;
                message.quote.content = quotedMessage.body;
            }
            else {
                TAPQuoteModel *quote = [TAPQuoteModel new];
                quote.title = quotedMessage.user.fullname;
                quote.content = quotedMessage.body;
                message.quote = [quote copy];
            }
            
            TAPReplyToModel *replyTo = [TAPReplyToModel new];
            replyTo.messageID = quotedMessage.messageID;
            replyTo.localID = quotedMessage.localID;
            replyTo.messageType = quotedMessage.type;
            replyTo.fullname = quotedMessage.user.fullname;
            replyTo.xcUserID = quotedMessage.user.xcUserID;
            replyTo.userID = quotedMessage.user.userID;
            message.replyTo = replyTo;
        }
        else if ([quotedMessageObject isKindOfClass:[TAPQuoteModel class]]) {
            //if message quoted from quote model then should just construct quote model
            TAPQuoteModel *quotedMessage = (TAPQuoteModel *)quotedMessageObject;
            message.quote = [quotedMessage copy];
        }
    }
    
    //Add message to waiting upload file dictionary in ChatManager to prepare save to database
    [[TAPChatManager sharedManager] addToWaitingUploadFileMessage:message];
    
    [[TAPChatManager sharedManager] notifySendMessageToDelegate:message];
    [[TAPFileUploadManager sharedManager] sendFileWithData:message];
}

- (TAPMessageModel *)generateUnreadMessageIdentifierWithRoom:(TAPRoomModel *)room created:(NSNumber *)created indexPosition:(NSInteger)index {
    TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser created:created room:room body:@"" type:TAPChatMessageTypeUnreadMessageIdentifier];
    
    return message;
//    for (id delegate in self.delegatesArray) {
//        if ([delegate respondsToSelector:@selector(chatManagerDidAddUnreadMessageIdentifier:indexPosition:)]) {
//            [delegate chatManagerDidAddUnreadMessageIdentifier:message indexPosition:index];
//        }
//    }
}

- (void)setActiveUser:(TAPUserModel *)activeUser {
    _activeUser = activeUser;
    
    //Update data in preference (for TAPDataManager)
    NSDictionary *userDictionary = [activeUser toDictionary];
    [[NSUserDefaults standardUserDefaults] setSecureObject:userDictionary forKey:TAP_PREFS_ACTIVE_USER];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)addDelegate:(id)delegate {
    if ([self.delegatesArray containsObject:delegate]) {
        return;
    }
    
    NSLog(@"[WARNING] ChatManager - Do not forget to remove the delegate object, since an object can't weak retained in an array, also please remove this delegate before dealloc or the delegate will always retained");
    
    [self.delegatesArray addObject:delegate];
}

- (void)removeDelegate:(id)delegate {
    [self.delegatesArray removeObject:delegate];
}

- (void)checkAndSendPendingMessage {
    if ([self.pendingMessageArray count] == 0) {
        return;
    }
    
    TAPMessageModel *messageToBeSend = [self.pendingMessageArray objectAtIndex:0];
    [self runSendMessageSequenceWithMessage:messageToBeSend];
    [self.pendingMessageArray removeObjectAtIndex:0];
    
    [self performSelector:@selector(checkAndSendPendingMessage) withObject:nil afterDelay:0.05f];
}

- (void)updateSendingMessageToFailed {
    [TAPDataManager updateMessageToFailedWhenClosedInDatabase];
}

- (void)checkPendingBackgroundTask {
    BOOL isPendingMessageExist = NO;
    BOOL isFileUploadProgressExist = NO;
    
    if ([self.pendingMessageArray count] > 0) {
        //Pending message exist
        isPendingMessageExist = YES;
    }
    
    if ([[TAPFileUploadManager sharedManager] isUploadingFile]) {
        isFileUploadProgressExist = YES;
    }
    
    if ((isPendingMessageExist || isFileUploadProgressExist || [[TAPMessageStatusManager sharedManager] hasPendingProcess]) && self.checkPendingBackgroundTaskRetryAttempt < kMaximumRetryAttempt) {
        _checkPendingBackgroundTaskRetryAttempt++;
    }
    else {
        [self saveIncomingMessageAndDisconnect];
        
        //Stop timer save new message
        [self stopTimerSaveNewMessage];

        [TapTalk sharedInstance].instanceState = TapTalkInstanceStateInactive;
        
        //End background task
        [self removeAllBackgroundSequenceTaskWithApplication:[UIApplication sharedApplication]];
    }
}

- (void)runEnterBackgroundSequenceWithApplication:(UIApplication *)application {
    _checkPendingBackgroundTaskRetryAttempt = 0;
    [self saveAllUnsentMessageInMainThread];
    
    _backgroundTask = [application beginBackgroundTaskWithName:@"backgroundSequence" expirationHandler:^{
        [self removeAllBackgroundSequenceTaskWithApplication:application];
    }];
    
    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Do the work associated with the task, preferably in chunks.
        if (self.isEnterBackgroundSequenceActive == NO) {
            self.isEnterBackgroundSequenceActive = YES;
            [self checkPendingBackgroundTask];
            
            self->_backgroundSequenceTimer = [NSTimer scheduledTimerWithTimeInterval:kDelayTime
                                                          target:self
                                                        selector:@selector(checkPendingBackgroundTask)
                                                        userInfo:nil
                                                         repeats:YES];
            [[NSRunLoop currentRunLoop] addTimer:self.backgroundSequenceTimer forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
        }
    });
}

- (void)removeAllBackgroundSequenceTaskWithApplication:(UIApplication *)application {
    [[TAPNotificationManager sharedManager] updateApplicationBadgeCount];
    _isEnterBackgroundSequenceActive = NO;
    [self.backgroundSequenceTimer invalidate];
    _backgroundSequenceTimer = nil;
    [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
    self.backgroundTask = UIBackgroundTaskInvalid;
}

- (void)receiveMessageFromSocketWithEvent:(NSString *)eventName dataDictionary:(NSDictionary *)dataDictionary {
    
    //Decrypt message
    TAPMessageModel *decryptedMessage = [TAPEncryptorManager decryptToMessageModelFromDictionary:dataDictionary];
    
    //Add User to Contact Manager
    [[TAPContactManager sharedManager] addContactWithUserModel:decryptedMessage.user saveToDatabase:NO];
    
    decryptedMessage.isSending = NO;
    
#ifdef DEBUG
    NSLog(@"Receive Message (event: %@, localID: %@): %@", eventName, decryptedMessage.localID, decryptedMessage.body);
#endif
    
    if ([eventName isEqualToString:kTAPEventNewMessage]) {
        //Remove message from waiting response dictionary
        if ([self.waitingResponseDictionary count] != 0) {
            [self.waitingResponseDictionary removeObjectForKey:decryptedMessage.localID];
        }
        
        NSString *senderUserID = decryptedMessage.user.userID;
        senderUserID = [TAPUtil nullToEmptyString:senderUserID];
        
        NSString *currentUserID = [TAPDataManager getActiveUser].userID;
        currentUserID = [TAPUtil nullToEmptyString:currentUserID];
        
        //Check if message is send by other user, update delivery status
        if (![senderUserID isEqualToString:currentUserID]) {
            //Call API send delivery status to server (Update delivery status)
            [self processMessageAsDelivered:decryptedMessage];
        }
    }
    
    //Add new message to incoming array
    [self.incomingMessageArray addObject:decryptedMessage];
    
    //Check is in foreground or not
    if ([TapTalk sharedInstance].instanceState == TapTalkInstanceStateActive) {
        //In foreground state or in background sequence mode
        if ([decryptedMessage.room.roomID isEqualToString:self.activeRoom.roomID]) {
            //Message from current active room
            for (id delegate in self.delegatesArray) {
                if ([eventName isEqualToString:kTAPEventNewMessage]) {
                    if ([delegate respondsToSelector:@selector(chatManagerDidReceiveNewMessageInActiveRoom:)]) {
                        [delegate chatManagerDidReceiveNewMessageInActiveRoom:[decryptedMessage copyMessageModel]];
                    }
                }
                else if ([eventName isEqualToString:kTAPEventUpdateMessage]) {
                    if ([delegate respondsToSelector:@selector(chatManagerDidReceiveUpdateMessageInActiveRoom:)]) {
                        [delegate chatManagerDidReceiveUpdateMessageInActiveRoom:[decryptedMessage copyMessageModel]];
                    }
                }
            }
        }
        else {
            //Message not from current active room
            for (id delegate in self.delegatesArray) {
                if ([eventName isEqualToString:kTAPEventNewMessage]) {
                    if ([delegate respondsToSelector:@selector(chatManagerDidReceiveNewMessageOnOtherRoom:)]) {
                        [delegate chatManagerDidReceiveNewMessageOnOtherRoom:[decryptedMessage copyMessageModel]];
                    }
                }
                else if ([eventName isEqualToString:kTAPEventUpdateMessage]) {
                    if ([delegate respondsToSelector:@selector(chatManagerDidReceiveUpdateMessageOnOtherRoom:)]) {
                        [delegate chatManagerDidReceiveUpdateMessageOnOtherRoom:[decryptedMessage copyMessageModel]];
                    }
                }
            }
        }
    }
    else {
        //In background state
        //DV Temp
        //TODO Notification Manager Handle New Message
    }
}

- (void)receiveContactUpdatedFromSocketWithDataDictionary:(NSDictionary *)dataDictionary {
    TAPUserModel *user = [[TAPUserModel alloc] initWithDictionary:dataDictionary error:nil];
    [[TAPContactCacheManager sharedManager] shouldUpdateUserWithData:user];
}

- (void)receiveOnlineStatusFromSocketWithDataDictionary:(NSDictionary *)dataDictionary {
    _isShouldRefreshOnlineStatus = NO;
    TAPOnlineStatusModel *onlineStatus = [[TAPOnlineStatusModel alloc] initWithDictionary:dataDictionary error:nil];

    for (id delegate in self.delegatesArray) {
        if ([delegate respondsToSelector:@selector(chatManagerDidReceiveOnlineStatus:)]) {
            [delegate chatManagerDidReceiveOnlineStatus:onlineStatus];
        }
    }
}

- (void)receiveStartTypingFromSocketWithDataDictionary:(NSDictionary *)dataDictionary {
    
    TAPTypingModel *typing = [[TAPTypingModel alloc] initWithDictionary:dataDictionary error:nil];
    [self.typingDictionary setObject:typing forKey:typing.roomID];
    
    for (id delegate in self.delegatesArray) {
        if ([delegate respondsToSelector:@selector(chatManagerDidReceiveStartTyping:)]) {
            [delegate chatManagerDidReceiveStartTyping:typing];
        }
    }
}

- (void)receiveStopTypingFromSocketWithDataDictionary:(NSDictionary *)dataDictionary {
    TAPTypingModel *typing = [[TAPTypingModel alloc] initWithDictionary:dataDictionary error:nil];
    [self.typingDictionary removeObjectForKey:typing.roomID];
    
    for (id delegate in self.delegatesArray) {
        if ([delegate respondsToSelector:@selector(chatManagerDidReceiveStopTyping:)]) {
            [delegate chatManagerDidReceiveStopTyping:typing];
        }
    }
}

- (void)triggerSaveNewMessage {
    //Check timer is already running or not
    if ([self.saveNewMessageTimer isValid]) {
        return;
    }

    CGFloat timerInterval = 1.0f;
    _saveNewMessageTimer = [NSTimer timerWithTimeInterval:timerInterval
                                                   target:self
                                                 selector:@selector(saveNewMessageToDatabase)
                                                 userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.saveNewMessageTimer forMode:NSRunLoopCommonModes];
//    [[NSRunLoop mainRunLoop] addTimer:repeatingTimer forMode:NSDefaultRunLoopMode];
}

- (void)stopTimerSaveNewMessage {
    [self.saveNewMessageTimer invalidate];
    _saveNewMessageTimer = nil;
}

- (void)saveNewMessageToDatabase {
    if ([self.incomingMessageArray count] != 0) {
        //Insert to database
        [TAPDataManager updateOrInsertDatabaseMessageWithData:self.incomingMessageArray success:^{
            //Clear incoming message array
            [self.incomingMessageArray removeAllObjects];

            [[TAPMessageStatusManager sharedManager] triggerUpdateMessageStatus];
        } failure:^(NSError *error) {

        }];
    }
    else {
        //Call to check if there are read message when there are no new incoming message
        [[TAPMessageStatusManager sharedManager] triggerUpdateMessageStatus];
    }
}

- (void)saveAllUnsentMessage {
    NSMutableArray *groupedMessageArray = [NSMutableArray array];
    
    //Save new messages to database
    if ([self.incomingMessageArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:self.incomingMessageArray];
    }
    
    //Save pending messages to database
    if ([self.pendingMessageArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:self.pendingMessageArray];
    }
    
    //Save waitingResponse messages to database
    NSArray *waitingResponseArray = [NSArray array];
    waitingResponseArray = [self.waitingResponseDictionary allValues];
    if ([waitingResponseArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:waitingResponseArray];
    }
    
    //Save waiting upload file messages to database
    NSArray *waitingUploadArray = [NSArray array];
    waitingUploadArray = [self.waitingUploadDictionary allValues];
    if ([waitingUploadArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:waitingUploadArray];
    }
    
    if ([groupedMessageArray count] != 0) {
        [TAPDataManager updateOrInsertDatabaseMessageWithData:groupedMessageArray success:^{
            
        } failure:^(NSError *error) {
            
        }];
    }
    
    //Clear array incoming and waiting response dictionary
    [self.incomingMessageArray removeAllObjects];
    [self.waitingResponseDictionary removeAllObjects];
    [self.waitingUploadDictionary removeAllObjects];
}

- (void)saveAllUnsentMessageInMainThread {
    NSMutableArray *groupedMessageArray = [NSMutableArray array];
    
    //Save new messages to database
    if ([self.incomingMessageArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:self.incomingMessageArray];
    }
    
    //Save pending messages to database
    if ([self.pendingMessageArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:self.pendingMessageArray];
    }
    
    //Save waiting response messages to database
    NSArray *waitingResponseArray = [NSArray array];
    waitingResponseArray = [self.waitingResponseDictionary allValues];
    if ([waitingResponseArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:waitingResponseArray];
    }
    
    //Save waiting upload file messages to database
    NSArray *waitingUploadArray = [NSArray array];
    waitingUploadArray = [self.waitingUploadDictionary allValues];
    if ([waitingUploadArray count] != 0) {
        [groupedMessageArray addObjectsFromArray:waitingUploadArray];
    }
    
    if ([groupedMessageArray count] != 0) {
        [TAPDataManager updateOrInsertDatabaseMessageInMainThreadWithData:groupedMessageArray success:^{
            
        } failure:^(NSError *error) {
            
        }];
    }
    
    //Clear array incoming and waiting response dictionary
    [self.incomingMessageArray removeAllObjects];
    [self.waitingResponseDictionary removeAllObjects];
    [self.waitingUploadDictionary removeAllObjects];
}

- (void)saveIncomingMessageAndDisconnect {
    //Save new messages to database
    if ([self.incomingMessageArray count] != 0) {
        [TAPDataManager updateOrInsertDatabaseMessageInMainThreadWithData:self.incomingMessageArray success:^{
            
        } failure:^(NSError *error) {
            
        }];
    }
    
    [self disconnect];
}

- (void)saveUnsentMessageAndDisconnect {
    //Save all messages array to database
    [self saveAllUnsentMessage];
    [self disconnect];
}

- (void)saveMessageToDraftWithMessage:(NSString *)message roomID:(NSString *)roomID {
    roomID = [TAPUtil nullToEmptyString:roomID];
    
    if ([message isEqualToString:@""] || message.length == 0) {
        [[TAPChatManager sharedManager].messageDraftDictionary removeObjectForKey:roomID];
    }
    else {
        [[TAPChatManager sharedManager].messageDraftDictionary setObject:message forKey:roomID];
    }
}

- (NSString *)getMessageFromDraftWithRoomID:(NSString *)roomID {
    roomID = [TAPUtil nullToEmptyString:roomID];
    NSString *draftMessage = [[TAPChatManager sharedManager].messageDraftDictionary objectForKey:roomID];
    draftMessage = [TAPUtil nullToEmptyString:draftMessage];
    
    return draftMessage;
}

- (void)saveToQuotedMessage:(id)quotedMessageObject userInfo:(NSDictionary *)userInfo roomID:(NSString *)roomID { //Object could be TAPMessageModel or TAPQuoteModel
    if(quotedMessageObject != nil) {
        
        [[TAPChatManager sharedManager].quotedMessageDictionary setObject:quotedMessageObject forKey:roomID];
    }
    
    if(userInfo != nil) {
        [[TAPChatManager sharedManager].userInfoDictionary setObject:userInfo forKey:roomID];
    }
}

- (id)getQuotedMessageObjectWithRoomID:(NSString *)roomID { //Object could be TAPMessageModel or TAPQuoteModel
     roomID = [TAPUtil nullToEmptyString:roomID];
    id object =  [[TAPChatManager sharedManager].quotedMessageDictionary objectForKey:roomID];
    return object;
}

- (void)removeQuotedMessageObjectWithRoomID:(NSString *)roomID {
    roomID = [TAPUtil nullToEmptyString:roomID];
    [[TAPChatManager sharedManager].quotedMessageDictionary removeObjectForKey:roomID];
    [[TAPChatManager sharedManager].userInfoDictionary removeObjectForKey:roomID];
}

- (void)saveToQuoteActionWithType:(TAPChatManagerQuoteActionType)type roomID:(NSString *)roomID {
    //save to quoteActionTypeDictionary to identify whether it is reply or forward
    NSNumber *actionTypeNumber = [NSNumber numberWithInteger:type];
    [self.quoteActionTypeDictionary setObject:actionTypeNumber forKey:roomID];
}

- (TAPChatManagerQuoteActionType)getQuoteActionTypeWithRoomID:(NSString *)roomID {
    NSNumber *obtainedTypeNumber = [self.quoteActionTypeDictionary objectForKey:roomID];
    NSInteger obtainedType = [obtainedTypeNumber integerValue];
    TAPChatManagerQuoteActionType actionType = obtainedType;
    return actionType;
}

- (void)processMessageAsDelivered:(TAPMessageModel *)message {
    BOOL isDelivered = message.isDelivered;
    if (!isDelivered) {
        //Send delivered status to server
        [[TAPMessageStatusManager sharedManager] markMessageAsDeliveredWithMessage:message];
    }
}

- (BOOL)checkIsTypingWithRoomID:(NSString *)roomID {
    if([self.typingDictionary objectForKey:roomID] != nil) {
        return YES;
    }
    return NO;
}

- (void)setIsWaitingTypingNo {
    _isWaitingSendTyping = NO;
}

- (BOOL)checkShouldRefreshOnlineStatus {
    return self.isShouldRefreshOnlineStatus;
}

- (void)refreshShouldRefreshOnlineStatus {
    _isShouldRefreshOnlineStatus = YES;
}

- (void)addToWaitingUploadFileMessage:(TAPMessageModel *)message {
    [self.waitingUploadDictionary setObject:message forKey:message.localID];
}

- (void)removeFromWaitingUploadFileMessage:(TAPMessageModel *)message {
    [self.waitingUploadDictionary removeObjectForKey:message.localID];
}

- (TAPMessageModel *)getMessageFromWaitingUploadDictionaryWithKey:(NSString *)localID {
   TAPMessageModel *message = [self.waitingUploadDictionary objectForKey:localID];
    return message;
}

- (NSString *)getOtherUserIDWithRoomID:(NSString *)roomID {
    NSArray *roomIDArray = [roomID componentsSeparatedByString:@"-"];
    if([roomIDArray count] > 0) {
        for (NSString *userID in roomIDArray) {
            if(![userID isEqualToString:[TAPDataManager getActiveUser].userID]) {
                return userID;
            }
        }
    }
    
    return @"";
}

- (void)checkAndSendForwardedMessageWithRoom:(TAPRoomModel *)room {
    NSNumber *quoteActionTypeNumber = [self.quoteActionTypeDictionary objectForKey:room.roomID];
    TAPChatManagerQuoteActionType type = [quoteActionTypeNumber integerValue];
    
    TAPMessageModel *existingMessage = [self.quotedMessageDictionary objectForKey:room.roomID];
    
    if (type == TAPChatManagerQuoteActionTypeForward) {
        TAPMessageModel *message = [TAPMessageModel createMessageWithUser:[TAPChatManager sharedManager].activeUser room:room body:existingMessage.body type:existingMessage.type];
        
        message.data = existingMessage.data;
        message.quote = existingMessage.quote;
        message.replyTo = existingMessage.replyTo;
        
        if (existingMessage.forwardFrom.localID != nil && ![existingMessage.forwardFrom.localID isEqualToString:@""]) {
            //Obtain existing forward from model
            message.forwardFrom = existingMessage.forwardFrom;
        }
        else {
            //Create forward from model
            TAPForwardFromModel *forwardFrom = [TAPForwardFromModel new];
            forwardFrom.userID = existingMessage.user.userID;
            forwardFrom.xcUserID = existingMessage.user.xcUserID;
            forwardFrom.fullname = existingMessage.user.fullname;
            forwardFrom.messageID = existingMessage.messageID;
            forwardFrom.localID = existingMessage.localID;
            message.forwardFrom = forwardFrom;
        }
        
        [self sendMessage:message notifyDelegate:YES];
        
        //Remove from dictionary
        [self.quoteActionTypeDictionary removeObjectForKey:room.roomID];
        [self.quotedMessageDictionary removeObjectForKey:room.roomID];
    }
}

- (void)saveFilePathToDictionaryWithPath:(NSString *)path
                                 localID:(NSString *)localID
                                  roomID:(NSString *)roomID {
    
    NSMutableDictionary *storedFilePathPerRoomDictionary = [self.filePathStoredDictionary objectForKey:roomID];
    
    if ([storedFilePathPerRoomDictionary count] != 0) {
        [storedFilePathPerRoomDictionary setObject:path forKey:localID];
        [self.filePathStoredDictionary setObject:storedFilePathPerRoomDictionary forKey:roomID];
    }
    else {
        storedFilePathPerRoomDictionary = [[NSMutableDictionary alloc] init];
        [storedFilePathPerRoomDictionary setObject:path forKey:localID];
        [self.filePathStoredDictionary setObject:storedFilePathPerRoomDictionary forKey:roomID];
    }
}

- (void)updateMessageToFailedWithLocalID:(NSString *)localID {
    
    TAPMessageModel *message = [self getMessageFromWaitingUploadDictionaryWithKey:localID];
    if (message) {
        message.isSending = NO;
        message.isFailedSend = YES;
        [self.waitingUploadDictionary setObject:message forKey:message.localID];
    }
}

- (void)clearChatManagerData {
    [TAPChatManager sharedManager].activeUser = nil;
    [TAPChatManager sharedManager].activeRoom = nil;
    [[TAPChatManager sharedManager].messageDraftDictionary removeAllObjects];
    [[TAPChatManager sharedManager].quotedMessageDictionary removeAllObjects];
    [[TAPChatManager sharedManager].quoteActionTypeDictionary removeAllObjects];
    [[TAPChatManager sharedManager].userInfoDictionary removeAllObjects];
    [[TAPChatManager sharedManager].filePathStoredDictionary removeAllObjects];
}

@end