// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#import "OCTTox+Private.h"
#import "OCTToxAV+Private.h"
#import "OCTLogging.h"

ToxAV *(*_toxav_new)(Tox *tox, TOXAV_ERR_NEW *error);
uint32_t (*_toxav_iteration_interval)(const ToxAV *toxAV);
void (*_toxav_iterate)(ToxAV *toxAV);
void (*_toxav_kill)(ToxAV *toxAV);

bool (*_toxav_call)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error);
bool (*_toxav_answer)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error);
bool (*_toxav_call_control)(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error);

bool (*_toxav_audio_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error);
bool (*_toxav_video_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error);

bool (*_toxav_audio_send_frame)(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error);
bool (*_toxav_video_send_frame)(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error);


@interface OCTToxAV ()

@property (assign, nonatomic) ToxAV *toxAV;

@property (strong, nonatomic) dispatch_source_t timer;

@property (assign, nonatomic) uint64_t previousIterate;

@end

@implementation OCTToxAV

#pragma mark -  Lifecycle
- (instancetype)initWithTox:(OCTTox *)tox error:(NSError **)error
{
    self = [super init];

    if (! self) {
        return nil;
    }

    OCTLogVerbose(@"init called");

    [self setupCFunctions];

    TOXAV_ERR_NEW cError;
    _toxAV = _toxav_new(tox.tox, &cError);

    [self fillError:error withCErrorInit:cError];

    [self setupCallbacks];

    return self;
}

- (void)start
{
    OCTLogVerbose(@"start method called");

    @synchronized(self) {
        if (self.timer) {
            OCTLogWarn(@"already started");
            return;
        }

        dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTToxAVQueue", NULL);
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

        [self updateTimerIntervalIfNeeded];

        __weak OCTToxAV *weakSelf = self;
        dispatch_source_set_event_handler(self.timer, ^{
            OCTToxAV *strongSelf = weakSelf;
            if (! strongSelf) {
                return;
            }

            _toxav_iterate(strongSelf.toxAV);

            [strongSelf updateTimerIntervalIfNeeded];
        });

        dispatch_resume(self.timer);
    }
    OCTLogInfo(@"started");
}

- (void)stop
{
    OCTLogVerbose(@"stop method called");

    @synchronized(self) {
        if (! self.timer) {
            OCTLogWarn(@"toxav isn't running, nothing to stop");
            return;
        }

        dispatch_source_cancel(self.timer);
        self.timer = nil;
    }

    OCTLogInfo(@"stopped");
}

- (void)dealloc
{
    [self stop];
    _toxav_kill(self.toxAV);
    OCTLogVerbose(@"dealloc called, toxav killed");
}

#pragma mark - Call Methods

- (BOOL)callFriendNumber:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitRate error:(NSError **)error
{
    TOXAV_ERR_CALL cError;
    BOOL status = _toxav_call(self.toxAV, friendNumber, audioBitRate, videoBitRate, &cError);

    [self fillError:error withCErrorCall:cError];

    return status;
}

- (BOOL)answerIncomingCallFromFriend:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitrate error:(NSError **)error
{
    TOXAV_ERR_ANSWER cError;
    BOOL status = _toxav_answer(self.toxAV, friendNumber, audioBitRate, videoBitrate, &cError);

    [self fillError:error withCErrorAnswer:cError];

    return status;
}

- (BOOL)sendCallControl:(OCTToxAVCallControl)control toFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
    TOXAV_CALL_CONTROL cControl;

    switch (control) {
        case OCTToxAVCallControlResume:
            cControl = TOXAV_CALL_CONTROL_RESUME;
            break;
        case OCTToxAVCallControlPause:
            cControl = TOXAV_CALL_CONTROL_PAUSE;
            break;
        case OCTToxAVCallControlCancel:
            cControl = TOXAV_CALL_CONTROL_CANCEL;
            break;
        case OCTToxAVCallControlMuteAudio:
            cControl = TOXAV_CALL_CONTROL_MUTE_AUDIO;
            break;
        case OCTToxAVCallControlUnmuteAudio:
            cControl = TOXAV_CALL_CONTROL_UNMUTE_AUDIO;
            break;
        case OCTToxAVCallControlHideVideo:
            cControl = TOXAV_CALL_CONTROL_HIDE_VIDEO;
            break;
        case OCTToxAVCallControlShowVideo:
            cControl = TOXAV_CALL_CONTROL_SHOW_VIDEO;
            break;
    }

    TOXAV_ERR_CALL_CONTROL cError;

    BOOL status = _toxav_call_control(self.toxAV, friendNumber, cControl, &cError);

    [self fillError:error withCErrorControl:cError];

    return status;
}

