// Copyright 2018 Google Inc.
//
// 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 <array>
#include <cstddef>
#include <cstdint>
#include <string_view>

#include "./fuzz_utils.h"
#include "webp/demux.h"
#include "webp/mux.h"
#include "webp/mux_types.h"

namespace {

void MuxDemuxApiTest(std::string_view data_in, bool use_mux_api,
                     const std::array<int, 10>& chunk_flags) {
  const size_t size = data_in.size();
  WebPData webp_data;
  WebPDataInit(&webp_data);
  webp_data.size = size;
  webp_data.bytes = reinterpret_cast<const uint8_t*>(data_in.data());

  // Extracted chunks and frames are not processed or decoded,
  // which is already covered extensively by the other fuzz targets.

  if (use_mux_api) {
    // Mux API
    WebPMux* mux = WebPMuxCreate(&webp_data, size & 2);
    if (!mux) return;

    WebPData chunk;
    (void)WebPMuxGetChunk(mux, "EXIF", &chunk);
    (void)WebPMuxGetChunk(mux, "ICCP", &chunk);
    (void)WebPMuxGetChunk(mux, "FUZZ", &chunk);  // unknown

    uint32_t flags;
    (void)WebPMuxGetFeatures(mux, &flags);

    int width = 0, height = 0;
    (void)WebPMuxGetCanvasSize(mux, &width, &height);

    WebPMuxAnimParams params;
    (void)WebPMuxGetAnimationParams(mux, &params);

    WebPMuxError status;
    WebPMuxFrameInfo info;
    for (int i = 0; i < fuzz_utils::kFuzzFrameLimit; i++) {
      status = WebPMuxGetFrame(mux, i + 1, &info);
      if (status == WEBP_MUX_NOT_FOUND) {
        break;
      } else if (status == WEBP_MUX_OK) {
        WebPDataClear(&info.bitstream);
      }
    }

    // Try setting a custom chunk.
    if (size > 20) {
      WebPData custom_chunk;
      custom_chunk.bytes = reinterpret_cast<const uint8_t*>(data_in.data()) + 4;
      custom_chunk.size = size - 4;
      int chunk_idx = 0;
      for (std::string_view fourcc : {"VP8X", "ICCP", "ANIM", "EXIF", "XMP ",
                                      "ANMF", "ALPH", "VP8 ", "VP8L", "FUZZ"}) {
        // The last five image chunks should return WEBP_MUX_INVALID_ARGUMENT.
        for (int i = 0; i < chunk_flags[chunk_idx]; i++) {
          (void)WebPMuxSetChunk(mux, fourcc.data(), &custom_chunk, 0);
        }
        chunk_idx++;
      }
    }

    // Try assembling the mux
    WebPData assembled;
    WebPDataInit(&assembled);
    (void)WebPMuxAssemble(mux, &assembled);
    WebPDataClear(&assembled);

    // Get number of chunks of various types
    for (int chunk_type = WEBP_CHUNK_VP8X; chunk_type <= WEBP_CHUNK_NIL;
         ++chunk_type) {
      int num_chunks = 0;
      (void)WebPMuxNumChunks(mux, static_cast<WebPChunkId>(chunk_type),
                             &num_chunks);
    }

    WebPMuxDelete(mux);
  } else {
    // Demux API
    WebPDemuxer* demux;
    if (size & 2) {
      WebPDemuxState state;
      demux = WebPDemuxPartial(&webp_data, &state);
      if (state < WEBP_DEMUX_PARSED_HEADER) {
        WebPDemuxDelete(demux);
        return;
      }
    } else {
      demux = WebPDemux(&webp_data);
      if (!demux) return;
    }

    WebPChunkIterator chunk_iter;
    if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter)) {
      (void)WebPDemuxNextChunk(&chunk_iter);
    }
    WebPDemuxReleaseChunkIterator(&chunk_iter);
    if (WebPDemuxGetChunk(demux, "ICCP", 0, &chunk_iter)) {  // 0 == last
      (void)WebPDemuxPrevChunk(&chunk_iter);
    }
    WebPDemuxReleaseChunkIterator(&chunk_iter);
    // Skips FUZZ because the Demux API has no concept of (un)known chunks.

    WebPIterator iter;
    if (WebPDemuxGetFrame(demux, 1, &iter)) {
      for (int i = 1; i < fuzz_utils::kFuzzFrameLimit; i++) {
        if (!WebPDemuxNextFrame(&iter)) break;
      }
    }

    WebPDemuxReleaseIterator(&iter);
    WebPDemuxDelete(demux);
  }
}

}  // namespace

FUZZ_TEST(MuxDemuxApi, MuxDemuxApiTest)
    .WithDomains(
        fuzztest::String().WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1),
        /*mux=*/fuzztest::Arbitrary<bool>(),
        /*chunk_flags=*/fuzztest::ArrayOf<10>(fuzztest::InRange(0, 2)));
