var assert = require('assert');
var jmespath = require('../jmespath');
var tokenize = jmespath.tokenize;
var compile = jmespath.compile;
var strictDeepEqual = jmespath.strictDeepEqual;


describe('tokenize', function() {
    it('should tokenize unquoted identifier', function() {
        assert.deepEqual(tokenize('foo'),
                         [{type: "UnquotedIdentifier",
                          value: "foo",
                          start: 0}]);
    });
    it('should tokenize unquoted identifier with underscore', function() {
        assert.deepEqual(tokenize('_underscore'),
                          [{type: "UnquotedIdentifier",
                           value: "_underscore",
                           start: 0}]);
    });
    it('should tokenize unquoted identifier with numbers', function() {
        assert.deepEqual(tokenize('foo123'),
                          [{type: "UnquotedIdentifier",
                           value: "foo123",
                           start: 0}]);
    });
    it('should tokenize dotted lookups', function() {
        assert.deepEqual(
            tokenize('foo.bar'),
            [{type: "UnquotedIdentifier", value: "foo", start: 0},
             {type: "Dot", value: ".", start: 3},
             {type: "UnquotedIdentifier", value: "bar", start: 4},
            ]);
    });
    it('should tokenize numbers', function() {
        assert.deepEqual(
            tokenize('foo[0]'),
            [{type: "UnquotedIdentifier", value: "foo", start: 0},
             {type: "Lbracket", value: "[", start: 3},
             {type: "Number", value: 0, start: 4},
             {type: "Rbracket", value: "]", start: 5},
            ]);
    });
    it('should tokenize numbers with multiple digits', function() {
        assert.deepEqual(
            tokenize("12345"),
            [{type: "Number", value: 12345, start: 0}]);
    });
    it('should tokenize negative numbers', function() {
        assert.deepEqual(
            tokenize("-12345"),
            [{type: "Number", value: -12345, start: 0}]);
    });
    it('should tokenize quoted identifier', function() {
        assert.deepEqual(tokenize('"foo"'),
                         [{type: "QuotedIdentifier",
                          value: "foo",
                          start: 0}]);
    });
    it('should tokenize quoted identifier with unicode escape', function() {
        assert.deepEqual(tokenize('"\\u2713"'),
                         [{type: "QuotedIdentifier",
                          value: "✓",
                          start: 0}]);
    });
    it('should tokenize literal lists', function() {
        assert.deepEqual(tokenize("`[0, 1]`"),
                         [{type: "Literal",
                          value: [0, 1],
                          start: 0}]);
    });
    it('should tokenize literal dict', function() {
        assert.deepEqual(tokenize("`{\"foo\": \"bar\"}`"),
                         [{type: "Literal",
                          value: {"foo": "bar"},
                          start: 0}]);
    });
    it('should tokenize literal strings', function() {
        assert.deepEqual(tokenize("`\"foo\"`"),
                         [{type: "Literal",
                          value: "foo",
                          start: 0}]);
    });
    it('should tokenize json literals', function() {
        assert.deepEqual(tokenize("`true`"),
                         [{type: "Literal",
                          value: true,
                          start: 0}]);
    });
    it('should not requiring surrounding quotes for strings', function() {
        assert.deepEqual(tokenize("`foo`"),
                         [{type: "Literal",
                          value: "foo",
                          start: 0}]);
    });
    it('should not requiring surrounding quotes for numbers', function() {
        assert.deepEqual(tokenize("`20`"),
                         [{type: "Literal",
                           value: 20,
                           start: 0}]);
    });
    it('should tokenize literal lists with chars afterwards', function() {
        assert.deepEqual(
            tokenize("`[0, 1]`[0]"), [
                {type: "Literal", value: [0, 1], start: 0},
                {type: "Lbracket", value: "[", start: 8},
                {type: "Number", value: 0, start: 9},
                {type: "Rbracket", value: "]", start: 10}
        ]);
    });
    it('should tokenize two char tokens with shared prefix', function() {
        assert.deepEqual(
            tokenize("[?foo]"),
            [{type: "Filter", value: "[?", start: 0},
             {type: "UnquotedIdentifier", value: "foo", start: 2},
             {type: "Rbracket", value: "]", start: 5}]
        );
    });
    it('should tokenize flatten operator', function() {
        assert.deepEqual(
            tokenize("[]"),
            [{type: "Flatten", value: "[]", start: 0}]);
    });
    it('should tokenize comparators', function() {
        assert.deepEqual(tokenize("<"),
                         [{type: "LT",
                          value: "<",
                          start: 0}]);
    });
    it('should tokenize two char tokens without shared prefix', function() {
        assert.deepEqual(
            tokenize("=="),
            [{type: "EQ", value: "==", start: 0}]
        );
    });
    it('should tokenize not equals', function() {
        assert.deepEqual(
            tokenize("!="),
            [{type: "NE", value: "!=", start: 0}]
        );
    });
    it('should tokenize the OR token', function() {
        assert.deepEqual(
            tokenize("a||b"),
            [
                {type: "UnquotedIdentifier", value: "a", start: 0},
                {type: "Or", value: "||", start: 1},
                {type: "UnquotedIdentifier", value: "b", start: 3}
            ]
        );
    });
    it('should tokenize function calls', function() {
        assert.deepEqual(
            tokenize("abs(@)"),
            [
                {type: "UnquotedIdentifier", value: "abs", start: 0},
                {type: "Lparen", value: "(", start: 3},
                {type: "Current", value: "@", start: 4},
                {type: "Rparen", value: ")", start: 5}
            ]
        );
    });

});


