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

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import "PBX.h"
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import "Rainbow/Rainbow.h"
#import "IEventEmmiter.h"
#import "Contact+DictRepresentation.h"
#import "Navigation.h"
#import "NSData+Base64String.h"
#import "PBXEventsNames.h"
#import "DefaultContactIcon.h"
#import "PBXEventsNames.h"
#import "Call+DictRepresentation.h"
#import "RNRainbowRn.h"
#import "Rainbow/CallProtocol.h"

#define MAKE_PBX_CALL_FAIL_MESSAGE @"make_pbx_call_fail_message"
#define MAKE_PBX_CALL_FAIL_NO_PHONE_MESSAGE @"make_pbx_call_fail_no_phone_message"

@interface PBX ()
@property (strong, nonatomic) TelephonyService *telephonyService;
@property (strong, nonatomic) MyUser *myUser;
@property (strong, nonatomic) Call *currentPBXCall;
@property (nonatomic, strong) IEventEmmiter *eventEmitter;
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, strong) id<CallsServiceProtocol> callService;

@end

@implementation PBX
RCT_EXPORT_MODULE();

+(id)sharedManager {
    static PBX *sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}


- (instancetype)init {
    if ([[RNRainbowRn sharedManager] pbxEnabled]) {
        self = [super init];
        if (self) {
            _myUser = [ServicesManager sharedInstance].myUser;
            _telephonyService = [ServicesManager sharedInstance].telephonyService;
            [self registerTelephonyEvents];
        }
        return self;
    }
    return nil;
}

- (void)dealloc {
    _myUser = nil;
    _telephonyService = nil;
    _currentPBXCall = nil;
    [self unRegisterTelephonyEvents];
}

#pragma mark - ReactNative_functions

RCT_EXPORT_METHOD(startCall:(NSString *)number) {
    //I did this because the "[application openURL:URL ..." need to be processed in the main thread
    if (![NSThread isMainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self startCall:number];
        });
        return;
    }
    
    NSLog(@"[PBX] startCall with number: %@",number);
    //check if this my number
    NSArray<PhoneNumber*> *myNumbers = _myUser.contact.phoneNumbers;
    for (PhoneNumber *phone in myNumbers) {
        if ([phone.number isEqualToString:number] || [phone.numberE164 isEqualToString:number]) {
            //The user is trying to call him self
            [self sendEventWithName:MakePbxCallResponse eventBody:MAKE_PBX_CALL_FAIL_NO_PHONE_MESSAGE];
            return;
        }
    }
    if (_myUser.nomadicStatus.activated && _myUser.isAllowedToUseTelephony) {
        PhoneNumber *contactPhoneNumber = [[PhoneNumber alloc] initWithPhoneNumberString:number];
        [_telephonyService makeCallTo:contactPhoneNumber fallBackHandler:^(NSError* error , PhoneNumber* phoneNumber){
            NSLog(@"[PBX] calling Error%@", error);
            if (error != nil) {
                [self sendEventWithName:MakePbxCallResponse eventBody:MAKE_PBX_CALL_FAIL_MESSAGE];
                return;
            }
            NSLog(@"[PBX] phoneNumber %@", phoneNumber);
        } completionHandler:^(NSError * _Nullable error, NSString * _Nullable newGlobalCallId) {
            if (error != nil) {
                NSLog(@"[PBX] startCall completionHandler Error%@", error);
                [self sendEventWithName:MakePbxCallResponse eventBody:MAKE_PBX_CALL_FAIL_MESSAGE];
                return;
            }
        }];
    }else {
        //then make cellular phone call
        NSString *str = @"tel://";
        str = [str stringByAppendingString:number];
        UIApplication *application = [UIApplication sharedApplication];
        NSURL *URL = [NSURL URLWithString:str];
        [application openURL:URL options:@{} completionHandler:^(BOOL success) {
            if (success) {
                NSLog(@"Opened url");
            }
        }];
    }
}

RCT_EXPORT_METHOD(endCall) {
    NSLog(@"[PBX] endCall");
    _currentPBXCall = _telephonyService.currentCallsToDisplay.firstObject;
    if (_currentPBXCall.status == CallStatusRinging) {
        
        if (_currentPBXCall.isIncoming) {
            [_telephonyService declineIncomingCall:_currentPBXCall];
        }
        else {
            [_telephonyService cancelOutgoingCall:_currentPBXCall];
        }
        
    }
    
    [_telephonyService hangupCall:_currentPBXCall];
    
}

