/*
 *  Copyright 2018 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.
 */

#include "common_video/h264/h264_common.h"
#include "components/video_codec/nalu_rewriter.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/gunit.h"

#import <XCTest/XCTest.h>

#if TARGET_OS_IPHONE
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#endif

@interface NaluRewriterTests : XCTestCase

@end

static const uint8_t NALU_TEST_DATA_0[] = {0xAA, 0xBB, 0xCC};
static const uint8_t NALU_TEST_DATA_1[] = {0xDE, 0xAD, 0xBE, 0xEF};

// clang-format off
static const uint8_t SPS_PPS_BUFFER[] = {
  // SPS nalu.
  0x00, 0x00, 0x00, 0x01, 0x27, 0x42, 0x00, 0x1E, 0xAB, 0x40, 0xF0, 0x28,
  0xD3, 0x70, 0x20, 0x20, 0x20, 0x20,
  // PPS nalu.
  0x00, 0x00, 0x00, 0x01, 0x28, 0xCE, 0x3C, 0x30};
// clang-format on

@implementation NaluRewriterTests

- (void)testCreateVideoFormatDescription {
  CMVideoFormatDescriptionRef description =
      webrtc::CreateVideoFormatDescription(SPS_PPS_BUFFER, arraysize(SPS_PPS_BUFFER));
  XCTAssertTrue(description);
  if (description) {
    CFRelease(description);
    description = nullptr;
  }

  // clang-format off
  const uint8_t sps_pps_not_at_start_buffer[] = {
    // Add some non-SPS/PPS NALUs at the beginning
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x01,
    0xAB, 0x33, 0x21,
    // SPS nalu.
    0x00, 0x00, 0x01, 0x27, 0x42, 0x00, 0x1E, 0xAB, 0x40, 0xF0, 0x28, 0xD3,
    0x70, 0x20, 0x20, 0x20, 0x20,
    // PPS nalu.
    0x00, 0x00, 0x01, 0x28, 0xCE, 0x3C, 0x30};
  // clang-format on
  description = webrtc::CreateVideoFormatDescription(sps_pps_not_at_start_buffer,
                                                     arraysize(sps_pps_not_at_start_buffer));

  XCTAssertTrue(description);

  if (description) {
    CFRelease(description);
    description = nullptr;
  }

  const uint8_t other_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x28};
  XCTAssertFalse(webrtc::CreateVideoFormatDescription(other_buffer, arraysize(other_buffer)));
}

- (void)testReadEmptyInput {
  const uint8_t annex_b_test_data[] = {0x00};
  webrtc::AnnexBBufferReader reader(annex_b_test_data, 0);
  const uint8_t* nalu = nullptr;
  size_t nalu_length = 0;
  XCTAssertEqual(0u, reader.BytesRemaining());
  XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(nullptr, nalu);
  XCTAssertEqual(0u, nalu_length);
}

- (void)testReadSingleNalu {
  const uint8_t annex_b_test_data[] = {0x00, 0x00, 0x00, 0x01, 0xAA};
  webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data));
  const uint8_t* nalu = nullptr;
  size_t nalu_length = 0;
  XCTAssertEqual(arraysize(annex_b_test_data), reader.BytesRemaining());
  XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(annex_b_test_data + 4, nalu);
  XCTAssertEqual(1u, nalu_length);
  XCTAssertEqual(0u, reader.BytesRemaining());
  XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(nullptr, nalu);
  XCTAssertEqual(0u, nalu_length);
}

- (void)testReadSingleNalu3ByteHeader {
  const uint8_t annex_b_test_data[] = {0x00, 0x00, 0x01, 0xAA};
  webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data));
  const uint8_t* nalu = nullptr;
  size_t nalu_length = 0;
  XCTAssertEqual(arraysize(annex_b_test_data), reader.BytesRemaining());
  XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(annex_b_test_data + 3, nalu);
  XCTAssertEqual(1u, nalu_length);
  XCTAssertEqual(0u, reader.BytesRemaining());
  XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(nullptr, nalu);
  XCTAssertEqual(0u, nalu_length);
}

- (void)testReadMissingNalu {
  // clang-format off
  const uint8_t annex_b_test_data[] = {0x01,
                                       0x00, 0x01,
                                       0x00, 0x00, 0x00, 0xFF};
  // clang-format on
  webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data));
  const uint8_t* nalu = nullptr;
  size_t nalu_length = 0;
  XCTAssertEqual(0u, reader.BytesRemaining());
  XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(nullptr, nalu);
  XCTAssertEqual(0u, nalu_length);
}

