/******************************************************************************/
/*                                                                            */
/* Copyright Avaya LLC.                                                       */
/*                                                                            */
/******************************************************************************/

#import "SDKManager.h"
#import "MediaManager.h"
#import "NotificationHelper.h"
#import "ActiveCallViewController.h"

#import <AVFoundation/AVFoundation.h>

@interface MediaManager()
<CSVideoInterfaceDelegate,
CSVideoCapturerDelegate>

@property (nonatomic, weak) CSClient *client;
@property (nonatomic, weak) ActiveCallViewController *viewController;
@property (nonatomic) BOOL localVideoStarted;
@property (nonatomic, readonly) id<CSVideoInterface> videoInterface;
@property (nonatomic, readonly) id<CSAudioInterface> audioInterface;
@property (nonatomic, strong) CSVideoCapturerOSX *videoCapturer;

@end

@implementation MediaManager

@synthesize mediaServicesInstance;
@synthesize videoInterface;
@synthesize audioInterface;

- (id)initWithClient: (CSClient *)client {
    
    if (self = [super init]) {
        
        self.client = client;
        
        [self initializeMediaEngine];
        self.localVideoStarted = NO;
    }
    return self;
}

- (void)initializeMediaEngine {
    
    if (self.client) {
        
        mediaServicesInstance = self.client.mediaServices;
        videoInterface = mediaServicesInstance.videoInterface;
        audioInterface = mediaServicesInstance.audioInterface;
        
        videoInterface.delegate = self;
    }
}

- (void)initializeVideoView: (NSViewController *)viewController {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    self.viewController = (ActiveCallViewController *)viewController;
    
    self.localVideoSink = [[CSVideoRendererOSX alloc] init];
    self.remoteVideoSink = [[CSVideoRendererOSX alloc] init];
    
    self.videoCapturer = [[CSVideoCapturerOSX alloc] init];
    
    [self startVideoCapture:[self getFirstCamera] withFormat:nil];
}

// Start Capture
- (void)startVideoCapture:(CSVideoCaptureDevice*)camera withFormat:(CSVideoCaptureFormat*)format {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // If format is not provided, use 720p video stream
    if (!format) {
        
        NSLog(@"%s using 720p capture format", __PRETTY_FUNCTION__);
        format = [[CSVideoCaptureFormat alloc] init];
        format.minRate = 15;
        format.maxRate = 30;
        format.maxWidth = 1280;
        format.maxHeight = 720;
    }
    
    [self setCamera:camera withFormat:format];
}

// Stop Capture
- (void)stopVideoCapture {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [self.localVideoSink handleVideoFrame: nil];
    [self.videoCapturer setVideoSink:nil];
    [self.videoCapturer closeVideoCaptureDeviceWithCompletion:nil];
}

// Print list of all connected cameras
- (void)printCamerasList {
    
    NSArray *cameras = self.videoCapturer.availableDevices;
    
    if (cameras.count == 0) {
        
        NSLog(@"%s No cameras available", __PRETTY_FUNCTION__, nil);
    }
    else {
        
        NSLog(@"%s Cameras:", __PRETTY_FUNCTION__);
        uint count = 0;
        for (CSVideoCaptureDevice* camera in cameras) {
            
            if (camera.isDefaultDevice) {
                
                NSLog(@"%s * [%d] %@ (%@)", __PRETTY_FUNCTION__, count, camera.modelID, camera.localizedName);
            } else {
                
                NSLog(@"%s   [%d] %@ (%@)", __PRETTY_FUNCTION__, count, camera.modelID, camera.localizedName);
            }
            count++;
        }
    }
}

- (CSVideoCaptureDevice*)getFirstCamera {
    
    NSLog(@"%s Select first available camera for Mac device", __PRETTY_FUNCTION__);
    return self.videoCapturer.availableDevices.firstObject;
}

// Set requested CSVideoCaptureDevice as local video source
- (void)setCamera:(CSVideoCaptureDevice*)device withFormat:(CSVideoCaptureFormat *)format{
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (device) {
        
        // If format is not provided, use 720p video stream
        if (!format) {
            
            format = [[CSVideoCaptureFormat alloc] init];
            format.minRate = 15;
            format.maxRate = 30;
            format.maxWidth = 1280;
            format.maxHeight = 720;
        }
        
        // start local rendering
        [self.videoCapturer openVideoCaptureDevice:device withFormat:format withCompletion:^(NSError *error) {
            
            if (error) {
                
                NSLog(@"%s Error [%ld] - %@", __PRETTY_FUNCTION__, (long)error.code, error.localizedDescription);
            } else {
                
                self.localVideoStarted = YES;
            }
        }];
    } else {
        
        // stop local rendering
        [self.videoCapturer closeVideoCaptureDeviceWithCompletion:nil];
        self.localVideoStarted = NO;
    }
}