#pragma mark - Controlling bit rates

- (BOOL)setAudioBitRate:(OCTToxAVAudioBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
    TOXAV_ERR_BIT_RATE_SET cError;

    BOOL status = _toxav_audio_set_bit_rate(self.toxAV, friendNumber, bitRate, &cError);

    [self fillError:error withCErrorSetBitRate:cError];

    OCTLogVerbose(@"setAudioBitRate:%lu, force:%d, friend:%d", (long)bitRate, force, friendNumber);

    return status;
}

- (BOOL)setVideoBitRate:(OCTToxAVVideoBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
    TOXAV_ERR_BIT_RATE_SET cError;

    BOOL status = _toxav_video_set_bit_rate(self.toxAV, friendNumber, bitRate, &cError);

    [self fillError:error withCErrorSetBitRate:cError];

    return status;
}

#pragma mark - Sending frames
- (BOOL)sendAudioFrame:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount
              channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate
              toFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
    // TOXAUDIO: -outgoing-audio-
    TOXAV_ERR_SEND_FRAME cError;

    BOOL status = _toxav_audio_send_frame(self.toxAV, friendNumber,
                                          pcm, sampleCount,
                                          channels, sampleRate, &cError);

    [self fillError:error withCErrorSendFrame:cError];

    return status;
}

- (BOOL)sendVideoFrametoFriend:(OCTToxFriendNumber)friendNumber
                         width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height
                        yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane
                        vPlane:(OCTToxAVPlaneData *)vPlane
                         error:(NSError **)error
{
    TOXAV_ERR_SEND_FRAME cError;
    BOOL status = _toxav_video_send_frame(self.toxAV, friendNumber, width, height, yPlane, uPlane, vPlane, &cError);

    [self fillError:error withCErrorSendFrame:cError];

    return status;
}

#pragma mark - Private

- (void)setupCFunctions
{
    _toxav_new = toxav_new;
    _toxav_iteration_interval = toxav_iteration_interval;
    _toxav_iterate = toxav_iterate;
    _toxav_kill = toxav_kill;

    _toxav_call = toxav_call;
    _toxav_answer = toxav_answer;
    _toxav_call_control = toxav_call_control;

    _toxav_audio_set_bit_rate = toxav_audio_set_bit_rate;
    _toxav_video_set_bit_rate = toxav_video_set_bit_rate;

    _toxav_audio_send_frame = toxav_audio_send_frame;
    _toxav_video_send_frame = toxav_video_send_frame;
}

- (void)setupCallbacks
{
    toxav_callback_call(_toxAV, callIncomingCallback, (__bridge void *)(self));
    toxav_callback_call_state(_toxAV, callStateCallback, (__bridge void *)(self));
    toxav_callback_audio_bit_rate(_toxAV, audioBitRateStatusCallback, (__bridge void *)(self));
    toxav_callback_video_bit_rate(_toxAV, videoBitRateStatusCallback, (__bridge void *)(self));
    toxav_callback_audio_receive_frame(_toxAV, receiveAudioFrameCallback, (__bridge void *)(self));
    toxav_callback_video_receive_frame(_toxAV, receiveVideoFrameCallback, (__bridge void *)(self));
}

- (BOOL)fillError:(NSError **)error withCErrorInit:(TOXAV_ERR_NEW)cError
{
    if (! error || (cError == TOXAV_ERR_NEW_OK)) {
        return NO;
    }

    OCTToxAVErrorInitCode code = OCTToxAVErrorInitCodeUnknown;
    NSString *description = @"Cannot initialize ToxAV";
    NSString *failureReason = nil;

    switch (cError) {
        case TOXAV_ERR_NEW_OK:
            NSAssert(NO, @"We shouldn't be here!");
            break;
        case TOXAV_ERR_NEW_NULL:
            code = OCTToxAVErrorInitNULL;
            failureReason = @"One of the arguments to the function was NULL when it was not expected.";
            break;
        case TOXAV_ERR_NEW_MALLOC:
            code = OCTToxAVErrorInitCodeMemoryError;
            failureReason = @"Memory allocation failure while trying to allocate structures required for the A/V session.";
            break;
        case TOXAV_ERR_NEW_MULTIPLE:
            code = OCTToxAVErrorInitMultiple;
            failureReason = @"Attempted to create a second session for the same Tox instance.";
            break;
    }
    *error = [self createErrorWithCode:code description:description failureReason:failureReason];

    return YES;
}

