#include "hb-fuzzer.hh"

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

#include "hb.h"

// Only allow ~5,000 set values between the two input sets.
// Arbitrarily long input sets do not trigger any meaningful
// differences in behaviour so there's no benefit from allowing
// the fuzzer to create super large sets.
#define MAX_INPUT_SIZE 20000

enum set_operation_t : uint8_t
{
  INTERSECT = 0,
  UNION = 1,
  SUBTRACT = 2,
  SYMMETRIC_DIFFERENCE = 3
};

struct instructions_t
{
  set_operation_t operation;
  uint32_t first_set_size;
};

static hb_set_t *create_set (const uint32_t *value_array, int count)
{
  hb_set_t *set = hb_set_create ();
  for (int i = 0; i < count; i++)
    hb_set_add (set, value_array[i]);
  return set;
}


extern "C" int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
  alloc_state = _fuzzing_alloc_state (data, size);

  if (size < sizeof (instructions_t))
    return 0;

  if (size > MAX_INPUT_SIZE)
    return 0;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
  const instructions_t &instructions = reinterpret_cast<const instructions_t &> (data);
#pragma GCC diagnostic pop
  data += sizeof (instructions_t);
  size -= sizeof (instructions_t);

  const uint32_t *values = reinterpret_cast<const uint32_t *> (data);
  size = size / sizeof (uint32_t);

  if (size < instructions.first_set_size)
    return 0;

  hb_set_t *set_a = create_set (values, instructions.first_set_size);

  values += instructions.first_set_size;
  size -= instructions.first_set_size;
  hb_set_t *set_b = create_set (values, size);

  switch (instructions.operation)
  {
  case INTERSECT:
    hb_set_intersect (set_a, set_b);
    break;
  case UNION:
    hb_set_union (set_a, set_b);
    break;
  case SUBTRACT:
    hb_set_subtract (set_a, set_b);
    break;
  case SYMMETRIC_DIFFERENCE:
    hb_set_symmetric_difference (set_a, set_b);
    break;
  default:
    break;
  }

  hb_set_destroy (set_a);
  hb_set_destroy (set_b);

  return 0;
}