// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
// Compare non-incremental and incremental decode results of an arbitrary byte
// sequence.

#include <algorithm>
#include <cstdint>
#include <string>

#include "avif/avif.h"
#include "avif_fuzztest_helpers.h"
#include "avifincrtest_helpers.h"
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"

using ::fuzztest::Arbitrary;

namespace avif {
namespace testutil {
namespace {

//------------------------------------------------------------------------------

struct DecoderInput {
  const uint8_t* available_bytes;
  size_t available_size;
  size_t read_size;
};

// A custom reader is necessary to get the number of bytes read by libavif.
// See avifIOReadFunc() documentation.
avifResult AvifIoRead(struct avifIO* io, uint32_t read_flags, uint64_t offset,
                      size_t size, avifROData* out) {
  DecoderInput* data = reinterpret_cast<DecoderInput*>(io->data);
  if (read_flags != 0 || !data || data->available_size < offset) {
    return AVIF_RESULT_IO_ERROR;
  }
  out->data = data->available_bytes + offset;
  out->size =
      std::min(size, data->available_size - static_cast<size_t>(offset));
  data->read_size =
      std::max(data->read_size, static_cast<size_t>(offset) + out->size);
  return AVIF_RESULT_OK;
}

//------------------------------------------------------------------------------

void DecodeIncr(const std::string& arbitrary_bytes, bool is_persistent,
                bool give_size_hint, bool use_nth_image_api) {
  ASSERT_FALSE(GetSeedDataDirs().empty());  // Make sure seeds are available.

  ImagePtr reference(avifImageCreateEmpty());
  ASSERT_NE(reference.get(), nullptr);

  DecoderInput data = {reinterpret_cast<const uint8_t*>(arbitrary_bytes.data()),
                       arbitrary_bytes.size(), 0};
  avifIO io = {.destroy = nullptr,
               .read = AvifIoRead,
               .write = nullptr,
               .sizeHint = arbitrary_bytes.size(),
               .persistent = AVIF_TRUE,
               .data = &data};

  DecoderPtr decoder(avifDecoderCreate());
  ASSERT_NE(decoder.get(), nullptr);
  avifDecoderSetIO(decoder.get(), &io);
  // OSS-Fuzz limits the allocated memory to 2560 MB.
  constexpr uint32_t kMaxMem = 2560u * 1024 * 1024;
  // Consider at most four planes of 16-bit samples.
  constexpr uint32_t kMaxImageSize =
      kMaxMem / (AVIF_PLANE_COUNT_YUV + 1) / sizeof(uint16_t);
  // Reduce the limit further to include pixel buffer copies and other memory
  // allocations.
  constexpr uint32_t kImageSizeLimit = kMaxImageSize / 4;
  // avifDecoderParse returns AVIF_RESULT_NOT_IMPLEMENTED if kImageSizeLimit is
  // bigger than AVIF_DEFAULT_IMAGE_SIZE_LIMIT.
  static_assert(kImageSizeLimit <= AVIF_DEFAULT_IMAGE_SIZE_LIMIT,
                "Too big an image size limit");
  decoder->imageSizeLimit = kImageSizeLimit;

  if (avifDecoderRead(decoder.get(), reference.get()) == AVIF_RESULT_OK) {
    // Avoid timeouts by discarding big images decoded many times.
    // TODO(yguyon): Increase this arbitrary threshold but decode incrementally
    //               fewer times than as many bytes.
    if (reference->width * reference->height * data.read_size >
        8 * 1024 * 1024) {
      return;
    }
    // decodeIncrementally() will fail if there are leftover bytes.
    const avifRWData encoded_data = {const_cast<uint8_t*>(data.available_bytes),
                                     data.read_size};
    // No clue on whether encoded_data is tiled so use a lower bound of a single
    // tile for the whole image.
    // Note that an AVIF tile is at most as high as an AV1 frame
    // (aomediacodec.github.io/av1-spec says max_frame_height_minus_1 < 65536)
    // but libavif successfully decodes AVIF files with dimensions unrelated to
    // the underlying AV1 frame (for example a 1x1000000 AVIF for a 1x1 AV1).
    // Otherwise we could use the minimum of reference->height and 65536u below.
    const uint32_t max_cell_height = reference->height;
    const avifResult result = DecodeIncrementally(
        encoded_data, decoder.get(), is_persistent, give_size_hint,
        use_nth_image_api, *reference, max_cell_height,
        /*enable_fine_incremental_check=*/false,
        /*expect_whole_file_read=*/true,
        /*expect_parse_success_from_partial_file=*/false);
    // The result does not matter, as long as we do not crash.
    (void)result;
  }
}

FUZZ_TEST(DecodeAvifFuzzTest, DecodeIncr)
    .WithDomains(ArbitraryImageWithSeeds({AVIF_APP_FILE_FORMAT_AVIF}),
                 Arbitrary<bool>(), Arbitrary<bool>(), Arbitrary<bool>());

//------------------------------------------------------------------------------

}  // namespace
}  // namespace testutil
}  // namespace avif
