/**
 *  Copyright 2017 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#import "RTCFileVideoCapturer.h"

#import "base/RTCLogging.h"
#import "base/RTCVideoFrameBuffer.h"
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
#include "rtc_base/system/gcd_helpers.h"

NSString *const kRTCFileVideoCapturerErrorDomain =
    @"org.webrtc.RTC_OBJC_TYPE(RTCFileVideoCapturer)";

typedef NS_ENUM(NSInteger, RTCFileVideoCapturerErrorCode) {
  RTCFileVideoCapturerErrorCode_CapturerRunning = 2000,
  RTCFileVideoCapturerErrorCode_FileNotFound
};

typedef NS_ENUM(NSInteger, RTCFileVideoCapturerStatus) {
  RTCFileVideoCapturerStatusNotInitialized,
  RTCFileVideoCapturerStatusStarted,
  RTCFileVideoCapturerStatusStopped
};

@interface RTC_OBJC_TYPE (RTCFileVideoCapturer)
() @property(nonatomic, assign) CMTime lastPresentationTime;
@property(nonatomic, strong) NSURL *fileURL;
@end

@implementation RTC_OBJC_TYPE (RTCFileVideoCapturer) {
  AVAssetReader *_reader;
  AVAssetReaderTrackOutput *_outTrack;
  RTCFileVideoCapturerStatus _status;
  dispatch_queue_t _frameQueue;
}

@synthesize lastPresentationTime = _lastPresentationTime;
@synthesize fileURL = _fileURL;

- (void)startCapturingFromFileNamed:(NSString *)nameOfFile
                            onError:(RTCFileVideoCapturerErrorBlock)errorBlock {
  if (_status == RTCFileVideoCapturerStatusStarted) {
    NSError *error =
        [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
                            code:RTCFileVideoCapturerErrorCode_CapturerRunning
                        userInfo:@{
                          NSUnderlyingErrorKey : @"Capturer has been started."
                        }];

    errorBlock(error);
    return;
  } else {
    _status = RTCFileVideoCapturerStatusStarted;
  }

  dispatch_async(
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *pathForFile = [self pathForFileName:nameOfFile];
        if (!pathForFile) {
          NSString *errorString = [NSString
              stringWithFormat:@"File %@ not found in bundle", nameOfFile];
          NSError *error = [NSError
              errorWithDomain:kRTCFileVideoCapturerErrorDomain
                         code:RTCFileVideoCapturerErrorCode_FileNotFound
                     userInfo:@{NSUnderlyingErrorKey : errorString}];
          errorBlock(error);
          return;
        }

        self.lastPresentationTime = CMTimeMake(0, 0);

        self.fileURL = [NSURL fileURLWithPath:pathForFile];
        [self setupReaderOnError:errorBlock];
      });
}

- (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock {
  AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil];

  NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
  NSError *error = nil;

  _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
  if (error) {
    errorBlock(error);
    return;
  }

  NSDictionary *options = @{
    (NSString *)kCVPixelBufferPixelFormatTypeKey :
        @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
  };
  _outTrack =
      [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject
                                       outputSettings:options];
  [_reader addOutput:_outTrack];

  [_reader startReading];
  RTCLog(@"File capturer started reading");
  [self readNextBuffer];
}
- (void)stopCapture {
  _status = RTCFileVideoCapturerStatusStopped;
  RTCLog(@"File capturer stopped.");
}

#pragma mark - Private

- (nullable NSString *)pathForFileName:(NSString *)fileName {
  NSArray *nameComponents = [fileName componentsSeparatedByString:@"."];
  if (nameComponents.count != 2) {
    return nil;
  }

  NSString *path = [[NSBundle mainBundle] pathForResource:nameComponents[0]
                                                   ofType:nameComponents[1]];
  return path;
}

- (dispatch_queue_t)frameQueue {
  if (!_frameQueue) {
    _frameQueue = RTCDispatchQueueCreateWithTarget(
        "org.webrtc.filecapturer.video",
        DISPATCH_QUEUE_SERIAL,
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
  }
  return _frameQueue;
}

- (void)readNextBuffer {
  if (_status == RTCFileVideoCapturerStatusStopped) {
    [_reader cancelReading];
    _reader = nil;
    return;
  }

  if (_reader.status == AVAssetReaderStatusCompleted) {
    [_reader cancelReading];
    _reader = nil;
    [self setupReaderOnError:nil];
    return;
  }

  CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer];
  if (!sampleBuffer) {
    [self readNextBuffer];
    return;
  }
  if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
      !CMSampleBufferIsValid(sampleBuffer) ||
      !CMSampleBufferDataIsReady(sampleBuffer)) {
    CFRelease(sampleBuffer);
    [self readNextBuffer];
    return;
  }

  [self publishSampleBuffer:sampleBuffer];
}

- (void)publishSampleBuffer:(CMSampleBufferRef)sampleBuffer {
  CMTime presentationTime =
      CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
  Float64 presentationDifference =
      CMTimeGetSeconds(CMTimeSubtract(presentationTime, _lastPresentationTime));
  _lastPresentationTime = presentationTime;
  int64_t presentationDifferenceRound =
      lroundf(presentationDifference * NSEC_PER_SEC);

  __block dispatch_source_t timer = [self createStrictTimer];
  // Strict timer that will fire `presentationDifferenceRound` ns from now and
  // never again.
  dispatch_source_set_timer(
      timer,
      dispatch_time(DISPATCH_TIME_NOW, presentationDifferenceRound),
      DISPATCH_TIME_FOREVER,
      0);
  dispatch_source_set_event_handler(timer, ^{
    dispatch_source_cancel(timer);
    timer = nil;

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    if (!pixelBuffer) {
      CFRelease(sampleBuffer);
      dispatch_async(
          dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self readNextBuffer];
          });
      return;
    }

    RTC_OBJC_TYPE(RTCCVPixelBuffer) *rtcPixelBuffer =
        [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc]
            initWithPixelBuffer:pixelBuffer];
    NSTimeInterval timeStampSeconds = CACurrentMediaTime();
    int64_t timeStampNs = lroundf(timeStampSeconds * NSEC_PER_SEC);
    RTC_OBJC_TYPE(RTCVideoFrame) *videoFrame =
        [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:rtcPixelBuffer
                                                    rotation:0
                                                 timeStampNs:timeStampNs];
    CFRelease(sampleBuffer);

    dispatch_async(
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          [self readNextBuffer];
        });

    [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
  });
  dispatch_activate(timer);
}

- (dispatch_source_t)createStrictTimer {
  dispatch_source_t timer = dispatch_source_create(
      DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, [self frameQueue]);
  return timer;
}

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

@end