- (BOOL)fillError:(NSError **)error withCErrorCall:(TOXAV_ERR_CALL)cError
{
    if (! error || (cError == TOXAV_ERR_CALL_OK)) {
        return NO;
    }

    OCTToxAVErrorCall code = OCTToxAVErrorCallUnknown;
    NSString *description = @"Could not make call";
    NSString *failureReason = nil;

    switch (cError) {
        case TOXAV_ERR_CALL_OK:
            NSAssert(NO, @"We shouldn't be here!");
            break;
        case TOXAV_ERR_CALL_MALLOC:
            code = OCTToxAVErrorCallMalloc;
            failureReason = @"A resource allocation error occured while trying to create the structures required for the call.";
            break;
        case TOXAV_ERR_CALL_SYNC:
            code = OCTToxAVErrorCallSync;
            failureReason = @"Synchronization error occurred.";
            break;
        case TOXAV_ERR_CALL_FRIEND_NOT_FOUND:
            code = OCTToxAVErrorCallFriendNotFound;
            failureReason = @"The friend number did not designate a valid friend.";
            break;
        case TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED:
            code = OCTToxAVErrorCallFriendNotConnected;
            failureReason = @"The friend was valid, but not currently connected";
            break;
        case TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL:
            code = OCTToxAVErrorCallAlreadyInCall;
            failureReason = @"Attempted to call a friend while already in an audio or video call with them.";
            break;
        case TOXAV_ERR_CALL_INVALID_BIT_RATE:
            code = OCTToxAVErrorCallInvalidBitRate;
            failureReason = @"Audio or video bit rate is invalid";
            break;
    }

    *error = [self createErrorWithCode:code description:description failureReason:failureReason];

    return YES;
}

- (BOOL)fillError:(NSError **)error withCErrorAnswer:(TOXAV_ERR_ANSWER)cError
{
    if (! error || (cError == TOXAV_ERR_ANSWER_OK)) {
        return NO;
    }

    OCTToxAVErrorAnswer code = OCTToxAVErrorAnswerUnknown;
    NSString *description = @"Could not answer call";
    NSString *failureReason = nil;

    switch (cError) {
        case TOXAV_ERR_ANSWER_OK:
            NSAssert(NO, @"We shouldn't be here!");
            break;
        case TOXAV_ERR_ANSWER_SYNC:
            code = OCTToxAVErrorAnswerSync;
            break;
        case TOXAV_ERR_ANSWER_CODEC_INITIALIZATION:
            code = OCTToxAVErrorAnswerCodecInitialization;
            break;
        case TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING:
            code = OCTToxAVErrorAnswerFriendNotCalling;
            break;
        case TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND:
            code = OCTToxAVErrorAnswerFriendNotFound;
            break;
        case TOXAV_ERR_ANSWER_INVALID_BIT_RATE:
            code = OCTToxAVErrorAnswerInvalidBitRate;
            break;
    }

    *error = [self createErrorWithCode:code description:description failureReason:failureReason];

    return YES;
}

- (BOOL)fillError:(NSError **)error withCErrorControl:(TOXAV_ERR_CALL_CONTROL)cError
{
    if (! error || (cError == TOXAV_ERR_CALL_CONTROL_OK)) {
        return NO;
    }

    OCTToxErrorCallControl code = OCTToxAVErrorControlUnknown;
    NSString *description = @"Unable set control";
    NSString *failureReason = nil;

    switch (cError) {
        case TOXAV_ERR_CALL_CONTROL_OK:
            NSAssert(NO, @"We shouldn't be here!");
            break;
        case TOXAV_ERR_CALL_CONTROL_SYNC:
            code = OCTToxAVErrorControlSync;
            failureReason = @"Synchronization error occurred.";
            break;
        case TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND:
            code = OCTToxAVErrorControlFriendNotFound;
            failureReason = @"The friend number passed did not designate a valid friend.";
            break;
        case TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL:
            code = OCTToxAVErrorControlFriendNotInCall;
            failureReason = @"This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid control.";
            break;
        case TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION:
            code = OCTToxAVErrorControlInvaldTransition;
            failureReason = @"Happens if user tried to pause an already paused call or if trying to resume a call that is not paused.";
            break;
    }

    *error = [self createErrorWithCode:code description:description failureReason:failureReason];

    return YES;
}