RCT_EXPORT_METHOD(rejectCall){
    [self endCall];
}

RCT_EXPORT_METHOD(holdCall) {
    NSLog(@"[PBX] holdCall");
    if ([self.callService respondsToSelector:@selector(holdCall:)]) {
        [self.callService holdCall:_currentPBXCall completionHandler:^(NSError * _Nonnull error) {
            NSLog(@"holdCall has been failed ");

        }];
      } else {
          NSLog(@"The call service doesn't support holdCall:");
      }
    
}

RCT_EXPORT_METHOD(retrieveCall:(NSString *)callId) {
    NSLog(@"[PBX] retrieveCall");
    if ([self.callService respondsToSelector:@selector(holdCall:)]) {
        [self.callService retrieveCall:_currentPBXCall completionHandler:^(NSError * _Nonnull error) {
            NSLog(@"retrieveCall has been failed ");

        }];
      } else {
          NSLog(@"The call service doesn't support holdCall:");
      }
    
}

RCT_EXPORT_METHOD(takeCall:(NSString *)sessionId) {
    NSLog(@"[PBX] takeCall");
    [_telephonyService answerCall:_currentPBXCall];
}

RCT_EXPORT_METHOD(alternateCalls:(NSString *)activeSessionId heldSessionId:(NSString *)heldSessionId) {
    Call *heldCall = _telephonyService.currentHeldCall;
    Call *activeCall = _telephonyService.currentActiveCall;
    
    [_telephonyService swapCall:activeCall withHeldCall:heldCall completionBlock:^(NSError * _Nullable error) {
        NSLog(@"swapCall has been failed  %@", error);

    }];
    
}


RCT_EXPORT_METHOD(forwardCallToVoiceMail:(NSString*)sessionId) {
    NSLog(@"[PBX] forwardCallToVoiceMail with seesion: %@",sessionId);
    [_telephonyService deflectIncomingCall:_currentPBXCall];
}


RCT_EXPORT_METHOD(transferCall:(NSString*)outgoingSessionId heldSessionId:(NSString*)heldSessionId) {
    Call *heldCall = _telephonyService.currentHeldCall;
    Call *activeCall = _telephonyService.currentActiveCall;
    
    [_telephonyService transferCall:activeCall toHeldCall:heldCall];
}

RCT_EXPORT_METHOD(makeConferenceCall:(NSString*)activeSessionId heldSessionId:(NSString*)heldSessionId) {
    Call *heldCall = _telephonyService.currentHeldCall;
    Call *activeCall = _telephonyService.currentActiveCall;
    [_telephonyService mergeCall:activeCall withHeldCall:heldCall completionBlock:^(NSError * _Nullable error) {
        if(error)     NSLog(@"[PBX] makeConferenceCall: error in merge call");

    }];
}


#pragma mark - Notification_Listeners

-(void) didUpdateCall:(NSNotification *) notification {
    NSLog(@"[PBX] didUpdateCall");
    
    if ([notification.object class] == [Call class]) {
        Call *call = (Call*)notification.object;
        _currentPBXCall = call;
        
        BOOL isCallStillActive = [[Navigation sharedManager] isCallActive];
        if (!isCallStillActive) {
            [[Navigation sharedManager] moveViewToCall];
        }
        [self handleUpdatedCall];
    }
}


-(void) didFinishLoadingCallContainer:(NSNotification *) notification {
    NSLog(@"[PBX] didFinishLoadingCallContainer");
    RCTRootView *currentView = (RCTRootView*)notification.object;
    if ([currentView.moduleName isEqualToString:@"call_activity"]) {//create new methode for poth
        [self handleUpdatedCall];
    }
}

-(void) didFailedToCall:(NSNotification *) notification {
    NSLog(@"[PBX] didFailedToCall");
    if ([notification.object class] == [Call class]) {
        //TODO: I think it should hanguo the call
    }
}

#pragma mark - Others

-(void) pbxCountUpdated {
    NSInteger pbxCount = _telephonyService.currentCallsNumber;
    NSLog(@"[PBX] pbxCountUpdated with count: %ld",(long)pbxCount);
    [self sendEventWithName:SessionsCountUpdated eventBody:[NSNumber numberWithInteger:pbxCount]];
}

