#import "RNZoomBroadcastStreamingView.h"
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>

@interface RNZoomBroadcastStreamingView ()

@property (nonatomic, strong) AVSampleBufferDisplayLayer *displayLayer;

// Audio
@property (nonatomic, strong) AVAudioEngine *audioEngine;
@property (nonatomic, strong) AVAudioPlayerNode *audioPlayerNode;
@property (nonatomic, strong) AVAudioFormat *audioInputFormat;
@property (nonatomic, assign) NSInteger configuredSampleRate;
@property (nonatomic, assign) NSInteger configuredChannelNum;
@property (nonatomic, assign) BOOL audioStarted;

@property (nonatomic, assign) BOOL subscribed;

@end

@implementation RNZoomBroadcastStreamingView

- (instancetype)init
{
    return [self initWithFrame:CGRectZero];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _videoResolution = ZoomVideoSDKVideoResolution_720;
        _displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
        _displayLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        _displayLayer.frame = self.bounds;
        [self.layer addSublayer:_displayLayer];
        self.backgroundColor = [UIColor blackColor];
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.displayLayer.frame = self.bounds;
}

#pragma mark - Subscribe / Unsubscribe lifecycle

- (void)willMoveToWindow:(UIWindow *)newWindow
{
    [super willMoveToWindow:newWindow];
    if (newWindow != nil) {
        [self subscribeIfNeeded];
    } else {
        [self unsubscribeIfNeeded];
    }
}

- (void)subscribeIfNeeded
{
    if (self.subscribed) {
        return;
    }
    ZoomVideoSDKBroadcastStreamingViewerHelper *viewer = [[ZoomVideoSDK shareInstance] getBroadcastStreamingViewerHelper];
    if (viewer == nil) {
        NSLog(@"[RNZoomBroadcastStreamingView] viewer helper unavailable; not subscribing");
        return;
    }
    ZoomVideoSDKError vErr = [viewer subscribeVideoWithDelegate:self resolution:self.videoResolution];
    ZoomVideoSDKError aErr = [viewer subscribeAudioWithDelegate:self];
    if (vErr != Errors_Success || aErr != Errors_Success) {
        NSLog(@"[RNZoomBroadcastStreamingView] subscribe error video=%ld audio=%ld", (long)vErr, (long)aErr);
    }
    self.subscribed = (vErr == Errors_Success || aErr == Errors_Success);
}

- (void)unsubscribeIfNeeded
{
    if (!self.subscribed) {
        return;
    }
    ZoomVideoSDKBroadcastStreamingViewerHelper *viewer = [[ZoomVideoSDK shareInstance] getBroadcastStreamingViewerHelper];
    if (viewer != nil) {
        [viewer unSubscribeVideo];
        [viewer unSubscribeAudio];
    }
    self.subscribed = NO;
    [self stopAudioEngine];
}

- (void)dealloc
{
    [self unsubscribeIfNeeded];
}

#pragma mark - Setters

- (void)setVideoResolution:(ZoomVideoSDKVideoResolution)videoResolution
{
    if (_videoResolution == videoResolution) {
        return;
    }
    _videoResolution = videoResolution;
    if (self.subscribed) {
        ZoomVideoSDKBroadcastStreamingViewerHelper *viewer = [[ZoomVideoSDK shareInstance] getBroadcastStreamingViewerHelper];
        if (viewer != nil) {
            [viewer unSubscribeVideo];
            [viewer subscribeVideoWithDelegate:self resolution:videoResolution];
        }
    }
}

#pragma mark - Video: ZoomVideoSDKBroadcastStreamingVideoDelegate

- (void)onPixelBuffer:(CVPixelBufferRef)pixelBuffer rotation:(ZoomVideoSDKVideoRawDataRotation)rotation
{
    if (pixelBuffer == NULL) {
        return;
    }

    // Apply rotation to the display layer transform on main thread.
    CGFloat angle = 0.0;
    switch (rotation) {
        case ZoomVideoSDKVideoRawDataRotationNone:    angle = 0.0;          break;
        case ZoomVideoSDKVideoRawDataRotation90:      angle = M_PI_2;       break;
        case ZoomVideoSDKVideoRawDataRotation180:     angle = M_PI;         break;
        case ZoomVideoSDKVideoRawDataRotation270:     angle = -M_PI_2;      break;
        default: angle = 0.0; break;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        self.displayLayer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
    });

    // Build a CMSampleBuffer wrapping the CVPixelBufferRef and enqueue.
    CMVideoFormatDescriptionRef formatDescription = NULL;
    OSStatus err = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &formatDescription);
    if (err != noErr || formatDescription == NULL) {
        return;
    }

    CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};
    CMSampleBufferRef sampleBuffer = NULL;
    err = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,
                                              pixelBuffer,
                                              true,
                                              NULL,
                                              NULL,
                                              formatDescription,
                                              &timing,
                                              &sampleBuffer);
    CFRelease(formatDescription);

    if (err != noErr || sampleBuffer == NULL) {
        return;
    }

    // Mark for immediate display.
    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
    if (attachments != NULL && CFArrayGetCount(attachments) > 0) {
        CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
    }

    if (self.displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
        [self.displayLayer flush];
    }
    [self.displayLayer enqueueSampleBuffer:sampleBuffer];
    CFRelease(sampleBuffer);
}

