// Copyright 2019 The libgav1 Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/yuv_buffer.h"

#include <cassert>
#include <cstddef>
#include <new>

#include "src/frame_buffer_utils.h"
#include "src/utils/common.h"
#include "src/utils/logging.h"

namespace libgav1 {

// Size conventions:
// * Widths, heights, and border sizes are in pixels.
// * Strides and plane sizes are in bytes.
//
// YuvBuffer objects may be reused through the BufferPool. Realloc() must
// assume that data members (except buffer_alloc_ and buffer_alloc_size_) may
// contain stale values from the previous use, and must set all data members
// from scratch. In particular, Realloc() must not rely on the initial values
// of data members set by the YuvBuffer constructor.
bool YuvBuffer::Realloc(int bitdepth, bool is_monochrome, int width, int height,
                        int8_t subsampling_x, int8_t subsampling_y,
                        int left_border, int right_border, int top_border,
                        int bottom_border,
                        GetFrameBufferCallback get_frame_buffer,
                        void* callback_private_data,
                        void** buffer_private_data) {
  // Only support allocating buffers that have borders that are a multiple of
  // 2. The border restriction is required because we may subsample the
  // borders in the chroma planes.
  if (((left_border | right_border | top_border | bottom_border) & 1) != 0) {
    LIBGAV1_DLOG(ERROR,
                 "Borders must be a multiple of 2: left_border = %d, "
                 "right_border = %d, top_border = %d, bottom_border = %d.",
                 left_border, right_border, top_border, bottom_border);
    return false;
  }

  // Every row in the plane buffers needs to be kFrameBufferRowAlignment-byte
  // aligned. Since the strides are multiples of kFrameBufferRowAlignment bytes,
  // it suffices to just make the plane buffers kFrameBufferRowAlignment-byte
  // aligned.
  const int plane_align = kFrameBufferRowAlignment;
  const int uv_width =
      is_monochrome ? 0 : SubsampledValue(width, subsampling_x);
  const int uv_height =
      is_monochrome ? 0 : SubsampledValue(height, subsampling_y);
  const int uv_left_border = is_monochrome ? 0 : left_border >> subsampling_x;
  const int uv_right_border = is_monochrome ? 0 : right_border >> subsampling_x;
  const int uv_top_border = is_monochrome ? 0 : top_border >> subsampling_y;
  const int uv_bottom_border =
      is_monochrome ? 0 : bottom_border >> subsampling_y;

  if (get_frame_buffer != nullptr) {
    assert(buffer_private_data != nullptr);

    const Libgav1ImageFormat image_format =
        ComposeImageFormat(is_monochrome, subsampling_x, subsampling_y);
    FrameBuffer frame_buffer;
    if (get_frame_buffer(callback_private_data, bitdepth, image_format, width,
                         height, left_border, right_border, top_border,
                         bottom_border, kFrameBufferRowAlignment,
                         &frame_buffer) != kStatusOk) {
      return false;
    }

    if (frame_buffer.plane[0] == nullptr ||
        (!is_monochrome && frame_buffer.plane[1] == nullptr) ||
        (!is_monochrome && frame_buffer.plane[2] == nullptr)) {
      assert(false && "The get_frame_buffer callback malfunctioned.");
      LIBGAV1_DLOG(ERROR, "The get_frame_buffer callback malfunctioned.");
      return false;
    }

    stride_[kPlaneY] = frame_buffer.stride[0];
    stride_[kPlaneU] = frame_buffer.stride[1];
    stride_[kPlaneV] = frame_buffer.stride[2];
    buffer_[kPlaneY] = frame_buffer.plane[0];
    buffer_[kPlaneU] = frame_buffer.plane[1];
    buffer_[kPlaneV] = frame_buffer.plane[2];
    *buffer_private_data = frame_buffer.private_data;
  } else {
    assert(callback_private_data == nullptr);
    assert(buffer_private_data == nullptr);

    // Calculate y_stride (in bytes). It is padded to a multiple of
    // kFrameBufferRowAlignment bytes.
    int y_stride = width + left_border + right_border;
#if LIBGAV1_MAX_BITDEPTH >= 10
    if (bitdepth > 8) y_stride *= sizeof(uint16_t);
#endif
    y_stride = Align(y_stride, kFrameBufferRowAlignment);
    // Size of the Y plane in bytes.
    const uint64_t y_plane_size = (height + top_border + bottom_border) *
                                      static_cast<uint64_t>(y_stride) +
                                  (plane_align - 1);

    // Calculate uv_stride (in bytes). It is padded to a multiple of
    // kFrameBufferRowAlignment bytes.
    int uv_stride = uv_width + uv_left_border + uv_right_border;
#if LIBGAV1_MAX_BITDEPTH >= 10
    if (bitdepth > 8) uv_stride *= sizeof(uint16_t);
#endif
    uv_stride = Align(uv_stride, kFrameBufferRowAlignment);
    // Size of the U or V plane in bytes.
    const uint64_t uv_plane_size =
        is_monochrome ? 0
                      : (uv_height + uv_top_border + uv_bottom_border) *
                                static_cast<uint64_t>(uv_stride) +
                            (plane_align - 1);

    // Allocate unaligned y_buffer, u_buffer, and v_buffer.
    uint8_t* y_buffer = nullptr;
    uint8_t* u_buffer = nullptr;
    uint8_t* v_buffer = nullptr;

    const uint64_t frame_size = y_plane_size + 2 * uv_plane_size;
    if (frame_size > buffer_alloc_size_) {
      // Allocation to hold larger frame, or first allocation.
      if (frame_size != static_cast<size_t>(frame_size)) return false;

      buffer_alloc_.reset(new (std::nothrow)
                              uint8_t[static_cast<size_t>(frame_size)]);
      if (buffer_alloc_ == nullptr) {
        buffer_alloc_size_ = 0;
        return false;
      }

      buffer_alloc_size_ = static_cast<size_t>(frame_size);
    }

    y_buffer = buffer_alloc_.get();
    if (!is_monochrome) {
      u_buffer = y_buffer + y_plane_size;
      v_buffer = u_buffer + uv_plane_size;
    }

    stride_[kPlaneY] = y_stride;
    stride_[kPlaneU] = stride_[kPlaneV] = uv_stride;

    int left_border_bytes = left_border;
    int uv_left_border_bytes = uv_left_border;
#if LIBGAV1_MAX_BITDEPTH >= 10
    if (bitdepth > 8) {
      left_border_bytes *= sizeof(uint16_t);
      uv_left_border_bytes *= sizeof(uint16_t);
    }
#endif
    buffer_[kPlaneY] = AlignAddr(
        y_buffer + (top_border * y_stride) + left_border_bytes, plane_align);
    buffer_[kPlaneU] =
        AlignAddr(u_buffer + (uv_top_border * uv_stride) + uv_left_border_bytes,
                  plane_align);
    buffer_[kPlaneV] =
        AlignAddr(v_buffer + (uv_top_border * uv_stride) + uv_left_border_bytes,
                  plane_align);
  }

  y_width_ = width;
  y_height_ = height;
  left_border_[kPlaneY] = left_border;
  right_border_[kPlaneY] = right_border;
  top_border_[kPlaneY] = top_border;
  bottom_border_[kPlaneY] = bottom_border;

  uv_width_ = uv_width;
  uv_height_ = uv_height;
  left_border_[kPlaneU] = left_border_[kPlaneV] = uv_left_border;
  right_border_[kPlaneU] = right_border_[kPlaneV] = uv_right_border;
  top_border_[kPlaneU] = top_border_[kPlaneV] = uv_top_border;
  bottom_border_[kPlaneU] = bottom_border_[kPlaneV] = uv_bottom_border;

  subsampling_x_ = subsampling_x;
  subsampling_y_ = subsampling_y;

  bitdepth_ = bitdepth;
  is_monochrome_ = is_monochrome;
  assert(!is_monochrome || stride_[kPlaneU] == 0);
  assert(!is_monochrome || stride_[kPlaneV] == 0);
  assert(!is_monochrome || buffer_[kPlaneU] == nullptr);
  assert(!is_monochrome || buffer_[kPlaneV] == nullptr);

  return true;
}

}  // namespace libgav1
