#include "errno.h"
#include "string.h"

#include "jsctypes-test.h"
#include "jsctypes-test-finalizer.h"

/**
 * Shared infrastructure
 */

/**
 * An array of integers representing resources.
 * - 0: unacquired
 * - 1: acquired
 * - < 0: error, resource has been released several times.
 */
int* gFinalizerTestResources = nullptr;
char** gFinalizerTestNames = nullptr;
size_t gFinalizerTestSize;

void test_finalizer_start(size_t size) {
  gFinalizerTestResources = new int[size];
  gFinalizerTestNames = new char*[size];
  gFinalizerTestSize = size;
  for (size_t i = 0; i < size; ++i) {
    gFinalizerTestResources[i] = 0;
    gFinalizerTestNames[i] = nullptr;
  }
}

void test_finalizer_stop() { delete[] gFinalizerTestResources; }

/**
 * Check if an acquired resource has been released
 */
bool test_finalizer_resource_is_acquired(size_t i) {
  return gFinalizerTestResources[i] == 1;
}
// Resource type: size_t

// Acquire resource i
size_t test_finalizer_acq_size_t(size_t i) {
  gFinalizerTestResources[i] = 1;
  return i;
}

// Release resource i
void test_finalizer_rel_size_t(size_t i) {
  if (--gFinalizerTestResources[i] < 0) {
    MOZ_CRASH("Assertion failed");
  }
}

size_t test_finalizer_rel_size_t_return_size_t(size_t i) {
  if (--gFinalizerTestResources[i] < 0) {
    MOZ_CRASH("Assertion failed");
  }
  return i;
}

myRECT test_finalizer_rel_size_t_return_struct_t(size_t i) {
  if (--gFinalizerTestResources[i] < 0) {
    MOZ_CRASH("Assertion failed");
  }
  const int32_t narrowed = (int32_t)i;
  myRECT result = {narrowed, narrowed, narrowed, narrowed};
  return result;
}

bool test_finalizer_cmp_size_t(size_t a, size_t b) { return a == b; }

// Resource type: int32_t

// Acquire resource i
int32_t test_finalizer_acq_int32_t(size_t i) {
  gFinalizerTestResources[i] = 1;
  return i;
}

// Release resource i
void test_finalizer_rel_int32_t(int32_t i) {
  if (--gFinalizerTestResources[i] < 0) {
    MOZ_CRASH("Assertion failed");
  }
}

bool test_finalizer_cmp_int32_t(int32_t a, int32_t b) { return a == b; }

// Resource type: int64_t

// Acquire resource i
int64_t test_finalizer_acq_int64_t(size_t i) {
  gFinalizerTestResources[i] = 1;
  return i;
}

// Release resource i
void test_finalizer_rel_int64_t(int64_t i) {
  if (--gFinalizerTestResources[i] < 0) {
    MOZ_CRASH("Assertion failed");
  }
}

bool test_finalizer_cmp_int64_t(int64_t a, int64_t b) { return a == b; }

// Resource type: void*

// Acquire resource i
void* test_finalizer_acq_ptr_t(size_t i) {
  gFinalizerTestResources[i] = 1;
  return (void*)&gFinalizerTestResources[i];
}

// Release resource i
void test_finalizer_rel_ptr_t(void* i) {
  int* as_int = (int*)i;
  --(*as_int);
  if (*as_int < 0) {
    MOZ_CRASH("Assertion failed");
  }
}

bool test_finalizer_cmp_ptr_t(void* a, void* b) { return a == b; }

// Resource type: int32_t*

// Acquire resource i
int32_t* test_finalizer_acq_int32_ptr_t(size_t i) {
  gFinalizerTestResources[i] = 1;
  return (int32_t*)&gFinalizerTestResources[i];
}

// Release resource i
void test_finalizer_rel_int32_ptr_t(int32_t* i) {
  --(*i);
  if (*i < 0) {
    MOZ_CRASH("Assertion failed");
  }
}

bool test_finalizer_cmp_int32_ptr_t(int32_t* a, int32_t* b) { return a == b; }

// Resource type: nullptr

// Acquire resource i
void* test_finalizer_acq_null_t(size_t i) {
  gFinalizerTestResources[0] = 1;  // Always index 0
  return nullptr;
}

// Release resource i
void test_finalizer_rel_null_t(void* i) {
  if (i != nullptr) {
    MOZ_CRASH("Assertion failed");
  }
  gFinalizerTestResources[0]--;
}

bool test_finalizer_null_resource_is_acquired(size_t) {
  return gFinalizerTestResources[0] == 1;
}

bool test_finalizer_cmp_null_t(void* a, void* b) { return a == b; }

// Resource type: char*

// Acquire resource i
char* test_finalizer_acq_string_t(int i) {
  gFinalizerTestResources[i] = 1;
  if (!gFinalizerTestNames[i]) {
    char* buf = new char[12];
    snprintf(buf, 12, "%d", i);
    gFinalizerTestNames[i] = buf;
    return buf;
  }
  return gFinalizerTestNames[i];
}

// Release resource i
void test_finalizer_rel_string_t(char* i) {
  int index = atoi(i);
  if (index < 0 || index >= (int)gFinalizerTestSize) {
    MOZ_CRASH("Assertion failed");
  }
  gFinalizerTestResources[index]--;
}

bool test_finalizer_string_resource_is_acquired(size_t i) {
  return gFinalizerTestResources[i] == 1;
}

bool test_finalizer_cmp_string_t(char* a, char* b) {
  return !strncmp(a, b, 10);
}

// Resource type: myRECT

// Acquire resource i
myRECT test_finalizer_acq_struct_t(int i) {
  gFinalizerTestResources[i] = 1;
  myRECT result = {i, i, i, i};
  return result;
}

// Release resource i
void test_finalizer_rel_struct_t(myRECT i) {
  int index = i.top;
  if (index < 0 || index >= (int)gFinalizerTestSize) {
    MOZ_CRASH("Assertion failed");
  }
  gFinalizerTestResources[index]--;
}

bool test_finalizer_struct_resource_is_acquired(myRECT i) {
  int index = i.top;
  if (index < 0 || index >= (int)gFinalizerTestSize) {
    MOZ_CRASH("Assertion failed");
  }
  return gFinalizerTestResources[index] == 1;
}

bool test_finalizer_cmp_struct_t(myRECT a, myRECT b) { return a.top == b.top; }

// Support for checking that we reject nullptr finalizer
afun* test_finalizer_rel_null_function() { return nullptr; }

void test_finalizer_rel_size_t_set_errno(size_t i) {
  if (--gFinalizerTestResources[i] < 0) {
    MOZ_CRASH("Assertion failed");
  }
  errno = 10;
}

void reset_errno() { errno = 0; }
