/*
 Copyright 2015 OpenMarket Ltd
 Copyright 2018 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#import "MXKCallViewController.h"

@import MatrixSDK;

#import "MXKAppSettings.h"
#import "MXKSoundPlayer.h"
#import "MXKTools.h"
#import "NSBundle+MatrixKit.h"

#import "MXKSwiftHeader.h"

NSString *const kMXKCallViewControllerWillAppearNotification = @"kMXKCallViewControllerWillAppearNotification";
NSString *const kMXKCallViewControllerAppearedNotification = @"kMXKCallViewControllerAppearedNotification";
NSString *const kMXKCallViewControllerWillDisappearNotification = @"kMXKCallViewControllerWillDisappearNotification";
NSString *const kMXKCallViewControllerDisappearedNotification = @"kMXKCallViewControllerDisappearedNotification";
NSString *const kMXKCallViewControllerBackToAppNotification = @"kMXKCallViewControllerBackToAppNotification";

static const CGFloat kLocalPreviewMargin = 20;

@interface MXKCallViewController ()
{
    NSTimer *hideOverlayTimer;
    NSTimer *updateStatusTimer;
    
    Boolean isMovingLocalPreview;
    Boolean isSelectingLocalPreview;
    
    CGPoint startNewLocalMove;

    /**
     The popup showed in case of call stack error.
     */
    UIAlertController *errorAlert;
    
    // the room events listener
    id roomListener;
    
    // Observe kMXRoomDidFlushDataNotification to take into account the updated room members when the room history is flushed.
    id roomDidFlushDataNotificationObserver;
    
    // Observe AVAudioSessionRouteChangeNotification
    id audioSessionRouteChangeNotificationObserver;
    
    // Current alert (if any).
    UIAlertController *currentAlert;
    
    //  Current peer display name
    NSString *peerDisplayName;
}

@property (nonatomic, assign) Boolean isRinging;

@property (nonatomic, nullable) UIView *incomingCallView;

@property (nonatomic, strong) UITapGestureRecognizer *onHoldCallContainerTapRecognizer;

@end

@implementation MXKCallViewController
@synthesize backgroundImageView;
@synthesize localPreviewContainerView, localPreviewVideoView, localPreviewActivityView, remotePreviewContainerView;
@synthesize overlayContainerView, callContainerView, callerImageView, callerNameLabel, callStatusLabel;
@synthesize callToolBar, rejectCallButton, answerCallButton, endCallButton;
@synthesize callControlContainerView, speakerButton, audioMuteButton, videoMuteButton;
@synthesize backToAppButton, cameraSwitchButton;
@synthesize backToAppStatusWindow;
@synthesize mxCall;
@synthesize mxCallOnHold;
@synthesize onHoldCallerImageView;
@synthesize onHoldCallContainerView;

#pragma mark - Class methods

+ (UINib *)nib
{
    return [UINib nibWithNibName:NSStringFromClass(self.class)
                          bundle:[NSBundle bundleForClass:self.class]];
}

+ (instancetype)callViewController:(MXCall*)call
{
    MXKCallViewController *instance = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
                                                                     bundle:[NSBundle bundleForClass:self.class]];
    
    // Load the view controller's view now (buttons and views will then be available).
    if ([instance respondsToSelector:@selector(loadViewIfNeeded)])
    {
        // iOS 9 and later
        [instance loadViewIfNeeded];
    }
    else if (instance.view)
    {
        // Patch: on iOS < 9.0, we load the view by calling its getter.
    }
    
    instance.mxCall = call;
    
    return instance;
}

#pragma mark -

- (void)finalizeInit
{
    [super finalizeInit];
    
    _playRingtone = YES;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    updateStatusTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTimeStatusLabel) userInfo:nil repeats:YES];
    
    self.callerImageView.defaultBackgroundColor = [UIColor clearColor];
    self.backToAppButton.backgroundColor = [UIColor clearColor];
    self.audioMuteButton.backgroundColor = [UIColor clearColor];
    self.videoMuteButton.backgroundColor = [UIColor clearColor];
    self.resumeButton.backgroundColor = [UIColor clearColor];
    self.moreButton.backgroundColor = [UIColor clearColor];
    self.speakerButton.backgroundColor = [UIColor clearColor];
    self.transferButton.backgroundColor = [UIColor clearColor];
    
    [self.backToAppButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_backtoapp"] forState:UIControlStateNormal];
    [self.backToAppButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_backtoapp"] forState:UIControlStateHighlighted];
    [self.audioMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_audio_unmute"] forState:UIControlStateNormal];
    [self.audioMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_audio_mute"] forState:UIControlStateSelected];
    [self.videoMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_video_unmute"] forState:UIControlStateNormal];
    [self.videoMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_video_mute"] forState:UIControlStateSelected];
    [self.moreButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_call_more"] forState:UIControlStateNormal];
    [self.moreButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_call_more"] forState:UIControlStateSelected];
    [self.speakerButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_speaker_off"] forState:UIControlStateNormal];
    [self.speakerButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_speaker_on"] forState:UIControlStateSelected];
    
    // Localize string
    [answerCallButton setTitle:[VectorL10n answerCall] forState:UIControlStateNormal];
    [answerCallButton setTitle:[VectorL10n answerCall] forState:UIControlStateHighlighted];
    [rejectCallButton setTitle:[VectorL10n rejectCall] forState:UIControlStateNormal];
    [rejectCallButton setTitle:[VectorL10n rejectCall] forState:UIControlStateHighlighted];
    [endCallButton setTitle:[VectorL10n endCall] forState:UIControlStateNormal];
    [endCallButton setTitle:[VectorL10n endCall] forState:UIControlStateHighlighted];
    [_resumeButton setTitle:[VectorL10n resumeCall] forState:UIControlStateNormal];
    [_resumeButton setTitle:[VectorL10n resumeCall] forState:UIControlStateHighlighted];
    
    // Refresh call information
    self.mxCall = mxCall;
    
    // Listen to AVAudioSession activation notification if CallKit is available and enabled
    BOOL isCallKitAvailable = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled;
    if (isCallKitAvailable)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleAudioSessionActivationNotification)
                                                     name:kMXCallKitAdapterAudioSessionDidActive
                                                   object:nil];
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXCallKitAdapterAudioSessionDidActive object:nil];

    [self removeObservers];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerWillAppearNotification object:nil];
    
    [self updateLocalPreviewLayout];
    [self showOverlayContainer:YES];
    
    if (mxCall)
    {
        // Refresh call display according to the call room state.
        [self callRoomStateDidChange:^{
            // Refresh call status
            [self call:self->mxCall stateDidChange:self->mxCall.state reason:nil];
        }];

    }
    
    if (_delegate)
    {
        backToAppButton.hidden = NO;
    }
    else
    {
        backToAppButton.hidden = YES;
    }
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerAppearedNotification object:nil];
    
    // trick to hide the volume at launch
    // as the mininum volume is forced by the application
    // the volume popup can be displayed
    //    volumeView = [[MPVolumeView alloc] initWithFrame: CGRectMake(5000, 5000, 0, 0)];
    //    [self.view addSubview: volumeView];
    //
    //    dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    //        [volumeView removeFromSuperview];
    //    });
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerWillDisappearNotification object:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerDisappearedNotification object:nil];
}

- (void)dismiss
{
    if (_delegate)
    {
        [_delegate dismissCallViewController:self completion:nil];
    }
    else
    {
        // Auto dismiss after few seconds
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self dismissViewControllerAnimated:YES completion:nil];
        });
    }
}