// Start rendering local video preview
- (BOOL)runLocalVideo {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.videoCapturer.availableDevices.count == 0) {
        
        NSLog(@"%s No cameras available", __PRETTY_FUNCTION__);
        return NO;
    } else {
        
        CSVideoCaptureFormat *format = [[CSVideoCaptureFormat alloc] init];
        format.minRate = 15;
        format.maxRate = 30;
        format.maxWidth = 1280;
        format.maxHeight = 720;
        [self setCamera:self.getFirstCamera withFormat:format];
        
        self.videoCapturer.localVideoSink = self.localVideoSink;
        
        // start local rendering
        self.viewController.localVideoView.hidden = NO;
        
        return YES;
    }
}

// Start rendering remote video preview
- (void)runRemoteVideo:(CSCall*) call {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    //Use first Video Channel
    CSVideoChannel * channel = call.videoChannels.firstObject;
    [[videoInterface getRemoteVideoSource:channel.channelId] setVideoSink:self.remoteVideoSink];
}

// Configure video for outgoing call
- (void)configureVideoForOutgoingCall:(CSCall*) call withVideoMode:(CSVideoMode)videoMode {
    
    __weak CSCall *videoCall = call;
    [call setVideoMode:videoMode completionHandler:^(NSError *error) {
        
        if (error) {
            
            NSLog(@"%s Error while configuring video for outgoing call (%@). Error[%ld] - %@", __PRETTY_FUNCTION__, videoCall, (long)error.code, error.localizedDescription);
            [NotificationHelper displayMessageToUser:[NSString stringWithFormat:@"Error while configuring video for outgoing call"] TAG: __PRETTY_FUNCTION__];
        } else {
            
            NSLog(@"%s Successfully configured video for outgoing call (%@)", __PRETTY_FUNCTION__, videoCall);
        }
    }];
}

- (void)updateVideoChannels:(NSArray *)videoChannels {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    
    if ([videoChannels count] == 0) {
        
        return;
    }
    
    if (!self.localVideoStarted) {
        
        [self runLocalVideo];
    }
    
    CSVideoChannel *videoChannel = videoChannels[0];
    
    if (videoChannel.enabled == YES) {
        
        if (self.viewController.remoteVideoView.hidden == YES) {
            self.viewController.remoteVideoView.hidden = NO;
        }
        
        // Start video rendering if the negotiated video direction is send-receive or receive-only and video rendering is not in progress
        if (videoChannel.negotiatedDirection == CSMediaDirectionSendReceive || videoChannel.negotiatedDirection == CSMediaDirectionReceiveOnly) {
            
            [[videoInterface getRemoteVideoSource:videoChannel.channelId] setVideoSink:self.remoteVideoSink];
            NSLog(@"%s Started video rendering", __PRETTY_FUNCTION__);
        }
        
        // for a held call the media direction is inactive
        if (videoChannel.negotiatedDirection == CSMediaDirectionInactive){
            
            [[videoInterface getRemoteVideoSource:videoChannel.channelId] setVideoSink:nil];
            NSLog(@"%s Stopped video rendering", __PRETTY_FUNCTION__);
        }
        
        // Start video transmission if the negotiated video direction is send-receive or send-only and video transmission is not in progress
        if (videoChannel.negotiatedDirection == CSMediaDirectionSendReceive || videoChannel.negotiatedDirection == CSMediaDirectionSendOnly) {
            
            [self.videoCapturer setVideoSink: [videoInterface getLocalVideoSink: videoChannel.channelId]];
            NSLog(@"%s Started video transmission", __PRETTY_FUNCTION__);
        }
    } else if(videoChannel.enabled == NO) {
        
        if (self.viewController.remoteVideoView.hidden == NO) {
            
            self.viewController.remoteVideoView.hidden = YES;
        }
        
        [[videoInterface getRemoteVideoSource:videoChannel.channelId] setVideoSink:nil];
        [self.videoCapturer setVideoSink: nil];
    }
}