- (void)testReadMultipleNalus {
  // clang-format off
  const uint8_t annex_b_test_data[] = {0x00, 0x00, 0x00, 0x01, 0xFF,
                                       0x01,
                                       0x00, 0x01,
                                       0x00, 0x00, 0x00, 0xFF,
                                       0x00, 0x00, 0x01, 0xAA, 0xBB};
  // clang-format on
  webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data));
  const uint8_t* nalu = nullptr;
  size_t nalu_length = 0;
  XCTAssertEqual(arraysize(annex_b_test_data), reader.BytesRemaining());
  XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(annex_b_test_data + 4, nalu);
  XCTAssertEqual(8u, nalu_length);
  XCTAssertEqual(5u, reader.BytesRemaining());
  XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(annex_b_test_data + 15, nalu);
  XCTAssertEqual(2u, nalu_length);
  XCTAssertEqual(0u, reader.BytesRemaining());
  XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length));
  XCTAssertEqual(nullptr, nalu);
  XCTAssertEqual(0u, nalu_length);
}

- (void)testEmptyOutputBuffer {
  const uint8_t expected_buffer[] = {0x00};
  const size_t buffer_size = 1;
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
  memset(buffer.get(), 0, buffer_size);
  webrtc::AvccBufferWriter writer(buffer.get(), 0);
  XCTAssertEqual(0u, writer.BytesRemaining());
  XCTAssertFalse(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0)));
  XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer)));
}

- (void)testWriteSingleNalu {
  const uint8_t expected_buffer[] = {
      0x00, 0x00, 0x00, 0x03, 0xAA, 0xBB, 0xCC,
  };
  const size_t buffer_size = arraysize(NALU_TEST_DATA_0) + 4;
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
  webrtc::AvccBufferWriter writer(buffer.get(), buffer_size);
  XCTAssertEqual(buffer_size, writer.BytesRemaining());
  XCTAssertTrue(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0)));
  XCTAssertEqual(0u, writer.BytesRemaining());
  XCTAssertFalse(writer.WriteNalu(NALU_TEST_DATA_1, arraysize(NALU_TEST_DATA_1)));
  XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer)));
}

- (void)testWriteMultipleNalus {
  // clang-format off
  const uint8_t expected_buffer[] = {
    0x00, 0x00, 0x00, 0x03, 0xAA, 0xBB, 0xCC,
    0x00, 0x00, 0x00, 0x04, 0xDE, 0xAD, 0xBE, 0xEF
  };
  // clang-format on
  const size_t buffer_size = arraysize(NALU_TEST_DATA_0) + arraysize(NALU_TEST_DATA_1) + 8;
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
  webrtc::AvccBufferWriter writer(buffer.get(), buffer_size);
  XCTAssertEqual(buffer_size, writer.BytesRemaining());
  XCTAssertTrue(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0)));
  XCTAssertEqual(buffer_size - (arraysize(NALU_TEST_DATA_0) + 4), writer.BytesRemaining());
  XCTAssertTrue(writer.WriteNalu(NALU_TEST_DATA_1, arraysize(NALU_TEST_DATA_1)));
  XCTAssertEqual(0u, writer.BytesRemaining());
  XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer)));
}

- (void)testOverflow {
  const uint8_t expected_buffer[] = {0x00, 0x00, 0x00};
  const size_t buffer_size = arraysize(NALU_TEST_DATA_0);
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
  memset(buffer.get(), 0, buffer_size);
  webrtc::AvccBufferWriter writer(buffer.get(), buffer_size);
  XCTAssertEqual(buffer_size, writer.BytesRemaining());
  XCTAssertFalse(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0)));
  XCTAssertEqual(buffer_size, writer.BytesRemaining());
  XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer)));
}

- (void)testH264AnnexBBufferToCMSampleBuffer {
  // clang-format off
  const uint8_t annex_b_test_data[] = {
    0x00,
    0x00, 0x00, 0x01,
    0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes
    0x00, 0x00, 0x01,
    0xAA, 0xFF, // second chunk, 2 bytes
    0x00, 0x00, 0x01,
    0xBB};  // third chunk, 1 byte, will not fit into output array

  const uint8_t expected_cmsample_data[] = {
    0x00, 0x00, 0x00, 0x04,
    0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes
    0x00, 0x00, 0x00, 0x02,
    0xAA, 0xFF}; // second chunk, 2 bytes
  // clang-format on

  CMMemoryPoolRef memory_pool = CMMemoryPoolCreate(nil);
  CMSampleBufferRef out_sample_buffer = nil;
  CMVideoFormatDescriptionRef description = [self createDescription];

  Boolean result = webrtc::H264AnnexBBufferToCMSampleBuffer(annex_b_test_data,
                                                            arraysize(annex_b_test_data),
                                                            description,
                                                            &out_sample_buffer,
                                                            memory_pool);

  XCTAssertTrue(result);

  XCTAssertEqual(description, CMSampleBufferGetFormatDescription(out_sample_buffer));

  char* data_ptr = nullptr;
  CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(out_sample_buffer);
  size_t block_buffer_size = CMBlockBufferGetDataLength(block_buffer);
  CMBlockBufferGetDataPointer(block_buffer, 0, nullptr, nullptr, &data_ptr);
  XCTAssertEqual(block_buffer_size, arraysize(annex_b_test_data));

  int data_comparison_result =
      memcmp(expected_cmsample_data, data_ptr, arraysize(expected_cmsample_data));

  XCTAssertEqual(0, data_comparison_result);

  if (description) {
    CFRelease(description);
    description = nullptr;
  }

  CMMemoryPoolInvalidate(memory_pool);
  CFRelease(memory_pool);
}