#pragma mark - override MXKViewController

- (void)destroy
{
    self.peer = nil;
    
    self.mxCall = nil;
    
    _delegate = nil;
    
    self.isRinging = NO;
    
    [hideOverlayTimer invalidate];
    [updateStatusTimer invalidate];
    
    _incomingCallView = nil;
    
    _onHoldCallContainerTapRecognizer = nil;
    
    [super destroy];
}

#pragma mark - Properties

- (UIImage *)picturePlaceholder
{
    return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"];
}

- (void)setMxCall:(MXCall *)call
{
    // Remove previous call (if any)
    if (mxCall)
    {
        mxCall.delegate = nil;
        mxCall.selfVideoView = nil;
        mxCall.remoteVideoView = nil;
        [self removeMatrixSession:self.mainSession];
        
        [self removeObservers];
        
        mxCall = nil;
    }
    
    if (call && call.room)
    {
        mxCall = call;
        
        [self addMatrixSession:mxCall.room.mxSession];

        MXWeakify(self);

        // Register a listener to handle messages related to room name, members...
        roomListener = [mxCall.room listenToEventsOfTypes:@[kMXEventTypeStringRoomName, kMXEventTypeStringRoomTopic, kMXEventTypeStringRoomAliases, kMXEventTypeStringRoomAvatar, kMXEventTypeStringRoomCanonicalAlias, kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
            MXStrongifyAndReturnIfNil(self);

            // Consider only live events
            if (self->mxCall && direction == MXTimelineDirectionForwards)
            {
                // The room state has been changed
                [self callRoomStateDidChange:nil];
            }
        }];
        
        // Observe room history flush (sync with limited timeline, or state event redaction)
        roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
            MXStrongifyAndReturnIfNil(self);
            
            MXRoom *room = notif.object;
            if (self->mxCall && self.mainSession == room.mxSession && [self->mxCall.room.roomId isEqualToString:room.roomId])
            {
                // The existing room history has been flushed during server sync.
                // Take into account the updated room state
                [self callRoomStateDidChange:nil];
            }
            
        }];
        
        audioSessionRouteChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionRouteChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
            
            [self updateProximityAndSleep];
            
        }];
        
        // Hide video mute on voice call
        self.videoMuteButton.hidden = !call.isVideoCall;
        
        // Hide camera switch on voice call
        self.cameraSwitchButton.hidden = !call.isVideoCall;
        
        _moreButtonForVideo.hidden = !call.isVideoCall;
        _moreButtonForVoice.hidden = call.isVideoCall;
        
        // Observe call state change
        call.delegate = self;

        // Display room call information
        [self callRoomStateDidChange:^{
            [self call:call stateDidChange:call.state reason:nil];
        }];
        
        if (call.isVideoCall && localPreviewContainerView)
        {
            // Access to the camera is mandatory to display the self view
            // Check the permission right now
            NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
            [MXKTools checkAccessForMediaType:AVMediaTypeVideo
                          manualChangeMessage:[VectorL10n cameraAccessNotGrantedForCall:appDisplayName]

                    showPopUpInViewController:self completionHandler:^(BOOL granted) {

                   if (granted)
                   {
                       self->localPreviewContainerView.hidden = NO;
                       self->remotePreviewContainerView.hidden = NO;

                       call.selfVideoView = self->localPreviewVideoView;
                       call.remoteVideoView = self->remotePreviewContainerView;
                       [self applyDeviceOrientation:YES];

                       [[NSNotificationCenter defaultCenter] addObserver:self
                                                                selector:@selector(deviceOrientationDidChange)
                                                                    name:UIDeviceOrientationDidChangeNotification
                                                                  object:nil];
                   }
               }];
        }
        else
        {
            localPreviewContainerView.hidden = YES;
            remotePreviewContainerView.hidden = YES;
        }
    }
}