- (BOOL)fillError:(NSError **)error withCErrorSetBitRate:(TOXAV_ERR_BIT_RATE_SET)cError
{
    if (! error || (cError == TOXAV_ERR_BIT_RATE_SET_OK)) {
        return NO;
    }

    OCTToxAVErrorSetBitRate code = OCTToxAVErrorSetBitRateUnknown;
    NSString *description = @"Unable to set audio/video bitrate";
    NSString *failureReason = nil;

    switch (cError) {
        case TOXAV_ERR_BIT_RATE_SET_OK:
            NSAssert(NO, @"We shouldn't be here!");
            break;
        case TOXAV_ERR_BIT_RATE_SET_SYNC:
            code = OCTToxAVErrorSetBitRateSync;
            failureReason = @"Synchronization error occurred.";
            break;
        case TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE:
            code = OCTToxAVErrorSetBitRateInvalidBitRate;
            failureReason = @"The bit rate passed was not one of the supported values.";
            break;
        case TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND:
            code = OCTToxAVErrorSetBitRateFriendNotFound;
            failureReason = @"The friend number passed did not designate a valid friend";
            break;
        case TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL:
            code = OCTToxAVErrorSetBitRateFriendNotInCall;
            failureReason = @"This client is currently not in a call with the friend";
            break;
    }

    *error = [self createErrorWithCode:code description:description failureReason:failureReason];

    return YES;
}

- (BOOL)fillError:(NSError **)error withCErrorSendFrame:(TOXAV_ERR_SEND_FRAME)cError
{
    if (! error || (cError == TOXAV_ERR_SEND_FRAME_OK)) {
        return NO;
    }

    OCTToxAVErrorSendFrame code = OCTToxAVErrorSendFrameUnknown;
    NSString *description = @"Failed to send audio/video frame";
    NSString *failureReason = @"Unable to sending audio/video frame";
    switch (cError) {
        case TOXAV_ERR_SEND_FRAME_OK:
            NSAssert(NO, @"We shouldn't be here!");
            break;
        case TOXAV_ERR_SEND_FRAME_NULL:
            code = OCTToxAVErrorSendFrameNull;
            failureReason = @"In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL.";
            break;
        case TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND:
            code = OCTToxAVErrorSendFrameFriendNotFound;
            failureReason = @"The friend number passed did not designate a valid friend.";
            break;
        case TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL:
            code = OCTToxAVErrorSendFrameFriendNotInCall;
            failureReason = @"This client is currently not in a call with the friend";
            break;
        case TOXAV_ERR_SEND_FRAME_SYNC:
            code = OCTToxAVErrorSendFrameSync;
            failureReason = @"Synchronization error occurred";
            break;
        case TOXAV_ERR_SEND_FRAME_INVALID:
            code = OCTToxAVErrorSendFrameInvalid;
            failureReason = @"One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported";
            break;
        case TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED:
            code = OCTToxAVErrorSendFramePayloadTypeDisabled;
            failureReason = @"Either friend turned off audio/video receiving or we turned off sending for the said payload.";
            break;
        case TOXAV_ERR_SEND_FRAME_RTP_FAILED:
            code = OCTToxAVErrorSendFrameRTPFailed;
            failureReason = @"Failed to push frame through rtp interface";
            break;
    }

    *error = [self createErrorWithCode:code description:description failureReason:failureReason];

    return YES;
}

- (NSError *)createErrorWithCode:(NSUInteger)code
                     description:(NSString *)description
                   failureReason:(NSString *)failureReason
{
    NSMutableDictionary *userInfo = [NSMutableDictionary new];

    if (description) {
        userInfo[NSLocalizedDescriptionKey] = description;
    }

    if (failureReason) {
        userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
    }

    return [NSError errorWithDomain:kOCTToxAVErrorDomain code:code userInfo:userInfo];
}

- (void)updateTimerIntervalIfNeeded
{
    uint64_t nextIterate = _toxav_iteration_interval(self.toxAV) * (NSEC_PER_SEC / 1000);

    if (self.previousIterate == nextIterate) {
        return;
    }

    self.previousIterate = nextIterate;
    dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, nextIterate), nextIterate, nextIterate / 5);
}

@end

#pragma mark - Callbacks

void callIncomingCallback(ToxAV *cToxAV,
                          uint32_t friendNumber,
                          bool audioEnabled,
                          bool videoEnabled,
                          void *userData)
{
    OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;

    dispatch_async(dispatch_get_main_queue(), ^{
        OCTLogCInfo(@"callIncomingCallback from friend %lu with audio:%d with video:%d", toxAV, (unsigned long)friendNumber, audioEnabled, videoEnabled);
        if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveCallAudioEnabled:videoEnabled:friendNumber:)]) {
            [toxAV.delegate toxAV:toxAV receiveCallAudioEnabled:audioEnabled videoEnabled:videoEnabled friendNumber:friendNumber];
        }
    });
}

