#include "hb-fuzzer.hh"

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

#include "hb-subset-serialize.h"

typedef struct
{
  uint16_t parent;
  uint16_t child;
  uint16_t position;
  uint8_t width;
} link_t;

/* The fuzzer seed contains a serialized representation of a object graph which forms
 * the input graph to the repacker call. The binary format is:
 *
 * table tag: 4 bytes
 * number of objects: 2 bytes
 * objects[number of objects]:
 *   blob size: 2 bytes
 *   blob: blob size bytes
 * num of real links: 2 bytes
 * links[number of real links]: link_t struct
 *
 * TODO(garretrieger): add optional virtual links
 */

template <typename T>
bool read(const uint8_t** data, size_t* size, T* out)
{
  if (*size < sizeof (T)) return false;

  memcpy(out, *data, sizeof (T));

  *data += sizeof (T);
  *size -= sizeof (T);

  return true;
}

void cleanup (hb_subset_serialize_object_t* objects, uint16_t num_objects)
{
  for (uint32_t i = 0; i < num_objects; i++)
  {
    free (objects[i].head);
    free (objects[i].real_links);
  }
}

void add_links_to_objects (hb_subset_serialize_object_t* objects, uint16_t num_objects,
                           link_t* links, uint16_t num_links)
{
  unsigned* link_count = (unsigned*) calloc (num_objects, sizeof (unsigned));

  for (uint32_t i = 0; i < num_links; i++)
  {
    uint16_t parent_idx = links[i].parent;
    link_count[parent_idx]++;
  }

  for (uint32_t i = 0; i < num_objects; i++)
  {
    objects[i].num_real_links = link_count[i];
    objects[i].real_links = (hb_subset_serialize_link_t*) calloc (link_count[i], sizeof (hb_subset_serialize_link_t));
    objects[i].num_virtual_links = 0;
    objects[i].virtual_links = nullptr;
  }

  for (uint32_t i = 0; i < num_links; i++)
  {
    uint16_t parent_idx = links[i].parent;
    uint16_t child_idx = links[i].child + 1; // All indices are shifted by 1 by the null object.
    hb_subset_serialize_link_t* link = &(objects[parent_idx].real_links[link_count[parent_idx] - 1]);

    link->width = links[i].width;
    link->position = links[i].position;
    link->objidx = child_idx;
    link_count[parent_idx]--;
  }

  free (link_count);
}

extern "C" int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
  // TODO(garretrieger): move graph validity checks into repacker graph creation.
  _fuzzing_skip_leading_comment (&data, &size);
  alloc_state = _fuzzing_alloc_state (data, size);

  uint16_t num_objects = 0;
  hb_subset_serialize_object_t* objects = nullptr;

  uint16_t num_real_links = 0;
  link_t* links = nullptr;

  hb_tag_t table_tag;
  if (!read<hb_tag_t> (&data, &size, &table_tag)) goto end;
  if (!read<uint16_t> (&data, &size, &num_objects)) goto end;

  objects = (hb_subset_serialize_object_t*) calloc (num_objects, sizeof (hb_subset_serialize_object_t));
  for (uint32_t i = 0; i < num_objects; i++)
  {
    uint16_t blob_size;
    if (!read<uint16_t> (&data, &size, &blob_size)) goto end;
    if (size < blob_size) goto end;

    char* copy = (char*) calloc (1, blob_size);
    memcpy (copy, data, blob_size);
    objects[i].head = (char*) copy;
    objects[i].tail = (char*) (copy + blob_size);

    size -= blob_size;
    data += blob_size;
  }

  if (!read<uint16_t> (&data, &size, &num_real_links)) goto end;
  links = (link_t*) calloc (num_real_links, sizeof (link_t));
  for (uint32_t i = 0; i < num_real_links; i++)
  {
    if (!read<link_t> (&data, &size, &links[i])) goto end;

    if (links[i].parent >= num_objects)
      goto end;
  }

  add_links_to_objects (objects, num_objects,
                        links, num_real_links);

  hb_blob_destroy (hb_subset_serialize_or_fail (table_tag,
                                                objects,
                                                num_objects));

end:
  if (objects)
  {
    cleanup (objects, num_objects);
    free (objects);
  }
  free (links);

  return 0;
}