- (void)setMxCallOnHold:(MXCall *)callOnHold
{
    if (mxCallOnHold == callOnHold)
    {
        //  setting same property, return
        return;
    }
    
    mxCallOnHold = callOnHold;
    
    if (mxCallOnHold)
    {
        self.onHoldCallContainerView.hidden = NO;
        [self.onHoldCallContainerView addGestureRecognizer:self.onHoldCallContainerTapRecognizer];
        [self.onHoldCallContainerView setUserInteractionEnabled:YES];
        
        // Handle peer here
        if (mxCallOnHold.isIncoming)
        {
            self.peerOnHold = [mxCallOnHold.room.mxSession getOrCreateUser:mxCallOnHold.callerId];
        }
        else
        {
            // For 1:1 call, find the other peer
            // Else, the room information will be used to display information about the call
            MXWeakify(self);
            [mxCallOnHold.room state:^(MXRoomState *roomState) {
                MXStrongifyAndReturnIfNil(self);
            
                MXUser *theMember = nil;
                NSArray *members = roomState.members.joinedMembers;
                for (MXUser *member in members)
                {
                    if (![member.userId isEqualToString:self->mxCallOnHold.callerId])
                    {
                        theMember = member;
                        break;
                    }
                }

                self.peerOnHold = theMember;
            }];
        }
    }
    else
    {
        [self.onHoldCallContainerView removeGestureRecognizer:self.onHoldCallContainerTapRecognizer];
        [self.onHoldCallContainerView setUserInteractionEnabled:NO];
        self.onHoldCallContainerView.hidden = YES;
        self.peerOnHold = nil;
    }
}

- (void)setPeer:(MXUser *)peer
{
    _peer = peer;
    
    [self updatePeerInfoDisplay];
}

- (void)setPeerOnHold:(MXUser *)peerOnHold
{
    _peerOnHold = peerOnHold;
    
    NSString *peerAvatarURL;
    
    if (_peerOnHold)
    {
        peerAvatarURL = _peerOnHold.avatarUrl;
    }
    else if (mxCall.isConferenceCall)
    {
        peerAvatarURL = mxCallOnHold.room.summary.avatar;
    }
    
    onHoldCallerImageView.imageView.contentMode = UIViewContentModeScaleAspectFill;
    
    if (peerAvatarURL)
    {
        // Suppose avatar url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
        onHoldCallerImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
        onHoldCallerImageView.enableInMemoryCache = YES;
        [onHoldCallerImageView setImageURI:peerAvatarURL
                                  withType:nil
                       andImageOrientation:UIImageOrientationUp
                             toFitViewSize:onHoldCallerImageView.frame.size
                                withMethod:MXThumbnailingMethodCrop
                              previewImage:self.picturePlaceholder
                              mediaManager:self.mainSession.mediaManager];
    }
    else
    {
        onHoldCallerImageView.image = self.picturePlaceholder;
    }
}

- (void)updatePeerInfoDisplay
{
    NSString *peerAvatarURL;
    
    if (_peer)
    {
        peerDisplayName = [_peer displayname];
        if (!peerDisplayName.length)
        {
            peerDisplayName = _peer.userId;
        }
        peerAvatarURL = _peer.avatarUrl;
    }
    else if (mxCall.isConferenceCall)
    {
        peerDisplayName = mxCall.room.summary.displayName;
        peerAvatarURL = mxCall.room.summary.avatar;
    }
    
    if (mxCall.isConsulting)
    {
        callerNameLabel.text = [VectorL10n callConsultingWithUser:peerDisplayName];
    }
    else
    {
        if (mxCall.isVideoCall)
        {
            callerNameLabel.text = [VectorL10n callVideoWithUser:peerDisplayName];
        }
        else
        {
            callerNameLabel.text = [VectorL10n callVoiceWithUser:peerDisplayName];
        }
    }
    
    if (peerAvatarURL)
    {
        // Suppose avatar url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
        callerImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
        callerImageView.enableInMemoryCache = YES;
        [callerImageView setImageURI:peerAvatarURL
                            withType:nil
                 andImageOrientation:UIImageOrientationUp
                       toFitViewSize:callerImageView.frame.size
                          withMethod:MXThumbnailingMethodCrop
                        previewImage:self.picturePlaceholder
                        mediaManager:self.mainSession.mediaManager];
    }
    else
    {
        callerImageView.image = self.picturePlaceholder;
    }
    
    // Round caller image view
    [callerImageView.layer setCornerRadius:callerImageView.frame.size.width / 2];
    callerImageView.clipsToBounds = YES;
}

- (void)setIsRinging:(Boolean)isRinging
{
    if (_isRinging != isRinging)
    {
        if (isRinging)
        {
            NSURL *audioUrl;
            if (mxCall.isIncoming)
            {
                if (self.playRingtone)
                    audioUrl = [self audioURLWithName:@"ring"];
            }
            else
            {
                audioUrl = [self audioURLWithName:@"ringback"];
            }
            
            if (audioUrl)
            {
                [[MXKSoundPlayer sharedInstance] playSoundAt:audioUrl repeat:YES vibrate:mxCall.isIncoming routeToBuiltInReceiver:!mxCall.isIncoming];
            }
        }
        else
        {
            [[MXKSoundPlayer sharedInstance] stopPlayingWithAudioSessionDeactivation:NO];
        }
        
        _isRinging = isRinging;
    }
}

- (void)setDelegate:(id<MXKCallViewControllerDelegate>)delegate
{
    _delegate = delegate;
    
    if (_delegate)
    {
        backToAppButton.hidden = NO;
    }
    else
    {
        backToAppButton.hidden = YES;
    }
}

- (UITapGestureRecognizer *)onHoldCallContainerTapRecognizer
{
    if (_onHoldCallContainerTapRecognizer == nil)
    {
        _onHoldCallContainerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                    action:@selector(onHoldCallContainerTapped:)];
    }
    return _onHoldCallContainerTapRecognizer;
}

