/**
 * Basic integration tests for the $let expression.
 */
(function() {
"use strict";

load("jstests/aggregation/extras/utils.js");  // For assertErrorCode.

let coll = db.agg_expr_let;
coll.drop();
assert.commandWorked(coll.insert({zero: 0, one: 1, two: 2, three: 3, nested: {four: 4}}));

function testExpr(expression, output) {
    const res = coll.aggregate([{$project: {output: expression}}]).toArray();
    assert.eq(res.length, 1, tojson(res));
    assert.eq(res[0].output, output, tojson(res));

    // Test in group:
    const result = coll.aggregate({$group: {_id: 0, res: {$sum: expression}}}).toArray();
    assert.eq(result, [{_id: 0, res: output}]);
}

// Basic tests.
testExpr('$two', 2);
testExpr('$$CURRENT.two', 2);
testExpr('$$ROOT.two', 2);

// Using sub expressions.
testExpr({$add: ['$two', '$$CURRENT.three']}, 5);
testExpr({$add: ['$$CURRENT.two', '$$ROOT.nested.four']}, 6);

// Verify that the variables defined in $let work.
testExpr({$let: {vars: {a: 10}, in : '$$a'}}, 10);
testExpr({$let: {vars: {a: '$zero'}, in : '$$a'}}, 0);
testExpr({$let: {vars: {a: {$add: ['$one', '$two']}, b: 10}, in : {$multiply: ['$$a', '$$b']}}},
         30);

// Verify that the outer level variable works in inner level $let.
testExpr({
    $let:
        {vars: {var1: 1}, in : {$let: {vars: {var2: "$$var1"}, in : {$sum: ["$$var1", "$$var2"]}}}}
},
         2);

// Verify that the outer level variables get overwritten by inner level variables.
testExpr({
        $let: {
            vars: {var1: "$one"},
            in : {$let: {vars: {var2: "$$var1", var1: 3}, in : {$sum: ["$$var2", "$$var1"]}}}
        }
    },
             4);

// $let changing CURRENT
testExpr({$let: {vars: {CURRENT: '$$ROOT.nested'}, in : {$multiply: ['$four', '$$ROOT.two']}}}, 8);
testExpr({
    $let: {
        vars: {CURRENT: '$$CURRENT.nested'},  // using original value of CURRENT
        in : {$multiply: ['$four', '$$ROOT.two']}
    }
},
         8);
testExpr({
    $let: {
        vars: {CURRENT: '$nested'},  // same as last
        in : {$multiply: ['$four', '$$ROOT.two']}
    }
},
         8);
testExpr({
    $let: {
        vars: {CURRENT: {$const: {ten: 10}}},  // "artificial" object
        in : {$multiply: ['$ten', '$$ROOT.two']}
    }
},
         20);
testExpr({
    $let: {
        vars: {CURRENT: '$three'},  // sets current to the number 3 (not an object)
        in : {$multiply: ['$$CURRENT', '$$ROOT.two']}
    }
},
         6);

// Swapping with $let (ensures there is no ordering dependency in vars).
testExpr({
        $let: {
            vars: {x: 6, y: 10},
            in : {
                $let: {
                    vars: {x: '$$y', y: '$$x'},  // now {x:10, y:6}
                    in : {$subtract: ['$$x', '$$y']}
                }
            }
        }
    },            // Not commutative!
             4);  // 10-6 not 6-10 or 6-6

// Unicode is allowed.
testExpr({$let: {vars: {'日本語': 10}, in : '$$日本語'}},
         10);  // Japanese for "Japanese language".

// Can use ROOT and CURRENT directly with no subfield (SERVER-5916).
coll.drop();
coll.insert({_id: 'obj'});
assert.eq(coll.aggregate({$project: {_id: 0, obj: '$$ROOT'}}).toArray(), [{obj: {_id: 'obj'}}]);
assert.eq(coll.aggregate({$project: {_id: 0, obj: '$$CURRENT'}}).toArray(), [{obj: {_id: 'obj'}}]);
assert.eq(coll.aggregate({$group: {_id: 0, objs: {$push: '$$ROOT'}}}).toArray(),
          [{_id: 0, objs: [{_id: 'obj'}]}]);
assert.eq(coll.aggregate({$group: {_id: 0, objs: {$push: '$$CURRENT'}}}).toArray(),
          [{_id: 0, objs: [{_id: 'obj'}]}]);

// Check name validity checks.
assertErrorCode(
    coll, {$project: {a: {$let: {vars: {ROOT: 1}, in : '$$ROOT'}}}}, ErrorCodes.FailedToParse);
assertErrorCode(
    coll, {$project: {a: {$let: {vars: {FOO: 1}, in : '$$FOO'}}}}, ErrorCodes.FailedToParse);
assertErrorCode(
    coll, {$project: {a: {$let: {vars: {_underbar: 1}, in : '$$FOO'}}}}, ErrorCodes.FailedToParse);
assertErrorCode(
    coll, {$project: {a: {$let: {vars: {'a.b': 1}, in : '$$FOO'}}}}, ErrorCodes.FailedToParse);
assertErrorCode(
    coll, {$project: {a: {$let: {vars: {'a b': 1}, in : '$$FOO'}}}}, ErrorCodes.FailedToParse);
assertErrorCode(coll, {$project: {a: '$$_underbar'}}, ErrorCodes.FailedToParse);
assertErrorCode(coll, {$project: {a: '$$with spaces'}}, ErrorCodes.FailedToParse);

// Verify that variables defined in '$let' cannot be used to initialize other variables.
assertErrorCode(
    coll,
    [{$project: {output: {$let: {vars: {var1: "$one", var2: "$$var1"}, in : "$$var1"}}}}],
    17276);
}());
