try {
  // We might be running without privileges, in which case it's up to the
  // harness to give us the 'ctypes' object.
  Components.utils.import("resource://gre/modules/ctypes.jsm");
} catch (e) {
}

var acquire, dispose, reset_errno, dispose_errno,
  acquire_ptr, dispose_ptr,
  acquire_void_ptr, dispose_void_ptr,
  acquire_string, dispose_string;

function run_test() {
  let library = open_ctypes_test_lib();

  let start = library.declare("test_finalizer_start", ctypes.default_abi,
                          ctypes.void_t,
                          ctypes.size_t);
  let stop = library.declare("test_finalizer_stop", ctypes.default_abi,
                             ctypes.void_t);
  let tester = new ResourceTester(start, stop);
  acquire = library.declare("test_finalizer_acq_size_t",
                            ctypes.default_abi,
                            ctypes.size_t,
                            ctypes.size_t);
  dispose = library.declare("test_finalizer_rel_size_t",
                            ctypes.default_abi,
                            ctypes.void_t,
                            ctypes.size_t);
  reset_errno = library.declare("reset_errno",
                                ctypes.default_abi,
                                ctypes.void_t);
  dispose_errno = library.declare("test_finalizer_rel_size_t_set_errno",
                                  ctypes.default_abi,
                                  ctypes.void_t,
                                  ctypes.size_t);
  acquire_ptr = library.declare("test_finalizer_acq_int32_ptr_t",
                                ctypes.default_abi,
                                ctypes.int32_t.ptr,
                                ctypes.size_t);
  dispose_ptr = library.declare("test_finalizer_rel_int32_ptr_t",
                                ctypes.default_abi,
                                ctypes.void_t,
                                ctypes.int32_t.ptr);
  acquire_string = library.declare("test_finalizer_acq_string_t",
                                ctypes.default_abi,
                                ctypes.char.ptr,
                                ctypes.size_t);
  dispose_string = library.declare("test_finalizer_rel_string_t",
                                ctypes.default_abi,
                                ctypes.void_t,
                                ctypes.char.ptr);

  tester.launch(10, test_to_string);
  tester.launch(10, test_to_source);
  tester.launch(10, test_to_int);
  tester.launch(10, test_errno);
  tester.launch(10, test_to_pointer);
  tester.launch(10, test_readstring);
}

/**
 * Check that toString succeeds before/after forget/dispose.
 */
function test_to_string() {
  info("Starting test_to_string");
  let a = ctypes.CDataFinalizer(acquire(0), dispose);
  Assert.equal(a.toString(), "0");

  a.forget();
  Assert.equal(a.toString(), "[CDataFinalizer - empty]");

  a = ctypes.CDataFinalizer(acquire(0), dispose);
  a.dispose();
  Assert.equal(a.toString(), "[CDataFinalizer - empty]");
}

/**
 * Check that toSource succeeds before/after forget/dispose.
 */
function test_to_source() {
  info("Starting test_to_source");
  let value = acquire(0);
  let a = ctypes.CDataFinalizer(value, dispose);
  Assert.equal(a.toSource(),
               "ctypes.CDataFinalizer("
               + ctypes.size_t(value).toSource()
               + ", "
               + dispose.toSource()
               + ")");
  value = null;

  a.forget();
  Assert.equal(a.toSource(), "ctypes.CDataFinalizer()");

  a = ctypes.CDataFinalizer(acquire(0), dispose);
  a.dispose();
  Assert.equal(a.toSource(), "ctypes.CDataFinalizer()");
}

/**
 * Test conversion to int32
 */
function test_to_int() {
  let value = 2;
  let wrapped, converted, finalizable;
  wrapped = ctypes.int32_t(value);
  finalizable = ctypes.CDataFinalizer(acquire(value), dispose);
  converted = ctypes.int32_t(finalizable);

  structural_check_eq(converted, wrapped);
  structural_check_eq(converted, ctypes.int32_t(finalizable.forget()));

  finalizable = ctypes.CDataFinalizer(acquire(value), dispose);
  wrapped = ctypes.int64_t(value);
  converted = ctypes.int64_t(finalizable);
  structural_check_eq(converted, wrapped);
  finalizable.dispose();
}

/**
 * Test that dispose can change errno but finalization cannot
 */
function test_errno(size, tc, cleanup) {
  reset_errno();
  Assert.equal(ctypes.errno, 0);

  let finalizable = ctypes.CDataFinalizer(acquire(3), dispose_errno);
  finalizable.dispose();
  Assert.equal(ctypes.errno, 10);
  reset_errno();

  Assert.equal(ctypes.errno, 0);
  for (let i = 0; i < size; ++i) {
    finalizable = ctypes.CDataFinalizer(acquire(i), dispose_errno);
    cleanup.add(finalizable);
  }

  trigger_gc();
  Assert.equal(ctypes.errno, 0);
}

/**
 * Check that a finalizable of a pointer can be used as a pointer
 */
function test_to_pointer() {
  let ptr = ctypes.int32_t(2).address();
  let finalizable = ctypes.CDataFinalizer(ptr, dispose_ptr);
  let unwrapped = ctypes.int32_t.ptr(finalizable);

  Assert.equal("" + ptr, "" + unwrapped);

  finalizable.forget(); // Do not dispose: This is not a real pointer.
}

/**
 * Test that readstring can be applied to a finalizer
 */
function test_readstring(size) {
  for (let i = 0; i < size; ++i) {
    let acquired = acquire_string(i);
    let finalizable = ctypes.CDataFinalizer(acquired,
      dispose_string);
    Assert.equal(finalizable.readString(), acquired.readString());
    finalizable.dispose();
  }
}