- (BOOL)isDisplayingAlert
{
    return errorAlert != nil;
}

- (UIButton *)moreButton
{
    if (mxCall.isVideoCall)
    {
        return _moreButtonForVideo;
    }
    return _moreButtonForVoice;
}

#pragma mark - Sounds

- (NSURL *)audioURLWithName:(NSString *)soundName
{
    return [NSBundle mxk_audioURLFromMXKAssetsBundleWithName:soundName];
}

#pragma mark - Actions

- (void)onHoldCallContainerTapped:(UITapGestureRecognizer *)recognizer
{
    if ([self.delegate respondsToSelector:@selector(callViewControllerDidTapOnHoldCall:)])
    {
        [self.delegate callViewControllerDidTapOnHoldCall:self];
    }
}

- (IBAction)onButtonPressed:(id)sender
{
    if (sender == answerCallButton)
    {
        // If we are here, we have access to the camera
        // The following check is mainly to check microphone access permission
        NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];

        [MXKTools checkAccessForCall:mxCall.isVideoCall
         manualChangeMessageForAudio:[VectorL10n microphoneAccessNotGrantedForCall:appDisplayName]
         manualChangeMessageForVideo:[VectorL10n cameraAccessNotGrantedForCall:appDisplayName]
           showPopUpInViewController:self completionHandler:^(BOOL granted) {

               if (granted)
               {
                   [self->mxCall answer];
               }
           }];
    }
    else if (sender == rejectCallButton || sender == endCallButton)
    {
        if (mxCall.state != MXCallStateEnded)
        {
            [mxCall hangup];
        }
        else
        {
            [self dismiss];
        }
    }
    else if (sender == audioMuteButton)
    {
        mxCall.audioMuted = !mxCall.audioMuted;
        audioMuteButton.selected = mxCall.audioMuted;
    }
    else if (sender == videoMuteButton)
    {
        mxCall.videoMuted = !mxCall.videoMuted;
        videoMuteButton.selected = mxCall.videoMuted;
    }
    else if (sender == _resumeButton)
    {
        [mxCall hold:NO];
    }
    else if (sender == self.moreButton)
    {
        [currentAlert dismissViewControllerAnimated:NO completion:nil];
        
        MXWeakify(self);
        
        NSMutableArray<UIAlertAction *> *actions = [NSMutableArray arrayWithCapacity:4];
        
        if (self.speakerButton == nil)
        {
            //  audio device action
            UIAlertAction *audioDeviceAction = [UIAlertAction actionWithTitle:[VectorL10n callMoreActionsChangeAudioDevice]
                                                                        style:UIAlertActionStyleDefault
                                                                      handler:^(UIAlertAction * action) {
                
                MXStrongifyAndReturnIfNil(self);
                self->currentAlert = nil;
                [self showAudioDeviceOptions];
                
            }];
            
            [actions addObject:audioDeviceAction];
        }
        
        //  check the call can be up/downgraded
        
        //  check the call can send DTMF tones
        if (self.mxCall.supportsDTMF)
        {
            UIAlertAction *dialpadAction = [UIAlertAction actionWithTitle:[VectorL10n callMoreActionsDialpad]
                                                                    style:UIAlertActionStyleDefault
                                                                  handler:^(UIAlertAction * action) {
                
                MXStrongifyAndReturnIfNil(self);
                self->currentAlert = nil;
                [self openDialpad];
                
            }];
            
            [actions addObject:dialpadAction];
        }
        
        //  check the call be holded/unholded
        if (mxCall.supportsHolding)
        {
            NSString *actionLocKey = (mxCall.state == MXCallStateOnHold) ? [VectorL10n callMoreActionsUnhold] : [VectorL10n callMoreActionsHold];
            
            UIAlertAction *holdAction = [UIAlertAction actionWithTitle:actionLocKey
                                                                 style:UIAlertActionStyleDefault
                                                               handler:^(UIAlertAction * action) {
                
                MXStrongifyAndReturnIfNil(self);
                self->currentAlert = nil;
                [self->mxCall hold:(self.mxCall.state != MXCallStateOnHold)];
                
            }];
            
            [actions addObject:holdAction];
        }
        
        //  check the call be transferred
        if (mxCall.supportsTransferring && self.peer)
        {
            UIAlertAction *transferAction = [UIAlertAction actionWithTitle:[VectorL10n callMoreActionsTransfer]
                                                                     style:UIAlertActionStyleDefault
                                                                   handler:^(UIAlertAction * action) {
                
                MXStrongifyAndReturnIfNil(self);
                self->currentAlert = nil;
                
                [self openCallTransfer];
            }];
            
            [actions addObject:transferAction];
        }
        
        if (actions.count > 0)
        {
            //  create the alert
            currentAlert = [UIAlertController alertControllerWithTitle:nil
                                                               message:nil
                                                        preferredStyle:UIAlertControllerStyleActionSheet];
            
            //  add actions
            [actions enumerateObjectsUsingBlock:^(UIAlertAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                [currentAlert addAction:obj];
            }];
            
            //  add cancel action always
            [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                             style:UIAlertActionStyleCancel
                                                           handler:^(UIAlertAction * action) {
                
                MXStrongifyAndReturnIfNil(self);
                self->currentAlert = nil;
                
            }]];
            
            [currentAlert popoverPresentationController].sourceView = self.moreButton;
            [currentAlert popoverPresentationController].sourceRect = self.moreButton.bounds;
            [self presentViewController:currentAlert animated:YES completion:nil];
        }
    }
    else if (sender == speakerButton)
    {
        [self showAudioDeviceOptions];
    }
    else if (sender == cameraSwitchButton)
    {
        switch (mxCall.cameraPosition)
        {
            case AVCaptureDevicePositionFront:
                mxCall.cameraPosition = AVCaptureDevicePositionBack;
                break;
                
            default:
                mxCall.cameraPosition = AVCaptureDevicePositionFront;
                break;
        }
    }
    else if (sender == backToAppButton)
    {
        if (_delegate)
        {
            // Dismiss the view controller whereas the call is still running
            [_delegate dismissCallViewController:self completion:nil];
        }
    }
    else if (sender == _transferButton)
    {
        //  actually transfer the call without consulting
        [self.mainSession.callManager transferCall:mxCall.callWithTransferee
                                                to:mxCall.transferTarget
                                    withTransferee:mxCall.transferee
                                      consultFirst:NO
                                           success:^(NSString * _Nullable newCallId) {
            
        }
                                           failure:^(NSError * _Nullable error) {
            
        }];
    }
    
    [self updateProximityAndSleep];
}