// Add video to an already active audio call
- (void)addVideoToCall:(CSCall*)call withVideoMode:(CSVideoMode)videoMode {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if ([self runLocalVideo] == NO) {
        
        NSLog(@"%s Unable to add video", __PRETTY_FUNCTION__);
        return;
    }
    
    self.viewController.remoteVideoView.hidden = NO;
    
    __weak CSCall *videoCall = call;
    [call setVideoMode:videoMode completionHandler:^(NSError *error) {
        
        if (error) {
            
            NSLog(@"%s Error while adding video to call (%@). Error[%ld] - %@", __PRETTY_FUNCTION__, videoCall, (long)error.code, error.localizedDescription);
            [NotificationHelper displayMessageToUser:[NSString stringWithFormat:@"Error while adding video to call"] TAG: __PRETTY_FUNCTION__];
        } else {
            
            NSLog(@"%s Successfully added video to call (%@)", __PRETTY_FUNCTION__, videoCall);
        }
    }];
}

// Accept incoming video call
- (void)acceptVideoForCall:(CSCall*)call withVideoMode:(CSVideoMode)videoMode {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if ([self runLocalVideo] == NO) {
        
        NSLog(@"%s Unable to accept video", nil);
        return;
    }
    
    self.viewController.remoteVideoView.hidden = NO;
    
    [call acceptVideo:videoMode completionHandler:^(NSError *error) {
        
        if (error) {
            
            self.viewController.remoteVideoView.hidden = YES;
            NSLog(@"%s Error while accepting video of incoming call (%@). Error[%ld] - %@", __PRETTY_FUNCTION__, call, (long)error.code, error.localizedDescription);
            [NotificationHelper displayMessageToUser:[NSString stringWithFormat:@"Error while accepting video of incoming call"] TAG: __PRETTY_FUNCTION__];
        } else {
            
            NSLog(@"%s Successfully accepted video of incoming call (%@)", __PRETTY_FUNCTION__, call);
        }
    }];
}

// Remove video from an active video, call will be audio-only after this
- (void)removeVideoFromCall:(CSCall*)call {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    self.viewController.remoteVideoView.hidden = YES;
    
    __weak CSCall *videoCall = call;
    [call setVideoMode:CSVideoModeNone completionHandler:^(NSError *error) {
        
        if (error) {
            
            NSLog(@"%s Error while removing video of call (%@). Error[%ld] - %@", __PRETTY_FUNCTION__, videoCall, (long)error.code, error.localizedDescription);
            [NotificationHelper displayMessageToUser:[NSString stringWithFormat:@"Error while removing video of call"] TAG: __PRETTY_FUNCTION__];
        } else {
            
            NSLog(@"%s Successfully removed video of call (%@)", __PRETTY_FUNCTION__, videoCall);
        }
    }];
    
    
    CSVideoChannel * channel = [[call videoChannels] firstObject];
    
    [[videoInterface getRemoteVideoSource:channel.channelId] setVideoSink:nil];
    
    [self.localVideoSink handleVideoFrame: nil];
    
    self.videoCapturer.videoSink = nil;
    [self.videoCapturer closeVideoCaptureDeviceWithCompletion:nil];
    self.viewController.localVideoView.hidden = YES;
    self.viewController.remoteVideoView.hidden = YES;
}

// Update direction of video transmission
- (void)updateVideoOfCall:(CSCall*)call withVideoMode:(CSVideoMode)videoMode {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    
    __weak CSCall *videoCall = call;
    // Update video with requested CSVideoMode
    [call setVideoMode:videoMode completionHandler:^(NSError *error) {
        
        if (error) {
            
            NSLog(@"%s Error while updating video of call (%@). Error[%ld] - %@", __PRETTY_FUNCTION__, videoCall, (long)error.code, error.localizedDescription);
            [NotificationHelper displayMessageToUser:[NSString stringWithFormat:@"Error while updating video of call"] TAG: __PRETTY_FUNCTION__];
        } else {
            
            NSLog(@"%s Successfully updated video of call (%@)", __PRETTY_FUNCTION__, videoCall);
        }
    }];
}

- (void)printMicrophonesList {
    
    NSArray *microphones = audioInterface.availableRecordDevices;
    
    if (microphones.count == 0) {
        
        NSLog(@"%s No microphones available", __PRETTY_FUNCTION__);
    } else {
        
        NSLog(@"%s Microphones:", __PRETTY_FUNCTION__);
        uint count = 0;
        for (CSMicrophoneDevice* mc in microphones) {
            
            if ([mc.guid isEqualToString:[audioInterface activeMicrophone].guid]) {
                
                NSLog(@"%s * [%d] %@ (%@)", __PRETTY_FUNCTION__, count, mc.name, mc.guid);
            } else {
                
                NSLog(@"%s   [%d] %@ (%@)", __PRETTY_FUNCTION__, count, mc.name, mc.guid);
            }
            count++;
        }
    }
}

