// Copyright 2019 The Chromium Authors. All rights reserved.

#include <fuzzer/FuzzedDataProvider.h>
#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "third_party/icu/fuzzers/fuzzer_utils.h"
#include "third_party/icu/source/common/unicode/appendable.h"

static IcuEnvironment* env = new IcuEnvironment;

constexpr size_t kMaxInitialSize = 64;
constexpr size_t kMaxReserveSize = 4096;
constexpr size_t kMaxAppendLength = 64;
constexpr size_t kMaxAdditionalDesiredSize = 4096;

constexpr size_t kScratchBufSize = 4096;
char16_t scratch_buf[kScratchBufSize];

enum class AppendableApi {
  AppendCodeUnit,
  AppendCodePoint,
  AppendString,
  ReserveAppendCapacity,
  GetAppendBuffer,
  kMaxValue = GetAppendBuffer
};

// Entry point for LibFuzzer.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  FuzzedDataProvider provider(data, size);
  auto str(icu::UnicodeString::fromUTF8(
      provider.ConsumeRandomLengthString(kMaxInitialSize)));
  icu::UnicodeStringAppendable strAppendable(str);

  while (provider.remaining_bytes() > 0) {
    switch (provider.ConsumeEnum<AppendableApi>()) {
      case AppendableApi::AppendCodeUnit:
        strAppendable.appendCodeUnit(provider.ConsumeIntegral<char16_t>());
        break;
      case AppendableApi::AppendCodePoint:
        strAppendable.appendCodePoint(provider.ConsumeIntegral<UChar32>());
        break;
      case AppendableApi::AppendString: {
        std::string appendChrs8(
            provider.ConsumeRandomLengthString(kMaxAppendLength));
        if (appendChrs8.size() == 0)
          break;
        std::vector<char16_t> appendChrs(RandomChar16Array(
            2, reinterpret_cast<const uint8_t*>(appendChrs8.data()),
            appendChrs8.size()));
        strAppendable.appendString(appendChrs.data(), appendChrs.size());
        break;
      }
      case AppendableApi::ReserveAppendCapacity:
        strAppendable.reserveAppendCapacity(
            provider.ConsumeIntegralInRange<int32_t>(0, kMaxReserveSize));
        break;
      case AppendableApi::GetAppendBuffer: {
        int32_t out_capacity;
        const auto min_capacity =
            provider.ConsumeIntegralInRange<int32_t>(1, kScratchBufSize);
        char16_t* out_buffer = strAppendable.getAppendBuffer(
            min_capacity,
            min_capacity + provider.ConsumeIntegralInRange<int32_t>(
                               0, kMaxAdditionalDesiredSize),
            scratch_buf, kScratchBufSize, &out_capacity);
        // Write arbitrary value at the end of the buffer.
        if (out_buffer)
          out_buffer[out_capacity - 1] = 1;
        break;
      }
    }
  }

  return 0;
}