- (void)showAudioDeviceOptions
{
    NSMutableArray<UIAlertAction *> *actions = [NSMutableArray new];
    NSArray<MXiOSAudioOutputRoute *> *availableRoutes = mxCall.audioOutputRouter.availableOutputRoutes;
    
    for (MXiOSAudioOutputRoute *route in availableRoutes)
    {
        //  route action
        NSString *name = route.name;
        if (route.routeType == MXiOSAudioOutputRouteTypeLoudSpeakers)
        {
            name = [VectorL10n callMoreActionsAudioUseDevice];
        }
        MXWeakify(self);
        UIAlertAction *routeAction = [UIAlertAction actionWithTitle:name
                                                              style:UIAlertActionStyleDefault
                                                            handler:^(UIAlertAction * action) {
            
            MXStrongifyAndReturnIfNil(self);
            self->currentAlert = nil;
            [self->mxCall.audioOutputRouter changeCurrentRouteTo:route];
            
        }];
        
        [actions addObject:routeAction];
    }
    
    if (actions.count > 0)
    {
        //  create the alert
        currentAlert = [UIAlertController alertControllerWithTitle:nil
                                                           message:nil
                                                    preferredStyle:UIAlertControllerStyleActionSheet];
        
        for (UIAlertAction *action in actions)
        {
            [currentAlert addAction:action];
        }
        
        //  add cancel action
        MXWeakify(self);
        [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                         style:UIAlertActionStyleCancel
                                                       handler:^(UIAlertAction * action) {
            
            MXStrongifyAndReturnIfNil(self);
            self->currentAlert = nil;
            
        }]];
        
        [currentAlert popoverPresentationController].sourceView = self.moreButton;
        [currentAlert popoverPresentationController].sourceRect = self.moreButton.bounds;
        [self presentViewController:currentAlert animated:YES completion:nil];
    }
}
    
#pragma mark - DTMF

- (void)openDialpad
{
    //  no-op
}

#pragma mark - Call Transfer

- (void)openCallTransfer
{
    //  no-op
}

#pragma mark - MXCallDelegate

- (void)call:(MXCall *)call stateDidChange:(MXCallState)state reason:(MXEvent *)event
{
    // Set default configuration of bottom bar
    endCallButton.hidden = NO;
    rejectCallButton.hidden = YES;
    answerCallButton.hidden = YES;
    self.moreButton.enabled = YES;
    _resumeButton.hidden = state != MXCallStateOnHold;
    _pausedIcon.hidden = state != MXCallStateOnHold && state != MXCallStateRemotelyOnHold;
    _transferButton.hidden = YES;
    
    [localPreviewActivityView stopAnimating];
    
    switch (state)
    {
        case MXCallStateFledgling:
            self.isRinging = NO;
            callStatusLabel.text = [VectorL10n callConnecting];
            break;
        case MXCallStateWaitLocalMedia:
            self.isRinging = NO;
            [self configureSpeakerButton];
            [localPreviewActivityView startAnimating];
            
            // Try to show a special view for incoming view
            [self configureIncomingCallViewIfRequiredWith:call];
            
            break;
        case MXCallStateCreateOffer:
        {
            // When CallKit is enabled and we have an outgoing call, we need to start playing ringback sound
            // only after AVAudioSession will be activated by the system otherwise the sound will be gone.
            // We always receive signal about MXCallStateCreateOffer earlier than the system activates AVAudioSession
            // so we start playing ringback sound only on AVAudioSession activation in handleAudioSessionActivationNotification
            BOOL isCallKitAvailable = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled;
            if (!isCallKitAvailable)
            {
                self.isRinging = YES;
            }
            
            callStatusLabel.text = [VectorL10n callConnecting];
            break;
        }
        case MXCallStateInviteSent:
        {
            callStatusLabel.text = [VectorL10n callRinging];
            break;
        }
        case MXCallStateRinging:
            self.isRinging = YES;
            [self configureSpeakerButton];
            if (call.isVideoCall)
            {
                callStatusLabel.text = [VectorL10n incomingVideoCall];
            }
            else
            {
                callStatusLabel.text = [VectorL10n incomingVoiceCall];
            }
            // Update bottom bar
            endCallButton.hidden = YES;
            rejectCallButton.hidden = NO;
            answerCallButton.hidden = NO;
            
            // Try to show a special view for incoming view
            [self configureIncomingCallViewIfRequiredWith:call];
            
            break;
        case MXCallStateConnecting:
            self.isRinging = NO;
            
            // User has accepted the call and we can remove incomingCallView
            if (self.incomingCallView)
            {
                [UIView transitionWithView:self.view
                                  duration:0.33
                                   options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseOut
                                animations:^{
                                    [self.incomingCallView removeFromSuperview];
                                }
                                completion:^(BOOL finished) {
                                    self.incomingCallView = nil;
                                }];
            }
            
            break;
        case MXCallStateConnected:
            self.isRinging = NO;
            [self updateTimeStatusLabel];

            if (call.isVideoCall)
            {
                self.callerImageView.hidden = YES;
                
                if (call.isConferenceCall)
                {
                    // Do not show self view anymore because it is returned by the conference bridge
                    self.localPreviewContainerView.hidden = YES;

                    // Well, hide does not work. So, shrink the view to nil
                    self.localPreviewContainerView.frame = CGRectZero;
                }
            }
            audioMuteButton.enabled = YES;
            videoMuteButton.enabled = YES;
            speakerButton.enabled = YES;
            cameraSwitchButton.enabled = YES;
            if (call.isConsulting)
            {
                _transferButton.hidden = NO;
            }

            break;
        case MXCallStateOnHold:
            callStatusLabel.text = [VectorL10n callHolded];
            
            break;
        case MXCallStateRemotelyOnHold:
            audioMuteButton.enabled = NO;
            videoMuteButton.enabled = NO;
            speakerButton.enabled = NO;
            cameraSwitchButton.enabled = NO;
            self.moreButton.enabled = NO;
            callStatusLabel.text = [VectorL10n callRemoteHolded:peerDisplayName];
            
            break;
        case MXCallStateInviteExpired:
            // MXCallStateInviteExpired state is sent as an notification
            // MXCall will move quickly to the MXCallStateEnded state
            self.isRinging = NO;
            callStatusLabel.text = [VectorL10n callInviteExpired];
            
            break;
        case MXCallStateEnded:
        {
            self.isRinging = NO;
            callStatusLabel.text = [VectorL10n callEnded];
            
            NSString *soundName = [self soundNameForCallEnding];
            if (soundName)
            {
                NSURL *audioUrl = [self audioURLWithName:soundName];
                [[MXKSoundPlayer sharedInstance] playSoundAt:audioUrl repeat:NO vibrate:NO routeToBuiltInReceiver:YES];
            }
            else
            {
                [[MXKSoundPlayer sharedInstance] stopPlayingWithAudioSessionDeactivation:YES];
            }
            
            // Except in case of call error, quit the screen right now
            if (!errorAlert)
            {
                [self dismiss];
            }

            break;
        }
        default:
            break;
    }
    
    [self updateProximityAndSleep];
}

