// Copyright 2018 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 "components/arc/video_accelerator/arc_video_accelerator_util.h"

#include "base/files/file_util.h"
#include "base/files/platform_file.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/video_frame.h"
#include "media/gpu/buffer_validation.h"
#include "media/gpu/macros.h"
#include "mojo/public/cpp/system/platform_handle.h"

namespace arc {

base::ScopedFD UnwrapFdFromMojoHandle(mojo::ScopedHandle handle) {
  if (!handle.is_valid()) {
    VLOGF(1) << "Handle is invalid";
    return base::ScopedFD();
  }

  base::PlatformFile platform_file;
  MojoResult mojo_result =
      mojo::UnwrapPlatformFile(std::move(handle), &platform_file);
  if (mojo_result != MOJO_RESULT_OK) {
    VLOGF(1) << "UnwrapPlatformFile failed: " << mojo_result;
    return base::ScopedFD();
  }

  return base::ScopedFD(platform_file);
}

std::vector<base::ScopedFD> DuplicateFD(base::ScopedFD fd, size_t num_fds) {
  if (!fd.is_valid()) {
    VLOGF(1) << "Input fd is not valid";
    return {};
  }

  std::vector<base::ScopedFD> fds(num_fds);
  fds[0] = std::move(fd);
  for (size_t i = 1; i < num_fds; ++i) {
    base::ScopedFD dup_fd(HANDLE_EINTR(dup(fds[0].get())));
    if (!dup_fd.is_valid()) {
      VLOGF(1) << "Failed to duplicate fd";
      return {};
    }
    fds[i] = std::move(dup_fd);
  }

  return fds;
}

base::Optional<gfx::GpuMemoryBufferHandle> CreateGpuMemoryBufferHandle(
    media::VideoPixelFormat pixel_format,
    const gfx::Size& coded_size,
    std::vector<base::ScopedFD> scoped_fds,
    const std::vector<VideoFramePlane>& planes) {
  std::vector<media::ColorPlaneLayout> color_planes;
  for (size_t i = 0; i < planes.size(); ++i) {
    int32_t stride = base::checked_cast<int32_t>(planes[i].stride);
    size_t offset = base::checked_cast<size_t>(planes[i].offset);
    size_t plane_height =
        media::VideoFrame::Rows(i, pixel_format, coded_size.height());
    base::CheckedNumeric<size_t> current_size =
        base::CheckMul(stride, plane_height);
    if (!current_size.IsValid()) {
      VLOGF(1) << "Invalid stride/height";
      return base::nullopt;
    }

    color_planes.emplace_back(stride, offset, current_size.ValueOrDie());
  }

  return CreateGpuMemoryBufferHandle(pixel_format, coded_size,
                                     std::move(scoped_fds), color_planes);
}

base::Optional<gfx::GpuMemoryBufferHandle> CreateGpuMemoryBufferHandle(
    media::VideoPixelFormat pixel_format,
    const gfx::Size& coded_size,
    std::vector<base::ScopedFD> scoped_fds,
    const std::vector<media::ColorPlaneLayout>& planes) {
  const size_t num_planes = media::VideoFrame::NumPlanes(pixel_format);
  if (planes.size() != num_planes || planes.size() == 0) {
    VLOGF(1) << "Invalid number of dmabuf planes passed: " << planes.size()
             << ", expected: " << num_planes;
    return base::nullopt;
  }
  if (scoped_fds.size() != num_planes) {
    VLOGF(1) << "Invalid number of fds passed: " << scoped_fds.size()
             << ", expected: " << num_planes;
    return base::nullopt;
  }

  gfx::GpuMemoryBufferHandle gmb_handle;
  gmb_handle.type = gfx::NATIVE_PIXMAP;
  for (size_t i = 0; i < num_planes; ++i) {
    // NOTE: planes[i].stride and planes[i].offset both are int32_t. stride and
    // offset in NativePixmapPlane are uint32_t and uint64_t, respectively.
    if (!base::IsValueInRangeForNumericType<uint32_t>(planes[i].stride)) {
      VLOGF(1) << "Invalid stride";
      return base::nullopt;
    }
    if (!base::IsValueInRangeForNumericType<uint64_t>(planes[i].offset)) {
      VLOGF(1) << "Invalid offset";
      return base::nullopt;
    }
    uint32_t stride = base::checked_cast<uint32_t>(planes[i].stride);
    uint64_t offset = base::checked_cast<uint64_t>(planes[i].offset);
    uint64_t size = base::checked_cast<uint64_t>(planes[i].size);
    gmb_handle.native_pixmap_handle.planes.emplace_back(
        stride, offset, size, std::move(scoped_fds[i]));
  }

  if (!media::VerifyGpuMemoryBufferHandle(pixel_format, coded_size, gmb_handle))
    return base::nullopt;

  return gmb_handle;
}

base::ScopedFD CreateTempFileForTesting(const std::string& data) {
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  if (base::WriteFile(path, data.c_str(), data.size()) !=
      base::MakeStrictNum(data.size())) {
    VLOGF(1) << "Cannot write the whole data into file.";
    return base::ScopedFD();
  }

  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (!file.IsValid()) {
    VLOGF(1) << "Failed to create file.";
    return base::ScopedFD();
  }

  return base::ScopedFD(file.TakePlatformFile());
}

}  // namespace arc