void callStateCallback(ToxAV *cToxAV,
                       uint32_t friendNumber,
                       TOXAV_FRIEND_CALL_STATE cState,
                       void *userData)
{
    OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;

    dispatch_async(dispatch_get_main_queue(), ^{

        OCTLogCInfo(@"callStateCallback from friend %d with state: %d", toxAV, friendNumber, cState);

        OCTToxAVCallState state = 0;

        if (cState & TOXAV_FRIEND_CALL_STATE_ERROR) {
            state |= OCTToxAVFriendCallStateError;
        }
        if (cState & TOXAV_FRIEND_CALL_STATE_FINISHED) {
            state |= OCTToxAVFriendCallStateFinished;
        }
        if (cState & TOXAV_FRIEND_CALL_STATE_SENDING_A) {
            state |= OCTToxAVFriendCallStateSendingAudio;
        }
        if (cState & TOXAV_FRIEND_CALL_STATE_SENDING_V) {
            state |= OCTToxAVFriendCallStateSendingVideo;
        }
        if (cState & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A) {
            state |= OCTToxAVFriendCallStateAcceptingAudio;
        }
        if (cState & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V) {
            state |= OCTToxAVFriendCallStateAcceptingVideo;
        }

        if ([toxAV.delegate respondsToSelector:@selector(toxAV:callStateChanged:friendNumber:)]) {
            [toxAV.delegate toxAV:toxAV callStateChanged:state friendNumber:friendNumber];
        }
    });
}

void audioBitRateStatusCallback(ToxAV *cToxAV,
                                uint32_t friendNumber,
                                uint32_t bit_rate,
                                void *userData)
{
    OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;

    dispatch_async(dispatch_get_main_queue(), ^{
        OCTLogCInfo(@"audioBitRateStatusCallback from friend %d bitRate: %d", toxAV, friendNumber, bit_rate);
        if ([toxAV.delegate respondsToSelector:@selector(toxAV:audioBitRateStatus:forFriendNumber:)]) {
            [toxAV.delegate toxAV:toxAV audioBitRateStatus:bit_rate forFriendNumber:friendNumber];
        }
    });
}

void videoBitRateStatusCallback(ToxAV *cToxAV,
                                uint32_t friendNumber,
                                uint32_t bit_rate,
                                void *userData)
{
    OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;

    dispatch_async(dispatch_get_main_queue(), ^{
        OCTLogCInfo(@"videoBitRateStatusCallback from friend %d bitRate: %d", toxAV, friendNumber, bit_rate);
        if ([toxAV.delegate respondsToSelector:@selector(toxAV:videoBitRateStatus:forFriendNumber:)]) {
            [toxAV.delegate toxAV:toxAV videoBitRateStatus:bit_rate forFriendNumber:friendNumber];
        }
    });
}

void receiveAudioFrameCallback(ToxAV *cToxAV,
                               uint32_t friendNumber,
                               OCTToxAVPCMData *pcm,
                               OCTToxAVSampleCount sampleCount,
                               OCTToxAVChannels channels,
                               OCTToxAVSampleRate sampleRate,
                               void *userData)
{
    OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;

    // TOXAUDIO: -incoming-audio-
    if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveAudio:sampleCount:channels:sampleRate:friendNumber:)]) {
        [toxAV.delegate toxAV:toxAV receiveAudio:pcm sampleCount:sampleCount channels:channels sampleRate:sampleRate friendNumber:friendNumber];
    }
}

void receiveVideoFrameCallback(ToxAV *cToxAV,
                               uint32_t friendNumber,
                               OCTToxAVVideoWidth width,
                               OCTToxAVVideoHeight height,
                               OCTToxAVPlaneData *yPlane, OCTToxAVPlaneData *uPlane, OCTToxAVPlaneData *vPlane,
                               OCTToxAVStrideData yStride, OCTToxAVStrideData uStride, OCTToxAVStrideData vStride,
                               void *userData)
{
    OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;

    if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveVideoFrameWithWidth:height:yPlane:uPlane:vPlane:yStride:uStride:vStride:friendNumber:)]) {
        [toxAV.delegate toxAV:toxAV
   receiveVideoFrameWithWidth:width height:height
                       yPlane:yPlane uPlane:uPlane vPlane:vPlane
                      yStride:yStride uStride:uStride vStride:vStride
                 friendNumber:friendNumber];
    }

}