describe('parsing', function() {
    it('should parse field node', function() {
        assert.deepEqual(compile('foo'),
                          {type: 'Field', name: 'foo'});
    });
});

describe('strictDeepEqual', function() {
    it('should compare scalars', function() {
        assert.strictEqual(strictDeepEqual('a', 'a'), true);
    });
    it('should be false for different types', function() {
        assert.strictEqual(strictDeepEqual('a', 2), false);
    });
    it('should be false for arrays of different lengths', function() {
        assert.strictEqual(strictDeepEqual([0, 1], [1, 2, 3]), false);
    });
    it('should be true for identical arrays', function() {
        assert.strictEqual(strictDeepEqual([0, 1], [0, 1]), true);
    });
    it('should be true for nested arrays', function() {
        assert.strictEqual(
            strictDeepEqual([[0, 1], [2, 3]], [[0, 1], [2, 3]]), true);
    });
    it('should be true for nested arrays of strings', function() {
        assert.strictEqual(
            strictDeepEqual([["a", "b"], ["c", "d"]],
                            [["a", "b"], ["c", "d"]]), true);
    });
    it('should be false for different arrays of the same length', function() {
        assert.strictEqual(strictDeepEqual([0, 1], [1, 2]), false);
    });
    it('should handle object literals', function() {
        assert.strictEqual(strictDeepEqual({a: 1, b: 2}, {a: 1, b: 2}), true);
    });
    it('should handle keys in first not in second', function() {
        assert.strictEqual(strictDeepEqual({a: 1, b: 2}, {a: 1}), false);
    });
    it('should handle keys in second not in first', function() {
        assert.strictEqual(strictDeepEqual({a: 1}, {a: 1, b: 2}), false);
    });
    it('should handle nested objects', function() {
        assert.strictEqual(
            strictDeepEqual({a: {b: [1, 2]}},
                            {a: {b: [1, 2]}}), true);
    });
    it('should handle nested objects that are not equal', function() {
        assert.strictEqual(
            strictDeepEqual({a: {b: [1, 2]}},
                            {a: {b: [1, 4]}}), false);
    });
});

describe('search', function() {
    it(
        'should throw a readable error when invalid arguments are provided to a function',
        function() {
            try {
                jmespath.search([], 'length(`null`)');
            } catch (e) {
                assert(e.message.search(
                    'expected argument 1 to be type string,array,object'
                ), e.message);
                assert(e.message.search('received type null'), e.message);
            }
        }
    );
});
