// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include <cstddef>
#include <cstdint>
#include <vector>

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

namespace avif {
namespace testutil {
namespace {

struct FrameOptions {
  uint64_t duration;
  avifAddImageFlags flags;
};

// Encodes an animation and decodes it.
// For simplicity, there is only one source image, all frames are identical.
void EncodeDecodeAnimation(std::vector<ImagePtr> frames,
                           const std::vector<FrameOptions>& frame_options,
                           EncoderPtr encoder, DecoderPtr decoder) {
  ASSERT_NE(encoder, nullptr);
  ASSERT_NE(decoder, nullptr);
  ImagePtr decoded_image(avifImageCreateEmpty());
  ASSERT_NE(decoded_image, nullptr);

  const size_t num_frames = frames.size();
  int total_duration = 0;

  // Encode.
  for (size_t i = 0; i < num_frames; ++i) {
    total_duration += frame_options[i].duration;
    const avifResult result =
        avifEncoderAddImage(encoder.get(), frames[i].get(),
                            frame_options[i].duration, frame_options[i].flags);
    ASSERT_EQ(result, AVIF_RESULT_OK)
        << avifResultToString(result) << ": " << encoder->diag.error;
  }
  AvifRwData encoded_data;
  avifResult result = avifEncoderFinish(encoder.get(), &encoded_data);
  ASSERT_EQ(result, AVIF_RESULT_OK)
      << avifResultToString(result) << ": " << encoder->diag.error;

  // Decode.
  result = avifDecoderSetIOMemory(decoder.get(), encoded_data.data,
                                  encoded_data.size);
  ASSERT_EQ(result, AVIF_RESULT_OK)
      << avifResultToString(result) << ": " << decoder->diag.error;

  result = avifDecoderParse(decoder.get());
  ASSERT_EQ(result, AVIF_RESULT_OK)
      << avifResultToString(result) << ": " << decoder->diag.error;

  if (decoder->requestedSource == AVIF_DECODER_SOURCE_PRIMARY_ITEM ||
      num_frames == 1) {
    ASSERT_EQ(decoder->imageCount, 1);
  } else {
    ASSERT_EQ(decoder->imageCount, num_frames);
    EXPECT_EQ(decoder->durationInTimescales, total_duration);

    for (size_t i = 0; i < num_frames; ++i) {
      result = avifDecoderNextImage(decoder.get());
      ASSERT_EQ(result, AVIF_RESULT_OK)
          << " frame " << i << ": " << avifResultToString(result) << " ("
          << decoder->diag.error << ")";
      EXPECT_EQ(decoder->image->width, frames[i]->width);
      EXPECT_EQ(decoder->image->height, frames[i]->height);
      EXPECT_EQ(decoder->image->depth, frames[i]->depth);
      EXPECT_EQ(decoder->image->yuvFormat, frames[i]->yuvFormat);
    }
    result = avifDecoderNextImage(decoder.get());
    ASSERT_EQ(result, AVIF_RESULT_NO_IMAGES_REMAINING)
        << avifResultToString(result) << ": " << decoder->diag.error;
  }
}

FUZZ_TEST(EncodeDecodeAvifFuzzTest, EncodeDecodeAnimation)
    .WithDomains(
        ArbitraryAvifAnim(),
        fuzztest::VectorOf(
            fuzztest::StructOf<FrameOptions>(
                /*duration=*/fuzztest::InRange<uint64_t>(1, 10),
                // BitFlagCombinationOf() requires non zero flags as input.
                // AVIF_ADD_IMAGE_FLAG_NONE (0) is implicitly part of the set.
                fuzztest::BitFlagCombinationOf<avifAddImageFlags>(
                    {AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME})))
            // Some FrameOptions may be wasted, but this is simpler.
            .WithSize(kMaxNumFrames),
        ArbitraryAvifEncoder(), ArbitraryAvifDecoder());

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