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, null_dispose, compare, dispose_64;

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);
  compare = library.declare("test_finalizer_cmp_size_t",
                            ctypes.default_abi,
                            ctypes.bool,
                            ctypes.size_t,
                            ctypes.size_t);

  dispose_64 = library.declare("test_finalizer_rel_int64_t",
                               ctypes.default_abi,
                               ctypes.void_t,
                               ctypes.int64_t);

  let type_afun = ctypes.FunctionType(ctypes.default_abi,
                                      ctypes.void_t,
                                      [ctypes.size_t]).ptr;

  let null_dispose_maker =
    library.declare("test_finalizer_rel_null_function",
                    ctypes.default_abi,
                    type_afun
                   );
  null_dispose = null_dispose_maker();

  tester.launch(10, test_double_dispose);
  tester.launch(10, test_finalize_bad_construction);
  tester.launch(10, test_null_dispose);
  tester.launch(10, test_pass_disposed);
  tester.launch(10, test_wrong_type);
}


/**
 * Testing construction of finalizers with wrong arguments.
 */
function test_finalize_bad_construction() {
  // First argument does not match second
  must_throw(function() { ctypes.CDataFinalizer({}, dispose); });
  must_throw(function() { ctypes.CDataFinalizer(dispose, dispose); });

  // Not enough arguments
  must_throw(function() { ctypes.CDataFinalizer(dispose); },
             "TypeError: CDataFinalizer constructor takes two arguments");

  // Too many arguments
  must_throw(function() { ctypes.CDataFinalizer(dispose, dispose, dispose); },
             "TypeError: CDataFinalizer constructor takes two arguments");

  // Second argument is null
  must_throw(function() { ctypes.CDataFinalizer(dispose, null); },
             "TypeError: expected _a CData object_ of a function pointer type, got null");

  // Second argument is undefined
  must_throw(function() {
    let a;
    ctypes.CDataFinalizer(dispose, a);
  },
  "TypeError: expected _a CData object_ of a function pointer type, got undefined");
}

/**
 * Test that forget/dispose can only take place once.
 */
function test_double_dispose() {
  function test_one_combination(i, a, b) {
    let v = ctypes.CDataFinalizer(acquire(i), dispose);
    a(v);
    must_throw(function() { b(v); } );
  }

  let call_dispose = function(v) {
    v.dispose();
  };
  let call_forget = function(v) {
    v.forget();
  };

  test_one_combination(0, call_dispose, call_dispose);
  test_one_combination(1, call_dispose, call_forget);
  test_one_combination(2, call_forget, call_dispose);
  test_one_combination(3, call_forget, call_forget);
}


/**
 * Test that nothing (too) bad happens when the finalizer is NULL
 */
function test_null_dispose() {
  let exception;

  exception = false;
  try {
    ctypes.CDataFinalizer(acquire(0), null_dispose);
  } catch (x) {
    exception = true;
  }
  Assert.ok(exception);
}

/**
 * Test that conversion of a disposed/forgotten CDataFinalizer to a C
 * value fails nicely.
 */
function test_pass_disposed() {
  let exception, v;

  exception = false;
  v = ctypes.CDataFinalizer(acquire(0), dispose);
  Assert.ok(compare(v, 0));
  v.forget();

  try {
    compare(v, 0);
  } catch (x) {
    exception = true;
  }
  Assert.ok(exception);

  exception = false;
  v = ctypes.CDataFinalizer(acquire(0), dispose);
  Assert.ok(compare(v, 0));
  v.dispose();

  try {
    compare(v, 0);
  } catch (x) {
    exception = true;
  }
  Assert.ok(exception);

  exception = false;
  try {
    ctypes.int32_t(ctypes.CDataFinalizer(v, dispose));
  } catch (x) {
    exception = true;
  }
  Assert.ok(exception);
}

function test_wrong_type() {
  let int32_v = ctypes.int32_t(99);
  let exception;
  try {
    ctypes.CDataFinalizer(int32_v, dispose_64);
  } catch (x) {
    exception = x;
  }

  Assert.ok(!!exception);
  Assert.equal(exception.constructor.name, "TypeError");
}
