var addon = require('../native');
var assert = require('chai').assert;

describe('JsFunction', function() {
  it('return a JsFunction built in Rust', function () {
    assert.isFunction(addon.return_js_function());
  });

  it('return a JsFunction built in Rust that implements x => x + 1', function () {
    assert.equal(addon.return_js_function()(41), 42);
  });

  it('call a JsFunction built in JS that implements x => x + 1', function () {
    assert.equal(addon.call_js_function(function(x) { return x + 1 }), 17);
  });

  it('new a JsFunction', function () {
    assert.equal(addon.construct_js_function(Date), 1970);
  });

  it('got two parameters, a string and a number', function() {
    addon.check_string_and_number("string", 42);
  });

  it('converts a Rust panic to a throw in a function', function() {
    assert.throws(function() { addon.panic() }, Error, /^internal error in Neon module: zomg$/);
  });

  it('lets panic override a throw', function() {
    assert.throws(function() { addon.panic_after_throw() }, Error, /^internal error in Neon module: this should override the RangeError$/);
  });

  it('catches an exception with cx.try_catch', function() {
    var error = new Error('Something bad happened');
    assert.equal(addon.throw_and_catch(error), error);
    assert.equal(addon.throw_and_catch(42), 42);
    assert.equal(addon.throw_and_catch('a string'), 'a string');
    assert.equal(addon.call_and_catch(() => { throw 'shade' }), 'shade');
    assert.equal(addon.call_and_catch(() => {
      throw addon.call_and_catch(() => {
        throw addon.call_and_catch(() => {
          throw 'once';
        }) + ' upon';
      }) + ' a';
    }) + ' time', 'once upon a time');
  });

  it('gets a regular value with cx.try_catch', function() {
    assert.equal(addon.call_and_catch(() => { return 42 }), 42);
  });

  it('can return Rust type from cx.try_catch', function() {
    const n = Math.random();
    assert.strictEqual(addon.get_number_or_default(n), n);
    assert.strictEqual(addon.get_number_or_default(), 0);
  });

  it('propagates a panic with cx.try_catch', function() {
    assert.throws(function() {
      addon.panic_and_catch();
      return 'unreachable';
    }, Error, /^internal error in Neon module: oh no$/);
  });

  it('panics on unexpected Err(Throw) with cx.try_catch', function() {
    assert.throw(addon.unexpected_throw_and_catch, Error, /^internal error in Neon module: try_catch: unexpected Err\(Throw\) when VM is not in a throwing state$/);
  })

  it('should be able to stringify a downcast error', function () {
    let msg = addon.downcast_error();
    assert.strictEqual(msg, "failed to downcast string to number");
  });

  it('computes the right number of arguments', function() {
    assert.equal(addon.num_arguments(), 0);
    assert.equal(addon.num_arguments('a'), 1);
    assert.equal(addon.num_arguments('a', 'b'), 2);
    assert.equal(addon.num_arguments('a', 'b', 'c'), 3);
    assert.equal(addon.num_arguments('a', 'b', 'c', 'd'), 4);
  });

  it('gets the right `this`-value', function() {
    var o = { iamobject: 'i am object' };
    assert.equal(addon.return_this.call(o), o);

    var d = new Date();
    assert.equal(addon.return_this.call(d), d);

    var n = 19;
    assert.notStrictEqual(addon.return_this.call(n), n);
  });

  it('can manipulate an object `this` binding', function() {
    var o = { modified: false };
    addon.require_object_this.call(o);
    assert.equal(o.modified, true);
    // Doesn't throw because of implicit primitive wrapping:
    addon.require_object_this.call(42);
  });

  it('implicitly gets global', function() {
    var global = (new Function("return this"))();
    assert.equal(addon.return_this.call(undefined), global);
  });

  it('exposes an argument via arguments_opt iff it is there', function() {
    assert.equal(addon.is_argument_zero_some(), false);
    assert.equal(addon.is_argument_zero_some('a'), true);
    assert.equal(addon.is_argument_zero_some('a', 'b'), true);
    assert.equal(addon.is_argument_zero_some.call(null), false);
    assert.equal(addon.is_argument_zero_some.call(null, ['a']), true);
    assert.equal(addon.is_argument_zero_some.call(null, ['a', 'b']), true);
  });

  it('correctly casts an argument via cx.arguments', function() {
    assert.equal(addon.require_argument_zero_string('foobar'), 'foobar');
    assert.throws(function() { addon.require_argument_zero_string(new Date()) }, TypeError);
    assert.throws(function() { addon.require_argument_zero_string(17) }, TypeError);
  });

  it('executes a scoped computation', function() {
    assert.equal(addon.execute_scoped(), 99);
  });

  it('computes a value in a scoped computation', function() {
    assert.equal(addon.compute_scoped(), 99);
  });
});
