// test ArrayNode
var assert = require('assert');
var approx = require('../../../tools/approx');
var math = require('../../../index');
var Node = math.expression.node.Node;
var ConstantNode = math.expression.node.ConstantNode;
var SymbolNode = math.expression.node.SymbolNode;
var RangeNode = math.expression.node.RangeNode;
var ArrayNode = math.expression.node.ArrayNode;

describe('ArrayNode', function() {

  it ('should create an ArrayNode', function () {
    var c = new ConstantNode(1);
    var a = new ArrayNode([c]);
    var b = new ArrayNode([]);
    assert(a instanceof ArrayNode);
    assert(b instanceof ArrayNode);
    assert.equal(a.type, 'ArrayNode');
    assert.equal(b.type, 'ArrayNode');
  });

  it ('should have isArrayNode', function () {
    var node = new ArrayNode([]);

    assert(node.isArrayNode);
  });

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

  it ('should throw an error on wrong constructor arguments', function () {
    assert.throws(function () {new ArrayNode(2)}, TypeError);
    assert.throws(function () {new ArrayNode([2, 3])}, TypeError);
  });

  it ('should evaluate an ArrayNode', function () {
    var c = new ConstantNode(1);
    var a = new ArrayNode([c]);
    var b = new ArrayNode();

    assert.deepEqual(a.compile().eval(), math.matrix([1]));
    assert.deepEqual(b.compile().eval(), math.matrix([]));
  });

  it ('should compile an ArrayNode and evaluate as Matrix', function () {
    var a = new ConstantNode(1);
    var b = new ConstantNode(2);
    var c = new ConstantNode(3);
    var d = new ConstantNode(4);
    var n = new ArrayNode([a, b, c, d]);

    var expr = n.compile();
    assert.deepEqual(expr.eval(), math.matrix([1,2,3,4]));
  });

  it ('should compile an ArrayNode and evaluate as Array', function () {
    var mathArray = math.create({matrix: 'Array'});
    var a = new mathArray.expression.node.ConstantNode(1);
    var b = new mathArray.expression.node.ConstantNode(2);
    var c = new mathArray.expression.node.ConstantNode(3);
    var d = new mathArray.expression.node.ConstantNode(4);
    var n = new mathArray.expression.node.ArrayNode([a, b, c, d]);
    var expr = n.compile();
    assert.deepEqual(expr.eval(), [1,2,3,4]);
  });

  it ('should compile nested ArrayNodes', function () {
    var a = new ConstantNode(1);
    var b = new ConstantNode(2);
    var c = new ConstantNode(3);
    var d = new ConstantNode(4);

    var n2 = new ArrayNode([a, b]);
    var n3 = new ArrayNode([c, d]);
    var n4 = new ArrayNode([n2, n3]);

    var expr = n4.compile();
    assert.deepEqual(expr.eval(), math.matrix([[1,2],[3,4]]));
  });

  it ('should find an ArrayNode', function () {
    var a = new ConstantNode(1);
    var b = new SymbolNode('x');
    var c = new ConstantNode(2);
    var d = new ArrayNode([a, b, c]);

    assert.deepEqual(d.filter(function (node) {return node instanceof ArrayNode}),     [d]);
    assert.deepEqual(d.filter(function (node) {return node instanceof SymbolNode}),    [b]);
    assert.deepEqual(d.filter(function (node) {return node instanceof RangeNode}),     []);
    assert.deepEqual(d.filter(function (node) {return node instanceof ConstantNode}),  [a, c]);
    assert.deepEqual(d.filter(function (node) {return node instanceof ConstantNode && node.value == '2'}),  [c]);
  });

  it ('should run forEach on an ArrayNode', function () {
    // [x, 2]
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    var d = new ConstantNode(3);
    var nodes = [];
    var paths = [];
    c.forEach(function (node, path, parent) {
      nodes.push(node);
      paths.push(path);
      assert.strictEqual(parent, c);
    });

    assert.deepEqual(paths, ['items[0]', 'items[1]']);
    assert.equal(nodes.length, 2);
    assert.strictEqual(nodes[0], a);
    assert.strictEqual(nodes[1], b);
  });

  it ('should map an ArrayNode', function () {
    // [x, 2]
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    var d = new ConstantNode(3);
    var nodes = [];
    var paths = [];
    var e = c.map(function (node, path, parent) {
      nodes.push(node);
      paths.push(path);
      assert.strictEqual(parent, c);

      return (node instanceof SymbolNode) && (node.name == 'x') ? d : node;
    });

    assert.deepEqual(paths, ['items[0]', 'items[1]']);
    assert.equal(nodes.length, 2);
    assert.strictEqual(nodes[0], a);
    assert.strictEqual(nodes[1], b);

    assert.notStrictEqual(e,  c);
    assert.deepEqual(e.items[0],  d);
    assert.deepEqual(e.items[1],  b);
  });

  it ('should throw an error when the map callback does not return a node', function () {
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

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

  it ('should transform an ArrayNodes parameters', function () {
    // [x, 2]
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    var d = new ConstantNode(3);
    var e = c.transform(function (node) {
      return (node instanceof SymbolNode) && (node.name == 'x') ? d : node;
    });

    assert.notStrictEqual(e,  c);
    assert.deepEqual(e.items[0],  d);
    assert.deepEqual(e.items[1],  b);
  });

  it ('should transform an ArrayNode itself', function () {
    // [x, 2]
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    var d = new ConstantNode(3);
    var e = c.transform(function (node) {
      return (node instanceof ArrayNode) ? d : node;
    });

    assert.notStrictEqual(e, c);
    assert.deepEqual(e, d);
  });

  it ('should traverse an ArrayNode', function () {
    var a = new ConstantNode(1);
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    var count = 0;
    c.traverse(function (node, path, parent) {
      count++;

      switch(count) {
        case 1:
          assert.strictEqual(node, c);
          assert.strictEqual(path, null);
          assert.strictEqual(parent, null);
          break;

        case 2:
          assert.strictEqual(node, a);
          assert.strictEqual(path, 'items[0]');
          assert.strictEqual(parent, c);
          break;

        case 3:
          assert.strictEqual(node, b);
          assert.strictEqual(path, 'items[1]');
          assert.strictEqual(parent, c);
          break;
      }
    });

    assert.equal(count, 3);
  });

  it ('should clone an ArrayNode', function () {
    // [x, 2]
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    var d = c.clone();
    assert(d instanceof ArrayNode);
    assert.deepEqual(c, d);
    assert.notStrictEqual(c, d);
    assert.strictEqual(c.items[0], d.items[0]);
    assert.strictEqual(c.items[1], d.items[1]);
  });

  it ('test equality with other nodes', function () {
    // [x, 2]
    var a = new SymbolNode('x');
    var b = new ConstantNode(2);
    var c = new ArrayNode([a, b]);

    assert.strictEqual(c.equals(null), false);
    assert.strictEqual(c.equals(undefined), false);
    assert.strictEqual(c.equals(new ArrayNode([new SymbolNode('x'), new ConstantNode(2)])), true);
    assert.strictEqual(c.equals(new ArrayNode([new SymbolNode('x'), new ConstantNode(2), new ConstantNode(3)])), false);
    assert.strictEqual(c.equals(new ArrayNode([new SymbolNode('x'), new ConstantNode(3)])), false);
    assert.strictEqual(c.equals(new ArrayNode([new SymbolNode('x')])), false);
  });

  it ('should stringify an ArrayNode', function () {
    var a = new ConstantNode(1);
    var b = new ConstantNode(2);
    var c = new ConstantNode(3);
    var d = new ConstantNode(4);
    var n = new ArrayNode([a, b, c, d]);

    assert.equal(n.toString(), '[1, 2, 3, 4]');
  });

  it ('should stringify an ArrayNode with custom toString', function () {
    //Also checks if the custom functions get passed on to the children
    var customFunction = function (node, options) {
      if (node.type === 'ArrayNode') {
        var string = '[';
        node.items.forEach(function (item) {
          string += item.toString(options) + ', ';
        });

        string += ']';
        return string;
      }
      else if (node.type === 'ConstantNode') {
        return 'const(' + node.value + ', ' + node.valueType + ')'
      }
    };

    var a = new ConstantNode(1);
    var b = new ConstantNode(2);

    var n = new ArrayNode([a, b]);

    assert.equal(n.toString({handler: customFunction}), '[const(1, number), const(2, number), ]');
  });

  it ('should LaTeX an ArrayNode', function () {
    var a = new ConstantNode(1);
    var b = new ConstantNode(2);
    var c = new ConstantNode(3);
    var d = new ConstantNode(4);
    var v1 = new ArrayNode([a, b]);
    var v2 = new ArrayNode([c, d]);
    var n = new ArrayNode([v1, v2]);

    assert.equal(n.toTex(), '\\begin{bmatrix}1&2\\\\3&4\\\\\\end{bmatrix}');
  });

  it ('should LaTeX an ArrayNode with custom toTex', function () {
    //Also checks if the custom functions get passed on to the children
    var customFunction = function (node, options) {
      if (node.type === 'ArrayNode') {
        var latex = '\\left[';
        node.items.forEach(function (item) {
          latex += item.toTex(options) + ', ';
        });

        latex += '\\right]';
        return latex;
      }
      else if (node.type === 'ConstantNode') {
        return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)'
      }
    };

    var a = new ConstantNode(1);
    var b = new ConstantNode(2);

    var n = new ArrayNode([a, b]);

    assert.equal(n.toTex({handler: customFunction}), '\\left[const\\left(1, number\\right), const\\left(2, number\\right), \\right]');
  });

});
