// test Node
import assert from 'assert'

import math from '../../../../src/bundleAny'
const Node = math.Node

describe('Node', function () {
  function MyNode (value) {
    this.value = value
  }
  MyNode.prototype = new Node()
  MyNode.prototype.forEach = function () {}
  MyNode.prototype.map = function () {
    return new MyNode(this.value)
  }

  it('should create a Node', function () {
    const n = new Node()
    assert(n instanceof Node)
  })

  it('should have isNode', function () {
    const node = new Node()
    assert(node.isNode)
  })

  it('should throw an error when calling without new operator', function () {
    assert.throws(function () { Node() }, SyntaxError)
  })

  it('should filter a Node', function () {
    const n = new MyNode(2)

    assert.deepStrictEqual(n.filter(function () { return true }), [n])
    assert.deepStrictEqual(n.filter(function (node) { return node instanceof Node }), [n])
    assert.deepStrictEqual(n.filter(function (node) { return node instanceof Date }), [])
  })

  it('should transform a Node', function () {
    let a = new MyNode(2)
    let b = new MyNode(3)
    let c = a.transform(function (node) {
      return b
    })
    assert.deepStrictEqual(c, b)

    // no match
    a = new MyNode(2)
    b = new MyNode(3)
    c = a.transform(function (node) {
      return node
    })
    assert.deepStrictEqual(c, a)
  })

  it('should transform a Node using a replacement function', function () {
    const a = new MyNode(2)
    const b = new MyNode(3)
    const c = a.transform(function (node) {
      assert.deepStrictEqual(node, a)
      return b
    })
    assert.deepStrictEqual(c, b)
  })

  it('transform should iterate over unchanged nodes', function () {
    const logs = []

    function TestNode (value) {
      this.value = value
    }
    TestNode.prototype = new Node()
    TestNode.prototype.map = function () {
      logs.push('map ' + this.value)
      return new TestNode(this.value)
    }

    const a = new TestNode('a')
    const t = a.transform(function (node) {
      logs.push('transform ' + node.value)
      return node
    })
    assert.deepStrictEqual(t, a)
    assert.deepStrictEqual(logs, [
      'transform a',
      'map a'
    ])
  })

  it('transform should not iterate over replaced nodes', function () {
    const logs = []

    function TestNode (value) {
      this.value = value
    }
    TestNode.prototype = new Node()
    TestNode.prototype.map = function () {
      logs.push('map ' + this.value)
      return new TestNode(this.value)
    }

    const a = new TestNode('a')
    const b = new TestNode('b')

    const t = a.transform(function (node) {
      logs.push('transform ' + node.value)
      return b
    })
    assert.deepStrictEqual(t, b)
    assert.deepStrictEqual(logs, [
      'transform a'
      // NO 'map b' here!
    ])
  })

  it('should throw an error when cloning a Node interface', function () {
    assert.throws(function () {
      const a = new Node()
      a.clone()
    }, /Cannot clone a Node interface/)
  })

  it('should shallow clone the content of a Node', function () {
    const a = new math.ConstantNode(1)
    const b = new math.ConstantNode(2)
    const c = new math.OperatorNode('+', 'add', [a, b])

    const clone = c.clone()

    assert.deepStrictEqual(c, clone)
    assert.notStrictEqual(c, clone)
    assert.strictEqual(clone.args[0], c.args[0])
    assert.strictEqual(clone.args[1], c.args[1])
  })

  it('should deepClone the content of a Node', function () {
    const a = new math.ConstantNode(1)
    const b = new math.ConstantNode(2)
    const c = new math.OperatorNode('+', 'add', [a, b])

    const clone = c.cloneDeep()

    assert.deepStrictEqual(c, clone)
    assert.notStrictEqual(c, clone)
    assert.notStrictEqual(clone.args[0], c.args[0])
    assert.notStrictEqual(clone.args[1], c.args[1])
  })

  it('test equality with another Node', function () {
    assert.strictEqual(new Node().equals(new Node()), true)
    assert.strictEqual(new Node().equals(null), false)
    assert.strictEqual(new Node().equals(undefined), false)
    assert.strictEqual(new Node().equals({}), false)
  })

  it('should throw an error when stringifying a Node interface', function () {
    assert.throws(function () {
      const node = new Node()
      node.toString()
    }, /_toString not implemented for Node/)
  })

  it('should throw an error when calling toJSON on a Node interface', function () {
    assert.throws(function () {
      const a = new Node()
      a.toJSON()
    }, /Cannot serialize object: toJSON not implemented/)
  })

  it('should throw an error when calling _toTex', function () {
    assert.throws(function () {
      const node = new Node()
      node._toTex()
    }, /_toTex not implemented for Node/)
  })

  it('should ignore custom toString if it returns nothing', function () {
    const callback1 = function (node, callback) {}
    const callback2 = {
      bla: function (node, callbacks) {}
    }
    const mymath = math.create()
    mymath.Node.prototype._toString = function () {
      return 'default'
    }
    const n1 = new mymath.Node()
    const s = new mymath.SymbolNode('bla')
    const n2 = new mymath.FunctionNode(s, [])

    assert.strictEqual(n1.toString(callback1), 'default')
    assert.strictEqual(n2.toString(callback2), 'bla()')
  })

  it('should ignore custom toTex if it returns nothing', function () {
    const callback1 = function (node, callback) {}
    const callback2 = {
      bla: function (node, callbacks) {}
    }
    const mymath = math.create()
    mymath.Node.prototype._toTex = function () {
      return 'default'
    }
    const n1 = new mymath.Node()
    const s = new mymath.SymbolNode('bla')
    const n2 = new mymath.FunctionNode(s, [])

    assert.strictEqual(n1.toTex(callback1), 'default')
    assert.strictEqual(n2.toTex(callback2), '\\mathrm{bla}\\left(\\right)')
  })

  it('should throw an error when compiling an abstract node', function () {
    const node = new Node()
    assert.throws(function () {
      node.compile()
    }, /Error: Method _compile should be implemented by type Node/)
  })

  it('should have an identifier', function () {
    const node = new Node()

    assert.strictEqual(node.getIdentifier(), 'Node')
  })

  it('should get the content of a Node', function () {
    const c = new math.ConstantNode(1)

    assert.strictEqual(c.getContent(), c)
    assert.deepStrictEqual(c.getContent(), c)
  })
})