- (void)setMicrophone:(CSMicrophoneDevice*)microphone {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [audioInterface setUserRequestedMicrophone:microphone];
}

- (void)printSpeakersList {
    
    NSArray *speakers = audioInterface.availablePlayDevices;
    
    if (speakers.count == 0) {
        
        NSLog(@"%s No speakers available", __PRETTY_FUNCTION__, nil);
    } else {
        
        NSLog(@"%s Speakers:", __PRETTY_FUNCTION__);
        uint count = 0;
        for (CSSpeakerDevice* speaker in speakers) {
            
            if ([speaker.guid isEqualToString:[audioInterface activeSpeaker].guid]) {
                
                NSLog(@"%s * [%d] %@ (%@)", __PRETTY_FUNCTION__, count, speaker.name, speaker.guid);
            } else {
                
                NSLog(@"%s   [%d] %@ (%@)", __PRETTY_FUNCTION__, count, speaker.name, speaker.guid);
            }
            count++;
        }
    }
}

- (void)setSpeaker:(CSSpeakerDevice*)speaker {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
    self.audioInterface.userRequestedSpeaker = speaker;
}

////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////

#pragma mark - CSVideoInterfaceDelegate

////////////////////////////////////////////////////////////////////////////////

- (void)videoInterface:(id<CSVideoInterface>)videoInterface didChangeRemoteFrameWidth:(int)frameWidth frameHeight:(int)frameHeight forChannelId:(int)channelId {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)videoInterface:(id<CSVideoInterface>)videoInterface didChangeLocalFrameWidth:(int)frameWidth frameHeight:(int)frameHeight {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)videoInterface:(id<CSVideoInterface>)videoInterface onNoFramesFromCameraFor:(int)durationInSec {
    
    NSLog(@"%s No frames from camera for %d seconds", __PRETTY_FUNCTION__, durationInSec);
}

- (void)videoInterface:(id<CSVideoInterface>)videoInterface onPacketTimeOutForWebRTCChannelId:(int)nWebRTCChannelId timeout:(unsigned int)timeout forChannelId:(int)nChannelId {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}


////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////

#pragma mark - CSVideoCapturerDelegate

////////////////////////////////////////////////////////////////////////////////

- (void)videoCapturerRuntimeError:(NSError *)error {
    
    [NotificationHelper displayMessageToUser: [NSString stringWithFormat:@" Error code [%ld] - %@", (long)error.code, error.localizedDescription] TAG: __PRETTY_FUNCTION__];
}

- (void)videoCapturerWasInterrupted {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)videoCapturerInterruptionEnded {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

////////////////////////////////////////////////

- (BOOL)startPlayingTone:(id<CSAudioFilePlayerListener>)audioFilePlayerListener toneToBePlayed: (CSAudioTone)tone playInLoop:(BOOL)loop{
    
    BOOL bResult = NO;
    
    // Stop any previous playback of tone
    [self stopPlayingTone];
    
    self.audioFilePlayer = [audioInterface createAudioFilePlayer:audioFilePlayerListener];
    if ( self.audioFilePlayer ) {
        
        [self.audioFilePlayer setTone:tone];
        [self.audioFilePlayer setIsLoop:loop];
        bResult = [self.audioFilePlayer startPlaying];
    }
    return bResult;
}

- (BOOL)isPlayingTone {
    
    BOOL bResult = NO;
    
    if (self.audioFilePlayer) {
        
        bResult = [self.audioFilePlayer isPlaying];
    }
    return bResult;
}


- (BOOL)stopPlayingTone {
    
    BOOL bResult = NO;
    
    if ([self isPlayingTone]) {
        
        bResult = [self.audioFilePlayer stopPlaying];
    }
    return bResult;
}

////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////

#pragma mark - CSAudioFilePlayerListener

////////////////////////////////////////////////////////////////////////////////

- (void)audioFileDidStartPlaying:(id<CSAudioFilePlayer>)player {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)audioFileDidStopPlaying:(id<CSAudioFilePlayer>)player {
    
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

////////////////////////////////////////////////////////////////////////////////

@end