- (void)call:(MXCall *)call didEncounterError:(NSError *)error reason:(MXCallHangupReason)reason
{
    MXLogDebug(@"[MXKCallViewController] didEncounterError. mxCall.state: %tu. Stop call due to error: %@", mxCall.state, error);

    if (mxCall.state != MXCallStateEnded)
    {
        // Popup the error to the user
        NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
        if (!title)
        {
            title = [VectorL10n error];
        }
        NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
        if (!msg)
        {
            msg = [VectorL10n errorCommonMessage];
        }

        MXWeakify(self);
        errorAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
        
        [errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                                       style:UIAlertActionStyleDefault
                                                     handler:^(UIAlertAction * action) {
            
            MXStrongifyAndReturnIfNil(self);
            self->errorAlert = nil;
            [self dismiss];
            
        }]];
        
        [self presentViewController:errorAlert animated:YES completion:nil];
        
        // And interrupt the call
        [mxCall hangupWithReason:reason];
    }
}

- (void)callConsultingStatusDidChange:(MXCall *)call
{
    [self updatePeerInfoDisplay];
    
    if (call.isConsulting)
    {
        NSString *title = [VectorL10n callTransferToUser:call.transferee.displayname];
        [_transferButton setTitle:title forState:UIControlStateNormal];
        _transferButton.hidden = call.state != MXCallStateConnected;
    }
    else
    {
        _transferButton.hidden = YES;
    }
}

- (void)callAssertedIdentityDidChange:(MXCall *)call
{
    MXAssertedIdentityModel *assertedIdentity = call.assertedIdentity;
    
    if (assertedIdentity)
    {
        //  update caller display name and avatar with the asserted identity
        NSString *peerAvatarURL = assertedIdentity.avatarUrl;
        
        if (assertedIdentity.displayname)
        {
            peerDisplayName = assertedIdentity.displayname;
        }
        else if (assertedIdentity.userId)
        {
            peerDisplayName = assertedIdentity.userId;
        }
        
        if (mxCall.isVideoCall)
        {
            callerNameLabel.text = [VectorL10n callVideoWithUser:peerDisplayName];
        }
        else
        {
            callerNameLabel.text = [VectorL10n callVoiceWithUser:peerDisplayName];
        }
        
        if (peerAvatarURL)
        {
            // Suppose avatar url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
            callerImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
            callerImageView.enableInMemoryCache = YES;
            [callerImageView setImageURI:peerAvatarURL
                                withType:nil
                     andImageOrientation:UIImageOrientationUp
                           toFitViewSize:callerImageView.frame.size
                              withMethod:MXThumbnailingMethodCrop
                            previewImage:self.picturePlaceholder
                            mediaManager:self.mainSession.mediaManager];
        }
        else
        {
            callerImageView.image = self.picturePlaceholder;
        }
        
        [updateStatusTimer fire];
    }
    else
    {
        //  go back to the original display name and avatar
        [self updatePeerInfoDisplay];
    }
}

- (void)callAudioOutputRouteTypeDidChange:(MXCall *)call
{
    [self configureSpeakerButton];
}

- (void)callAvailableAudioOutputsDidChange:(MXCall *)call
{
    
}

#pragma mark - Internal

