// test import
const assert = require('assert')
const mathjs = require('../../src/main')
const approx = require('../../tools/approx')

describe('import', function () {
  let math = null

  beforeEach(function () {
    math = mathjs.create()
    math.import({
      myvalue: 42,
      hello: function (name) {
        return 'hello, ' + name + '!'
      }
    }, { override: true })
  })

  afterEach(function () {
    math = null
  })

  it('should import a custom member', function () {
    assert.strictEqual(math.myvalue * 2, 84)
    assert.strictEqual(math.hello('user'), 'hello, user!')
  })

  it('should not override existing functions', function () {
    assert.throws(function () { math.import({ myvalue: 10 }) },
      /Error: Cannot import "myvalue": already exists/)
    assert.strictEqual(math.myvalue, 42)
  })

  it('should throw no errors when silent:true', function () {
    math.import({ myvalue: 10 }, { silent: true })
    assert.strictEqual(math.myvalue, 42)
  })

  it('should override existing functions if forced', function () {
    math.import({ myvalue: 10 }, { override: true })
    assert.strictEqual(math.myvalue, 10)
  })

  it('should parse the user defined members', function () {
    if (math.parser) {
      const parser = math.parser()
      math.add(math.myvalue, 10)
      parser.eval('myvalue + 10') // 52
      parser.eval('hello("user")') // 'hello, user!'
    }
  })

  const getSize = function (array) {
    return array.length
  }

  it('shouldn\'t wrap custom functions by default', function () {
    math.import({ getSizeNotWrapped: getSize })
    assert.strictEqual(math.getSizeNotWrapped([1, 2, 3]), 3)
    assert.strictEqual(math.getSizeNotWrapped(math.matrix([1, 2, 3])), undefined)
  })

  it('should wrap custom functions if wrap = true', function () {
    math.import({ getSizeWrapped: getSize }, { wrap: true })
    assert.strictEqual(math.getSizeWrapped([1, 2, 3]), 3)
    assert.strictEqual(math.getSizeWrapped(math.matrix([1, 2, 3])), 3)
  })

  it('wrapped imported functions should accept undefined and null', function () {
    math.import({
      isNull: function (obj) {
        return obj === null
      }
    }, { wrap: true })
    assert.strictEqual(math.isNull(null), true)
    assert.strictEqual(math.isNull(0), false)

    math.import({
      isUndefined: function (obj) {
        return obj === undefined
      }
    }, { wrap: true })
    assert.strictEqual(math.isUndefined(undefined), true)
    assert.strictEqual(math.isUndefined(0), false)
    assert.strictEqual(math.isUndefined(null), false)
  })

  it('should throw an error in case of wrong number of arguments', function () {
    assert.throws(function () { math.import() }, /ArgumentsError/)
    assert.throws(function () { math.import('', {}, 3) }, /ArgumentsError/)
  })

  it('should throw an error in case of wrong type of arguments', function () {
    assert.throws(function () { math.import(2) }, /TypeError: Factory, Object, or Array expected/)
    assert.throws(function () { math.import(function () {}) }, /TypeError: Factory, Object, or Array expected/)
  })

  it('should ignore properties on Object', function () {
    Object.prototype.foo = 'bar' // eslint-disable-line no-extend-native

    math.import({ bar: 456 })

    assert(!math.hasOwnProperty('foo'))
    assert(math.hasOwnProperty('bar'))

    delete Object.prototype.foo
  })

  it('should return the imported object', function () {
    math.import({ a: 24 })
    assert.deepStrictEqual(math.a, 24)

    math.import({ pi: 24 }, { silent: true })
    approx.equal(math.pi, Math.PI) // pi was ignored
  })

  it('should import a boolean', function () {
    math.import({ a: true })
    assert.strictEqual(math.a, true)
  })

  it('should merge typed functions with the same name', function () {
    math.import({
      'foo': math.typed('foo', {
        'number': function (x) {
          return 'foo(number)'
        }
      })
    })

    math.import({
      'foo': math.typed('foo', {
        'string': function (x) {
          return 'foo(string)'
        }
      })
    })

    assert.deepStrictEqual(Object.keys(math.foo.signatures).sort(), ['number', 'string'])
    assert.strictEqual(math.foo(2), 'foo(number)')
    assert.strictEqual(math.foo('bar'), 'foo(string)')
    assert.throws(function () {
      math.foo(new Date())
    }, /TypeError: Unexpected type of argument in function foo/)
  })

  it('should override existing typed functions', function () {
    math.import({
      'foo': math.typed('foo', {
        'Date': function (x) {
          return 'foo(Date)'
        }
      })
    })

    assert.strictEqual(math.foo(new Date()), 'foo(Date)')

    math.import({
      'foo': math.typed('foo', {
        'string': function (x) {
          return 'foo(string)'
        }
      })
    }, { override: true })

    assert.deepStrictEqual(Object.keys(math.foo.signatures).sort(), ['string'])
    assert.strictEqual(math.foo('bar'), 'foo(string)')
    assert.throws(function () {
      math.foo(new Date())
    }, /TypeError: Unexpected type of argument in function foo/)
    assert.throws(function () {
      math.foo(new Date())
    }, /TypeError: Unexpected type of argument in function foo/)
  })

  it('should merge typed functions coming from a factory', function () {
    math.import({
      'foo': math.typed('foo', {
        'number': function (x) {
          return 'foo(number)'
        }
      })
    })

    math.import({
      'name': 'foo',
      'factory': function () {
        return math.typed('foo', {
          'string': function (x) {
            return 'foo(string)'
          }
        })
      }
    })

    assert.deepStrictEqual(Object.keys(math.foo.signatures).sort(), ['number', 'string'])
    assert.strictEqual(math.foo(2), 'foo(number)')
    assert.strictEqual(math.foo('bar'), 'foo(string)')
    assert.throws(function () {
      math.foo(new Date())
    }, /TypeError: Unexpected type of argument in function foo/)
  })

  it('should import a boolean', function () {
    math.import({ a: true })
    assert.strictEqual(math.a, true)
  })

  it('should import a function with transform', function () {
    function foo (text) {
      return text.toLowerCase()
    }

    foo.transform = function foo (text) {
      return text.toUpperCase()
    }

    math.import({ foo: foo })

    assert(math.hasOwnProperty('foo'))
    assert.strictEqual(math.foo, foo)
    assert(math.expression.transform.hasOwnProperty('foo'))
    assert.strictEqual(math.expression.transform.foo, foo.transform)
  })

  it('should override a function with transform for one without', function () {
    function mean () {
      return 'test'
    }

    math.import({ mean: mean }, { override: true })

    assert(math.hasOwnProperty('mean'))
    assert.strictEqual(math.mean, mean)
    assert.strictEqual(math.expression.transform.mean, undefined)
    assert.strictEqual(math.expression.mathWithTransform.mean, mean)
  })

  it('should override a function with transform for a factory function without', function () {
    const math2 = math.create()

    function mean () {
      return 'test'
    }

    const meanFactory = {
      name: 'mean',
      factory: () => mean
    }

    math2.import([meanFactory], { override: true })

    assert(math2.hasOwnProperty('mean'))
    assert.strictEqual(math2.mean, mean)
    assert.strictEqual(math2.expression.transform.mean, undefined)
    assert.strictEqual(math2.expression.mathWithTransform.mean, mean)
  })

  it('should throw an error when a factory function has a transform', function () {
    assert.throws(function () {
      math.import({
        name: 'foo2',
        factory: function () {
          const fn = function () {}
          fn.transform = function () {}
          return fn
        }
      })

      math.foo2() // as soon as we use it, it will resolve the factory function
    }, /Transforms cannot be attached to factory functions/)
  })

  it.skip('should import a factory with name', function () {
    // TODO: unit test importing a factory
  })

  it.skip('should import a factory with path', function () {
    // TODO: unit test importing a factory
  })

  it.skip('should import a factory without name', function () {
    // TODO: unit test importing a factory
  })

  it.skip('should pass the namespace to a factory function', function () {
    // TODO: unit test importing a factory
  })

  it.skip('should import an Array', function () {
    // TODO: unit test importing an Array containing stuff
  })

  it('should LaTeX import', function () {
    const expression = math.parse('import(object)')
    assert.strictEqual(expression.toTex(), '\\mathrm{import}\\left( object\\right)')
  })
})
