// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <cstring>

#include "base/macros.h"
#include "media/base/video_frame.h"
#include "media/cdm/api/content_decryption_module.h"
#include "media/cdm/cdm_helpers.h"
#include "media/mojo/services/mojo_cdm_allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"

namespace media {

class MojoCdmAllocatorTest : public testing::Test {
 public:
  MojoCdmAllocatorTest() {}
  ~MojoCdmAllocatorTest() override {}

 protected:
  cdm::Buffer* CreateCdmBuffer(size_t capacity) {
    return allocator_.CreateCdmBuffer(capacity);
  }

  std::unique_ptr<VideoFrameImpl> CreateCdmVideoFrame() {
    return allocator_.CreateCdmVideoFrame();
  }

  MojoHandle GetHandle(cdm::Buffer* buffer) {
    return allocator_.GetHandleForTesting(buffer);
  }

  size_t GetAvailableBufferCount() {
    return allocator_.GetAvailableBufferCountForTesting();
  }

 private:
  MojoCdmAllocator allocator_;
  DISALLOW_COPY_AND_ASSIGN(MojoCdmAllocatorTest);
};

TEST_F(MojoCdmAllocatorTest, CreateCdmBuffer) {
  cdm::Buffer* buffer = CreateCdmBuffer(100);
  EXPECT_GE(buffer->Capacity(), 100u);
  buffer->SetSize(50);
  EXPECT_EQ(50u, buffer->Size());
  buffer->Destroy();
}

TEST_F(MojoCdmAllocatorTest, ReuseCdmBuffer) {
  const size_t kRandomDataSize = 46;

  // Create a small buffer.
  cdm::Buffer* buffer = CreateCdmBuffer(kRandomDataSize);
  MojoHandle handle = GetHandle(buffer);
  buffer->Destroy();

  // Now allocate a new buffer of the same size, it should reuse the one
  // just freed.
  cdm::Buffer* new_buffer = CreateCdmBuffer(kRandomDataSize);
  EXPECT_EQ(handle, GetHandle(new_buffer));
  new_buffer->Destroy();
}

TEST_F(MojoCdmAllocatorTest, MaxFreeBuffers) {
  const size_t kMaxExpectedFreeBuffers = 3;
  size_t buffer_size = 0;
  const size_t kBufferSizeIncrease = 1000;
  std::vector<cdm::Buffer*> buffers;

  // Allocate and destroy 10 buffers in increasing size (to avoid buffer reuse).
  // Eventually allocating a new buffer will free the smallest free buffer, so
  // the number of free buffers will be capped at |kMaxExpectedFreeBuffers|.
  for (int i = 0; i < 10; ++i) {
    buffer_size += kBufferSizeIncrease;
    cdm::Buffer* buffer = CreateCdmBuffer(buffer_size);
    buffer->Destroy();
    EXPECT_LE(GetAvailableBufferCount(), kMaxExpectedFreeBuffers);
  }
}

TEST_F(MojoCdmAllocatorTest, CreateCdmVideoFrame) {
  const int kWidth = 16;
  const int kHeight = 9;
  const VideoPixelFormat kFormat = PIXEL_FORMAT_I420;
  const gfx::Size kSize(kWidth, kHeight);
  const size_t kBufferSize = VideoFrame::AllocationSize(kFormat, kSize);

  // Create a VideoFrameImpl and initialize it.
  std::unique_ptr<VideoFrameImpl> video_frame = CreateCdmVideoFrame();
  video_frame->SetFormat(cdm::kI420);
  video_frame->SetSize(cdm::Size(kWidth, kHeight));
  video_frame->SetStride(VideoFrameImpl::kYPlane,
                         static_cast<uint32_t>(VideoFrame::RowBytes(
                             VideoFrame::kYPlane, kFormat, kWidth)));
  video_frame->SetStride(VideoFrameImpl::kUPlane,
                         static_cast<uint32_t>(VideoFrame::RowBytes(
                             VideoFrame::kUPlane, kFormat, kWidth)));
  video_frame->SetStride(VideoFrameImpl::kVPlane,
                         static_cast<uint32_t>(VideoFrame::RowBytes(
                             VideoFrame::kVPlane, kFormat, kWidth)));
  EXPECT_EQ(nullptr, video_frame->FrameBuffer());

  // Now create a buffer to hold the frame and assign it to the VideoFrameImpl.
  cdm::Buffer* buffer = CreateCdmBuffer(kBufferSize);
  EXPECT_EQ(0u, GetAvailableBufferCount());
  buffer->SetSize(static_cast<uint32_t>(kBufferSize));
  video_frame->SetFrameBuffer(buffer);
  EXPECT_NE(nullptr, video_frame->FrameBuffer());

  // Transform it into a VideoFrame and make sure the buffer is no longer owned.
  scoped_refptr<VideoFrame> frame = video_frame->TransformToVideoFrame(kSize);
  EXPECT_EQ(nullptr, video_frame->FrameBuffer());
  EXPECT_EQ(0u, GetAvailableBufferCount());
  video_frame.reset();

  // Check that the buffer is still in use. It will be freed when |frame|
  // is destroyed.
  EXPECT_EQ(0u, GetAvailableBufferCount());
  frame = nullptr;

  // Check that the buffer is now in the free list.
  EXPECT_EQ(1u, GetAvailableBufferCount());
}

}  // namespace media