- (void)removeObservers
{
    if (roomDidFlushDataNotificationObserver)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver];
        roomDidFlushDataNotificationObserver = nil;
    }
    
    if (audioSessionRouteChangeNotificationObserver)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:audioSessionRouteChangeNotificationObserver];
        audioSessionRouteChangeNotificationObserver = nil;
    }
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    if (roomListener && mxCall.room)
    {
        MXWeakify(self);
        [mxCall.room liveTimeline:^(id<MXEventTimeline> liveTimeline) {
            MXStrongifyAndReturnIfNil(self);

            [liveTimeline removeListener:self->roomListener];
            self->roomListener = nil;
        }];
    }
}

- (void)callRoomStateDidChange:(dispatch_block_t)onComplete
{
    // Handle peer here
    if (mxCall.isIncoming)
    {
        self.peer = [mxCall.room.mxSession getOrCreateUser:mxCall.callerId];
        if (onComplete)
        {
            onComplete();
        }
    }
    else
    {
        // For 1:1 call, find the other peer
        // Else, the room information will be used to display information about the call
        if (!mxCall.isConferenceCall)
        {
            MXWeakify(self);
            [mxCall.room state:^(MXRoomState *roomState) {
                MXStrongifyAndReturnIfNil(self);
            
                MXUser *theMember = nil;
                NSArray *members = roomState.members.joinedMembers;
                for (MXUser *member in members)
                {
                    if (![member.userId isEqualToString:self->mxCall.callerId])
                    {
                        theMember = member;
                        break;
                    }
                }

                self.peer = theMember;
                if (onComplete)
                {
                    onComplete();
                }
            }];
        }
        else
        {
            self.peer = nil;
            if (onComplete)
            {
                onComplete();
            }
        }
    }
}

- (BOOL)isBuiltInReceiverAudioOuput
{
#if TARGET_IPHONE_SIMULATOR
    return YES;
#endif
    BOOL isBuiltInReceiverUsed = NO;
    
    // Check whether the audio output is the built-in receiver
    AVAudioSessionRouteDescription *audioRoute = [[AVAudioSession sharedInstance] currentRoute];
    if (audioRoute.outputs.count)
    {
        // TODO: handle the case where multiple outputs are returned
        AVAudioSessionPortDescription *audioOutputs = audioRoute.outputs.firstObject;
        isBuiltInReceiverUsed = ([audioOutputs.portType isEqualToString:AVAudioSessionPortBuiltInReceiver]);
    }
    
    return isBuiltInReceiverUsed;
}

- (NSString *)soundNameForCallEnding
{
    if (mxCall.endReason == MXCallEndReasonUnknown)
        return nil;
    
    if (mxCall.isEstablished)
        return @"callend";
    
    if (mxCall.endReason == MXCallEndReasonBusy || (!mxCall.isIncoming && mxCall.endReason == MXCallEndReasonMissed))
        return @"busy";
    
    return nil;
}

- (void)handleAudioSessionActivationNotification
{
    // It's only relevant for outgoing calls which aren't in connected state
    if (self.mxCall.state >= MXCallStateCreateOffer && self.mxCall.state != MXCallStateConnected && self.mxCall.state != MXCallStateEnded)
    {
        self.isRinging = YES;
    }
}

#pragma mark - UI methods

- (void)configureSpeakerButton
{
    switch (mxCall.audioOutputRouter.currentRoute.routeType)
    {
        case MXiOSAudioOutputRouteTypeBuiltIn:
            self.speakerButton.selected = NO;
            break;
        case MXiOSAudioOutputRouteTypeLoudSpeakers:
        case MXiOSAudioOutputRouteTypeExternalWired:
        case MXiOSAudioOutputRouteTypeExternalBluetooth:
        case MXiOSAudioOutputRouteTypeExternalCar:
            self.speakerButton.selected = YES;
            break;
    }
}

- (void)configureIncomingCallViewIfRequiredWith:(MXCall *)call
{
    if (call.isIncoming && !self.incomingCallView)
    {
        UIView *incomingCallView = [self createIncomingCallView];
        if (incomingCallView)
        {
            self.incomingCallView = incomingCallView;
            [self.view addSubview:incomingCallView];
            
            incomingCallView.translatesAutoresizingMaskIntoConstraints = NO;
            [incomingCallView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:0].active = YES;
            [incomingCallView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:0].active = YES;
            [incomingCallView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:0].active = YES;
            [incomingCallView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:0].active = YES;
        }
    }
}

- (void)updateLocalPreviewLayout
{
    // On IOS 8 and later, the screen size is oriented.
    CGRect bounds = [[UIScreen mainScreen] bounds];
    BOOL isLandscapeOriented = (bounds.size.width > bounds.size.height);
    
    CGFloat maxPreviewFrameSize, minPreviewFrameSize;
    
    if (_localPreviewContainerViewWidthConstraint.constant < _localPreviewContainerViewHeightConstraint.constant)
    {
        maxPreviewFrameSize = _localPreviewContainerViewHeightConstraint.constant;
        minPreviewFrameSize = _localPreviewContainerViewWidthConstraint.constant;
    }
    else
    {
        minPreviewFrameSize = _localPreviewContainerViewHeightConstraint.constant;
        maxPreviewFrameSize = _localPreviewContainerViewWidthConstraint.constant;
    }
    
    if (isLandscapeOriented)
    {
        _localPreviewContainerViewHeightConstraint.constant = minPreviewFrameSize;
        _localPreviewContainerViewWidthConstraint.constant = maxPreviewFrameSize;
    }
    else
    {
        _localPreviewContainerViewHeightConstraint.constant = maxPreviewFrameSize;
        _localPreviewContainerViewWidthConstraint.constant = minPreviewFrameSize;
    }
    
    CGPoint previewOrigin = self.localPreviewContainerView.frame.origin;
    
    if (previewOrigin.x != (bounds.size.width - _localPreviewContainerViewWidthConstraint.constant - kLocalPreviewMargin))
    {
        CGFloat posX = (bounds.size.width - _localPreviewContainerViewWidthConstraint.constant - kLocalPreviewMargin);
        _localPreviewContainerViewLeadingConstraint.constant = posX;
    }
    
    if (previewOrigin.y != kLocalPreviewMargin)
    {
        CGFloat posY = (bounds.size.height - _localPreviewContainerViewHeightConstraint.constant - kLocalPreviewMargin);
        _localPreviewContainerViewTopConstraint.constant = posY;
    }
}