#pragma mark - Audio: ZoomVideoSDKBroadcastStreamingAudioDelegate

- (void)onAudioRawDataReceived:(ZoomVideoSDKAudioRawData *)rawData
{
    if (rawData == nil || rawData.buffer == NULL || rawData.bufferLen <= 0) {
        return;
    }

    NSInteger sampleRate = rawData.sampleRate;
    NSInteger channelNum = rawData.channelNum;
    if (sampleRate <= 0 || channelNum <= 0) {
        return;
    }

    if (![self ensureAudioEngineForSampleRate:sampleRate channels:channelNum]) {
        return;
    }

    // SDK delivers 16-bit signed interleaved PCM. Convert to float32 non-interleaved
    // for AVAudioPCMBuffer (player node format).
    NSUInteger bytesPerFrame = sizeof(int16_t) * (NSUInteger)channelNum;
    if (rawData.bufferLen % bytesPerFrame != 0) {
        return;
    }
    AVAudioFrameCount frameCount = (AVAudioFrameCount)(rawData.bufferLen / bytesPerFrame);

    AVAudioPCMBuffer *pcmBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioInputFormat
                                                                frameCapacity:frameCount];
    if (pcmBuffer == nil) {
        return;
    }
    pcmBuffer.frameLength = frameCount;

    const int16_t *src = (const int16_t *)rawData.buffer;
    const float scale = 1.0f / 32768.0f;
    for (AVAudioFrameCount frame = 0; frame < frameCount; frame++) {
        for (NSInteger ch = 0; ch < channelNum; ch++) {
            int16_t sample = src[frame * channelNum + ch];
            pcmBuffer.floatChannelData[ch][frame] = (float)sample * scale;
        }
    }

    @try {
        [self.audioPlayerNode scheduleBuffer:pcmBuffer atTime:nil options:0 completionHandler:nil];
        if (!self.audioPlayerNode.isPlaying) {
            [self.audioPlayerNode play];
        }
    } @catch (NSException *e) {
        NSLog(@"[RNZoomBroadcastStreamingView] schedule audio failed: %@", e.reason);
    }
}

- (BOOL)ensureAudioEngineForSampleRate:(NSInteger)sampleRate channels:(NSInteger)channels
{
    if (self.audioEngine != nil
        && self.configuredSampleRate == sampleRate
        && self.configuredChannelNum == channels
        && self.audioStarted) {
        return YES;
    }

    [self stopAudioEngine];

    AVAudioFormat *format = [[AVAudioFormat alloc]
                              initWithCommonFormat:AVAudioPCMFormatFloat32
                              sampleRate:(double)sampleRate
                              channels:(AVAudioChannelCount)channels
                              interleaved:NO];
    if (format == nil) {
        return NO;
    }

    AVAudioEngine *engine = [[AVAudioEngine alloc] init];
    AVAudioPlayerNode *node = [[AVAudioPlayerNode alloc] init];
    [engine attachNode:node];
    [engine connect:node to:engine.mainMixerNode format:format];

    NSError *err = nil;
    if (![engine startAndReturnError:&err]) {
        NSLog(@"[RNZoomBroadcastStreamingView] audio engine start failed: %@", err);
        return NO;
    }

    self.audioEngine = engine;
    self.audioPlayerNode = node;
    self.audioInputFormat = format;
    self.configuredSampleRate = sampleRate;
    self.configuredChannelNum = channels;
    self.audioStarted = YES;
    return YES;
}

- (void)stopAudioEngine
{
    if (self.audioPlayerNode != nil) {
        @try {
            [self.audioPlayerNode stop];
        } @catch (NSException *e) {}
    }
    if (self.audioEngine != nil) {
        @try {
            [self.audioEngine stop];
        } @catch (NSException *e) {}
    }
    self.audioPlayerNode = nil;
    self.audioEngine = nil;
    self.audioInputFormat = nil;
    self.configuredSampleRate = 0;
    self.configuredChannelNum = 0;
    self.audioStarted = NO;
}

@end
