// test RelationalNode
const assert = require('assert')
const math = require('../../../src/main')
const Node = math.expression.node.Node
const ConstantNode = math.expression.node.ConstantNode
const SymbolNode = math.expression.node.SymbolNode
const RelationalNode = math.expression.node.RelationalNode

describe('RelationalNode', function () {
  const one = new ConstantNode(1)
  const two = new ConstantNode(2)
  const three = new ConstantNode(3)
  const four = new ConstantNode(4)

  it('should create a RelationalNode', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])
    assert(n instanceof RelationalNode)
    assert(n instanceof Node)
    assert.strictEqual(n.type, 'RelationalNode')
  })

  it('should have isRelationalNode', function () {
    const node = new RelationalNode(['smaller', 'smaller'], [one, two, three])
    assert(node.isRelationalNode)
  })

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

  it('should throw an error when creating without arguments', function () {
    assert.throws(function () { console.log(new RelationalNode()) }, TypeError)
    assert.throws(function () { console.log(new RelationalNode('smaller')) }, TypeError)
    assert.throws(function () { console.log(new RelationalNode(['smaller'])) }, TypeError)
    assert.throws(function () { console.log(new RelationalNode(['smaller'], one)) }, TypeError)
    assert.throws(function () { console.log(new RelationalNode(['smaller'], [one])) }, TypeError)
  })

  it('should evaluate a RelationalNode', function () {
    let n = new RelationalNode(['smaller', 'smaller'], [one, two, three])
    let expr = n.compile()
    let scope = {}
    assert.strictEqual(expr.eval(scope), true)

    n = new RelationalNode(['smaller', 'smaller'], [three, two, one])
    expr = n.compile()
    assert.strictEqual(expr.eval(scope), false)
  })

  it('should filter a RelationalNode', function () {
    let n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    assert.deepStrictEqual(n.filter(function (node) { return node instanceof RelationalNode }), [n])
    assert.deepStrictEqual(n.filter(function (node) { return node instanceof ConstantNode }), [one, two, three])
    assert.deepStrictEqual(n.filter(function (node) { return node instanceof ConstantNode && node.value === 2 }), [two])
  })

  it('should run forEach on a RelationalNode', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    const nodes = []
    const paths = []
    n.forEach(function (node, path, parent) {
      nodes.push(node)
      paths.push(path)
      assert.strictEqual(parent, n)
    })

    assert.strictEqual(nodes.length, 3)
    assert.strictEqual(nodes[0], one)
    assert.strictEqual(nodes[1], two)
    assert.strictEqual(nodes[2], three)
    assert.deepStrictEqual(paths, ['params[0]', 'params[1]', 'params[2]'])
  })

  it('should map a RelationalNode', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    const nodes = []
    const paths = []
    const f = n.map(function (node, path, parent) {
      nodes.push(node)
      paths.push(path)
      assert.strictEqual(parent, n)

      return node instanceof ConstantNode && node.value === 1 ? four : node
    })

    assert.strictEqual(nodes.length, 3)
    assert.strictEqual(nodes[0], one)
    assert.strictEqual(nodes[1], two)
    assert.strictEqual(nodes[2], three)
    assert.deepStrictEqual(paths, ['params[0]', 'params[1]', 'params[2]'])

    assert.notStrictEqual(f, n)
    assert.strictEqual(f.params[0], four)
    assert.strictEqual(f.params[1], two)
    assert.strictEqual(f.params[2], three)
  })

  it('should throw an error when the map callback does not return a node', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    assert.throws(function () {
      n.map(function () {})
    }, /Callback function must return a Node/)
  })

  it('should transform a RelationalNodes param', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    const f = n.transform(function (node) {
      return node instanceof ConstantNode && node.value === 1 ? four : node
    })

    assert.notStrictEqual(f, n)
    assert.deepStrictEqual(f.params[0], four)
    assert.deepStrictEqual(f.params[1], two)
    assert.deepStrictEqual(f.params[2], three)
  })

  it('should transform a RelationalNode itself', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    const f = n.transform(function (node) {
      return node instanceof RelationalNode ? four : node
    })

    assert.notStrictEqual(f, n)
    assert.deepStrictEqual(f, four)
  })

  it('should clone a RelationalNode itself', function () {
    const n = new RelationalNode(['smaller', 'smaller'], [one, two, three])

    const m = n.clone()

    assert(m instanceof RelationalNode)
    assert.deepStrictEqual(m, n)
    assert.notStrictEqual(m, n)
    assert.strictEqual(m.params[0], n.params[0])
    assert.strictEqual(m.params[1], n.params[1])
    assert.strictEqual(m.params[2], n.params[2])
    assert.strictEqual(m.conditionals[0], n.conditionals[0])
    assert.strictEqual(m.conditionals[1], n.conditionals[1])
  })

  it('test equality another Node', function () {
    const n1 = new RelationalNode(['smaller', 'smaller'], [one, two, three])
    const n2 = new RelationalNode(['smaller', 'smaller'], [one, two, three])
    const m = new RelationalNode(['smaller', 'larger'], [one, two, three])
    const p = new RelationalNode(['smaller', 'smaller', 'larger'], [one, two, three, four])
    const q = new RelationalNode(['smaller', 'smaller'], [two, two, two])

    assert.strictEqual(n1.equals(null), false)
    assert.strictEqual(n1.equals(undefined), false)
    assert.strictEqual(n1.equals(n2), true)
    assert.strictEqual(n1.equals(m), false)
    assert.strictEqual(n1.equals(p), false)
    assert.strictEqual(n1.equals(q), false)
  })

  it('should perform short-circuit evaluation', function () {
    const n = math.parse('(a = a+1) > (b = b+1) > (c = c+1) > (d = d+1)')
    const scope = { a: 0, b: 0, c: 0, d: 0 }
    let result = n.eval(scope)
    assert.strictEqual(scope.a, 1)
    assert.strictEqual(scope.b, 1)
    assert.strictEqual(scope.c, 0)
    assert.strictEqual(scope.d, 0)
    assert.strictEqual(result, false)
  })

  it('should not evaluate params more than once', function () {
    const n = math.parse('(a = a+1) >= (b = b+1) >= (c = c+1) >= (d = d+1)')
    const scope = { a: 0, b: 0, c: 0, d: 0 }
    let result = n.eval(scope)
    assert.strictEqual(scope.a, 1)
    assert.strictEqual(scope.b, 1)
    assert.strictEqual(scope.c, 1)
    assert.strictEqual(scope.d, 1)
    assert.strictEqual(result, true)
  })

  it('should respect the \'all\' parenthesis option', function () {
    assert.strictEqual(math.parse('a<b<c').toString({ parenthesis: 'all' }), '(a) < (b) < (c)')
  })

  it('should stringify a RelationalNode', function () {
    const n1 = new RelationalNode(['smaller', 'smaller'], [one, two, three])
    const n2 = new RelationalNode(['smaller', 'larger', 'smallerEq', 'largerEq', 'equal', 'unequal'], [one, three, two, three, two, two, one])
    const n3 = math.parse('(1 < 2 < 3) == (6 > 5 > 4) != (1 > 2 > 3)')

    assert.strictEqual(n1.toString(), '1 < 2 < 3')
    assert.strictEqual(n2.toString(), '1 < 3 > 2 <= 3 >= 2 == 2 != 1')
    assert.strictEqual(n3.toString(), '(1 < 2 < 3) == (6 > 5 > 4) != (1 > 2 > 3)')
  })

  it('should stringify a RelationalNode with custom toString', function () {
    // Also checks if the custom functions get passed on to the children
    const customFunction = function (node, options) {
      if (node.type === 'RelationalNode') {
        let ret = node.params[0].toString(options)
        for (let i = 0; i < node.conditionals.length; i++) {
          ret += ' ' + node.conditionals[i] + ' ' + node.params[i + 1].toString(options)
        }
        return ret
      } else if (node.type === 'ConstantNode') {
        switch (node.value) {
          case 1: return 'one'
          case 2: return 'two'
          case 3: return 'three'
          default: return 'NaN'
        }
      }
    }

    const n = new RelationalNode(['smaller', 'larger', 'smallerEq', 'largerEq', 'equal', 'unequal'], [one, three, two, three, two, two, one])

    assert.strictEqual(n.toString({ handler: customFunction }), 'one smaller three larger two smallerEq three largerEq two equal two unequal one')
  })

  it('toJSON and fromJSON', function () {
    const x = new SymbolNode('x')
    const n = new RelationalNode(['smaller', 'smaller'], [one, x, three])

    const json = n.toJSON()

    assert.deepStrictEqual(json, {
      mathjs: 'RelationalNode',
      conditionals: ['smaller', 'smaller'],
      params: [one, x, three]
    })

    const parsed = RelationalNode.fromJSON(json)
    assert.deepStrictEqual(parsed, n)
  })

  it('should LaTeX a RelationalNode', function () {
    const x = new SymbolNode('x')
    const n = new RelationalNode(['smaller', 'smaller'], [one, x, three])

    assert.strictEqual(n.toTex(), '1< x<3')
    assert.strictEqual(math.parse('1<x<sqrt(1+2)').toTex(), '1< x<\\sqrt{1+2}')
    assert.strictEqual(math.parse('(-1 < 4/2 < 3) == (3! > 2+3 > 4) != (1 > 2 > 1*3)').toTex(), '\\left(-1<\\frac{4}{2}<3\\right)=\\left(3!>2+3>4\\right)\\neq\\left(1>2>1\\cdot3\\right)')
  })

  it('should HTML a RelationalNode', function () {
    const x = new SymbolNode('x')
    const n = new RelationalNode(['smaller', 'smaller'], [one, x, three])

    assert.strictEqual(n.toHTML(), '<span class="math-number">1</span><span class="math-operator math-binary-operator math-explicit-binary-operator">&lt;</span><span class="math-symbol">x</span><span class="math-operator math-binary-operator math-explicit-binary-operator">&lt;</span><span class="math-number">3</span>')
  })
})