- (void)showOverlayContainer:(BOOL)isShown
{
    if (mxCall && !mxCall.isVideoCall) isShown = YES;
    if (mxCall.state != MXCallStateConnected) isShown = YES;
    
    if (isShown)
    {
        overlayContainerView.hidden = NO;
        if (mxCall && mxCall.isVideoCall)
        {
            [hideOverlayTimer invalidate];
            hideOverlayTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(hideOverlay:) userInfo:nil repeats:NO];
        }
    }
    else
    {
        overlayContainerView.hidden = YES;
    }
}

- (void)toggleOverlay
{
    [self showOverlayContainer:overlayContainerView.isHidden];
}

- (void)hideOverlay:(NSTimer*)theTimer
{
    [self showOverlayContainer:NO];
    hideOverlayTimer = nil;
}

- (void)updateTimeStatusLabel
{
    if (mxCall.state == MXCallStateConnected)
    {
        NSUInteger duration = mxCall.duration / 1000;
        NSUInteger secs = duration % 60;
        NSUInteger mins = (duration - secs) / 60;
        callStatusLabel.text = [NSString stringWithFormat:@"%02tu:%02tu", mins, secs];
    }
}

 - (void)updateProximityAndSleep
 {
     BOOL inCall = (mxCall.state == MXCallStateConnected || mxCall.state == MXCallStateRinging || mxCall.state == MXCallStateInviteSent || mxCall.state == MXCallStateConnecting || mxCall.state == MXCallStateCreateOffer || mxCall.state == MXCallStateCreateAnswer);

     BOOL isBuiltInReceiverUsed = self.isBuiltInReceiverAudioOuput;
     
     // Enable the proximity monitoring when the built in receiver is used as the audio output.
     BOOL enableProxMonitoring = inCall && isBuiltInReceiverUsed;
     
     UIDevice *device = [UIDevice currentDevice];
     if (device && device.isProximityMonitoringEnabled != enableProxMonitoring)
     {
         [device setProximityMonitoringEnabled:enableProxMonitoring];
     }

     // Disable the idle timer during a video call, or during a voice call which is performed with the built-in receiver.
     // Note: if the device is locked, VoIP calling get dropped if an incoming GSM call is received.
     BOOL disableIdleTimer = inCall && (mxCall.isVideoCall || isBuiltInReceiverUsed);
     
     UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
     if (sharedApplication && sharedApplication.isIdleTimerDisabled != disableIdleTimer)
     {
         sharedApplication.idleTimerDisabled = disableIdleTimer;
     }
 }

- (UIView *)createIncomingCallView
{
    return nil;
}

#pragma mark - UIResponder Touch Events

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self.view];
    if ((!self.localPreviewContainerView.hidden) && CGRectContainsPoint(self.localPreviewContainerView.frame, point))
    {
        // Starting to move the local preview view
        if (mxCallOnHold)
        {
            //  if there is a call on hold, do not move local preview for now
            //  TODO: Instead of wholly avoiding mobility of local preview, just avoid the on hold call's corner here
            return;
        }
        isSelectingLocalPreview = YES;
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    isMovingLocalPreview = NO;
    isSelectingLocalPreview = NO;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (isMovingLocalPreview)
    {
        UITouch *touch = [touches anyObject];
        CGPoint point = [touch locationInView:self.view];
        
        CGRect bounds = self.view.bounds;
        CGFloat midX = bounds.size.width / 2.0;
        CGFloat midY = bounds.size.height / 2.0;
        
        CGFloat posX = (point.x < midX) ? 20.0 : (bounds.size.width - _localPreviewContainerViewWidthConstraint.constant - 20.0);
        CGFloat posY = (point.y < midY) ? 20.0 : (bounds.size.height - _localPreviewContainerViewHeightConstraint.constant - 20.0);
        
        _localPreviewContainerViewLeadingConstraint.constant = posX;
        _localPreviewContainerViewTopConstraint.constant = posY;
        
        [self.view setNeedsUpdateConstraints];
    }
    else
    {
        [self toggleOverlay];
    }
    isMovingLocalPreview = NO;
    isSelectingLocalPreview = NO;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self.view];
    
    if (isSelectingLocalPreview)
    {
        isMovingLocalPreview = YES;
        self.localPreviewContainerView.center = point;
    }
}

#pragma mark - UIDeviceOrientationDidChangeNotification

- (void)deviceOrientationDidChange
{
    [self applyDeviceOrientation:NO];
    
    [self showOverlayContainer:YES];
}

- (void)applyDeviceOrientation:(BOOL)forcePortrait
{
    if (mxCall)
    {
        UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
        
        // Set the camera orientation according to the orientation supported by the app
        if (UIDeviceOrientationPortrait == deviceOrientation || UIDeviceOrientationLandscapeLeft == deviceOrientation || UIDeviceOrientationLandscapeRight == deviceOrientation)
        {
            mxCall.selfOrientation = deviceOrientation;
            [self updateLocalPreviewLayout];
        }
        else if (forcePortrait)
        {
            mxCall.selfOrientation = UIDeviceOrientationPortrait;
            [self updateLocalPreviewLayout];
        }        
    }
}

@end