-(void) handleUpdatedCall {
    
    [self pbxCountUpdated];
    if (_currentPBXCall.status == CallStatusRinging) {
        if (_telephonyService.currentCallsNumber > 1) {
            NSLog(@"sessionAdded:  second session Added: %@ session counts is: %ld",_currentPBXCall.callRef,(long)_telephonyService.currentCallsNumber);
            NSDictionary *callDic = [_currentPBXCall pBxCallToDictionaryRepresentation];
            [self sendEventWithName:SecondSessionAdded eventBody:callDic];
        } else {
            
            NSDictionary *callDic = [_currentPBXCall pBxCallToDictionaryRepresentation];
            [self sendEventWithName:SessionAdded eventBody:callDic];
        }
    }
    
    if (_currentPBXCall.status == CallStatusEstablished || _currentPBXCall.status == CallStatusHeldByUser || _currentPBXCall.status == CallStatusHeldByRemote) {
        //the user opened the PBX call, then send the updated call to the react-native
        if (_telephonyService.currentCallsNumber > 1) {
            NSArray<Call*> *currentCallsToDisplay = [_telephonyService currentCallsToDisplay];
            for (Call *callObj in currentCallsToDisplay) {
                if (callObj.status == CallStatusEstablished) {
                    NSDictionary *callDic = [callObj pBxCallToDictionaryRepresentation];
                    [self sendEventWithName:SecondSessionUpdated eventBody:callDic];
                }
                else{
                    NSDictionary *callDic = [callObj pBxCallToDictionaryRepresentation];
                    [self sendEventWithName:SessionUpdated eventBody:callDic];
                }
            }
            
        } else {
            //the user opened the PBX call, then send the updated call to the react-native
            NSDictionary *callDic = [_currentPBXCall pBxCallToDictionaryRepresentation];
            [self sendEventWithName:SessionUpdated eventBody:callDic];
        }
    }
    
    
    if (_currentPBXCall.status == CallStatusCanceled || _currentPBXCall.status == CallStatusHangup || _currentPBXCall.status == CallStatusDeclined ||_currentPBXCall.status == CallStatusTimeout) {
        
        if (_telephonyService.currentCallsNumber > 0) {
            NSLog(@"[PBX] second session is removed with ID: %@",_currentPBXCall.callRef);
            NSDictionary *callDic = [_currentPBXCall pBxCallToDictionaryRepresentation];
            [self sendEventWithName:SecondSessionRemoved eventBody:callDic];
        }
        else {
            NSLog(@"[PBX] the call is finished and the current call number is 0");
            NSDictionary *callDic = [_currentPBXCall pBxCallToDictionaryRepresentation];
            [self sendEventWithName:SecondSessionRemoved eventBody:callDic];
            [self sendEventWithName:SessionRemoved eventBody:callDic];
            [[Navigation sharedManager] moveViewToRainbow];
        }
    }
}

#pragma mark - Register-Notification

- (void) registerTelephonyEvents {
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateCall:) name:kTelephonyServiceDidAddCall object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateCall:) name:kTelephonyServiceDidUpdateCall object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateCall:) name:kTelephonyServiceDidRemoveCall object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateCall:) name:kTelephonyPBXAvailabilityChanged object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateCall:) name:kTelephonyWebRTCGatewayAvailabilityChanged object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didFailedToCall:) name:kTelephonyServiceDidFailToCall object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didFinishLoadingCallContainer:) name:RCTContentDidAppearNotification object:nil];
}

- (void) unRegisterTelephonyEvents {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTelephonyServiceDidAddCall object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTelephonyServiceDidUpdateCall object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTelephonyServiceDidRemoveCall object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTelephonyServiceDidFailToCall object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTelephonyWebRTCGatewayAvailabilityChanged object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTelephonyServiceDidFailToCall object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTContentDidAppearNotification object: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(@" [PBX] sendEventWithName: %@", eventName);
        [eventEmitter sendEvent:eventName eventBody:eventBody];
    } else {
        NSLog(@"[PBX] sendEventWithName:  Bridge is not ready. Cannot send event: %@", eventName);
    }
}
+ (BOOL)requiresMainQueueSetup
{
    return NO;
}

@end