- (void)testH264CMSampleBufferToAnnexBBuffer {
  // clang-format off
  const uint8_t cmsample_data[] = {
    0x00, 0x00, 0x00, 0x04,
    0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes
    0x00, 0x00, 0x00, 0x02,
    0xAA, 0xFF}; // second chunk, 2 bytes

  const uint8_t expected_annex_b_data[] = {
    0x00, 0x00, 0x00, 0x01,
    0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes
    0x00, 0x00, 0x00, 0x01,
    0xAA, 0xFF}; // second chunk, 2 bytes
  // clang-format on

  rtc::Buffer annexb_buffer(arraysize(cmsample_data));
  CMSampleBufferRef sample_buffer =
      [self createCMSampleBufferRef:(void*)cmsample_data cmsampleSize:arraysize(cmsample_data)];

  Boolean result = webrtc::H264CMSampleBufferToAnnexBBuffer(sample_buffer,
                                                            /* is_keyframe */ false,
                                                            &annexb_buffer);

  XCTAssertTrue(result);

  XCTAssertEqual(arraysize(expected_annex_b_data), annexb_buffer.size());

  int data_comparison_result =
      memcmp(expected_annex_b_data, annexb_buffer.data(), arraysize(expected_annex_b_data));

  XCTAssertEqual(0, data_comparison_result);
}

- (void)testH264CMSampleBufferToAnnexBBufferWithKeyframe {
  // clang-format off
  const uint8_t cmsample_data[] = {
    0x00, 0x00, 0x00, 0x04,
    0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes
    0x00, 0x00, 0x00, 0x02,
    0xAA, 0xFF}; // second chunk, 2 bytes

  const uint8_t expected_annex_b_data[] = {
    0x00, 0x00, 0x00, 0x01,
    0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes
    0x00, 0x00, 0x00, 0x01,
    0xAA, 0xFF}; // second chunk, 2 bytes
  // clang-format on

  rtc::Buffer annexb_buffer(arraysize(cmsample_data));
  CMSampleBufferRef sample_buffer =
      [self createCMSampleBufferRef:(void*)cmsample_data cmsampleSize:arraysize(cmsample_data)];

  Boolean result = webrtc::H264CMSampleBufferToAnnexBBuffer(sample_buffer,
                                                            /* is_keyframe */ true,
                                                            &annexb_buffer);

  XCTAssertTrue(result);

  XCTAssertEqual(arraysize(SPS_PPS_BUFFER) + arraysize(expected_annex_b_data),
                 annexb_buffer.size());

  XCTAssertEqual(0, memcmp(SPS_PPS_BUFFER, annexb_buffer.data(), arraysize(SPS_PPS_BUFFER)));

  XCTAssertEqual(0,
                 memcmp(expected_annex_b_data,
                        annexb_buffer.data() + arraysize(SPS_PPS_BUFFER),
                        arraysize(expected_annex_b_data)));
}

- (CMVideoFormatDescriptionRef)createDescription {
  CMVideoFormatDescriptionRef description =
      webrtc::CreateVideoFormatDescription(SPS_PPS_BUFFER, arraysize(SPS_PPS_BUFFER));
  XCTAssertTrue(description);
  return description;
}

- (CMSampleBufferRef)createCMSampleBufferRef:(void*)cmsampleData cmsampleSize:(size_t)cmsampleSize {
  CMSampleBufferRef sample_buffer = nil;
  OSStatus status;

  CMVideoFormatDescriptionRef description = [self createDescription];
  CMBlockBufferRef block_buffer = nullptr;

  status = CMBlockBufferCreateWithMemoryBlock(nullptr,
                                              cmsampleData,
                                              cmsampleSize,
                                              nullptr,
                                              nullptr,
                                              0,
                                              cmsampleSize,
                                              kCMBlockBufferAssureMemoryNowFlag,
                                              &block_buffer);
  XCTAssertEqual(kCMBlockBufferNoErr, status);

  status = CMSampleBufferCreate(nullptr,
                                block_buffer,
                                true,
                                nullptr,
                                nullptr,
                                description,
                                1,
                                0,
                                nullptr,
                                0,
                                nullptr,
                                &sample_buffer);
  XCTAssertEqual(noErr, status);

  return sample_buffer;
}

@end
