/*
 * HEIF codec.
 * Copyright (c) 2018 struktur AG, Joachim Bauch <bauch@struktur.de>
 *
 * This file is part of libheif.
 *
 * libheif is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * libheif is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <string.h>
#include <stdlib.h>

#include <memory>

#include "libheif/heif.h"

static void generate_plane(int width, int height, uint8_t* output, int stride)
{
  // TODO(fancycode): Fill with random data.
  if (width == stride) {
    memset(output, 0, width * height);
  }
  else {
    for (int y = 0; y < height; y++) {
      memset(output, 0, width);
      output += stride;
    }
  }
}

static size_t create_image(const uint8_t* data, size_t size, struct heif_image** image)
{
  if (size < 2) {
    return 0;
  }

  int width = data[0] + 16;
  int height = data[1] + 16;
  data += 2;
  size -= 2;
  // TODO(fancycode): Get colorspace/chroma from fuzzing input.
  heif_colorspace colorspace = heif_colorspace_YCbCr;
  heif_chroma chroma = heif_chroma_420;

  struct heif_error err = heif_image_create(width, height, colorspace, chroma, image);
  if (err.code != heif_error_Ok) {
    return 0;
  }

  int chroma_width = (width+1)/2;
  int chroma_height = (height+1)/2;

  err = heif_image_add_plane(*image, heif_channel_Y, width, height, 8);
  assert(err.code == heif_error_Ok);
  err = heif_image_add_plane(*image, heif_channel_Cb, chroma_width, chroma_height, 8);
  assert(err.code == heif_error_Ok);
  err = heif_image_add_plane(*image, heif_channel_Cr, chroma_width, chroma_height, 8);
  assert(err.code == heif_error_Ok);

  int stride;
  uint8_t* plane;

  plane = heif_image_get_plane(*image, heif_channel_Y, &stride);
  generate_plane(width, height, plane, stride);

  plane = heif_image_get_plane(*image, heif_channel_Cb, &stride);
  generate_plane(chroma_width, chroma_height, plane, stride);

  plane = heif_image_get_plane(*image, heif_channel_Cr, &stride);
  generate_plane(chroma_width, chroma_height, plane, stride);

  return 2;
}

class MemoryWriter
{
public:
  MemoryWriter() : data_(nullptr), size_(0), capacity_(0)
  {}

  ~MemoryWriter()
  {
    free(data_);
  }

  const uint8_t* data() const
  { return data_; }

  size_t size() const
  { return size_; }

  void write(const void* data, size_t size)
  {
    if (capacity_ - size_ < size) {
      size_t new_capacity = capacity_ + size;
      uint8_t* new_data = static_cast<uint8_t*>(malloc(new_capacity));
      assert(new_data);
      if (data_) {
        memcpy(new_data, data_, size_);
        free(data_);
      }
      data_ = new_data;
      capacity_ = new_capacity;
    }
    memcpy(&data_[size_], data, size);
    size_ += size;
  }

public:
  uint8_t* data_;
  size_t size_;
  size_t capacity_;
};

static struct heif_error writer_write(struct heif_context* ctx, const void* data, size_t size, void* userdata)
{
  MemoryWriter* writer = static_cast<MemoryWriter*>(userdata);
  writer->write(data, size);
  struct heif_error err{heif_error_Ok, heif_suberror_Unspecified, nullptr};
  return err;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
  struct heif_error err;
  std::shared_ptr<heif_context> context(heif_context_alloc(),
                                        [](heif_context* c) { heif_context_free(c); });
  assert(context);

  if (size < 2) {
    return 0;
  }

  int quality = (data[0] & 0x7F) % 101;
  bool lossless = (data[1] & 0x80);
  bool use_avif = (data[1] & 0x40);
  data += 2;
  size -= 2;

  static const size_t kMaxEncoders = 5;
  const heif_encoder_descriptor* encoder_descriptors[kMaxEncoders];
  int count = heif_get_encoder_descriptors(use_avif ? heif_compression_AV1 : heif_compression_HEVC,
                                           nullptr,
                                           encoder_descriptors, kMaxEncoders);
  assert(count >= 0);
  if (count == 0) {
    return 0;
  }

  heif_encoder* encoder;
  err = heif_context_get_encoder(context.get(), encoder_descriptors[0], &encoder);
  if (err.code != heif_error_Ok) {
    return 0;
  }

  heif_encoder_set_lossy_quality(encoder, quality);
  heif_encoder_set_lossless(encoder, lossless);

  struct heif_image* image = nullptr;
  size_t read = create_image(data, size, &image);
  assert(read <= size);
  if (!read) {
    heif_image_release(image);
    heif_encoder_release(encoder);
    return 0;
  }

  data += read;
  size -= read;

  struct heif_image_handle* img;
  err = heif_context_encode_image(context.get(), image, encoder, nullptr, &img);
  heif_image_release(image);
  heif_encoder_release(encoder);
  heif_image_handle_release(img);
  if (err.code != heif_error_Ok) {
    return 0;
  }

  MemoryWriter writer;
  struct heif_writer w;
  w.writer_api_version = 1;
  w.write = writer_write;
  heif_context_write(context.get(), &w, &writer);
  assert(writer.size() > 0);
  return 0;
}
