// Tests a mapReduce for a relatively simple use case of computing an average. This is interesting
// because it does more than a simple sum for the reduce function and also needs to use a finalize
// function to do the final division to compute the average from the sum and totals.
//
// Cannot implicitly shard accessed collections because mapReduce cannot replace a sharded
// collection as output.
// @tags: [
//   assumes_unsharded_collection,
//   # mapReduce does not support afterClusterTime.
//   does_not_support_causal_consistency,
//   does_not_support_stepdowns,
//   uses_map_reduce_with_temp_collections,
// ]
(function() {
"use strict";

const coll = db.mr_blog_posts;
coll.drop();

assert.commandWorked(coll.insert([
    {
        _id: "blog 1",
        author: "x",
        comments: [{user_id: "a", txt: "asdasdasd"}, {user_id: "b", txt: "asdasdasdasdasdasdas"}]
    },
    {
        _id: "blog 2",
        author: "y",
        comments: [{user_id: "b", txt: "asdasdasdaaa"}, {user_id: "c", txt: "asdasdasdaasdasdas"}]
    }
]));

function mapFn() {
    for (let comment of this.comments) {
        emit(comment.user_id, {totalSize: comment.txt.length, num: 1});
    }
}

function reduceFn(user_id, values) {
    let reduced = {totalSize: 0, num: 0};
    for (let value of values) {
        reduced.totalSize += value.totalSize;
        reduced.num += value.num;
    }
    return reduced;
}

const outputColl = db.mr_compute_avg_out;
outputColl.drop();

function reformat(cmdResult) {
    let x = {};
    let cursor;
    if (cmdResult.results)
        cursor = cmdResult.results;
    else
        cursor = outputColl.find();
    cursor.forEach(result => {
        x[result._id] = result.value;
    });
    return x;
}

function finalizeFn(user_id, res) {
    res.avg = res.totalSize / res.num;
    return res;
}

let res = coll.mapReduce(mapFn, reduceFn, {finalize: finalizeFn, out: outputColl.getName()});
assert.commandWorked(res);
let resultAsSingleObj = reformat(res);
assert.eq(9, resultAsSingleObj.a.avg, () => tojson(resultAsSingleObj));
assert.eq(16, resultAsSingleObj.b.avg, () => tojson(resultAsSingleObj));
assert.eq(18, resultAsSingleObj.c.avg, () => tojson(resultAsSingleObj));
outputColl.drop();

res = coll.mapReduce(mapFn, reduceFn, {finalize: finalizeFn, out: {inline: 1}});
assert.commandWorked(res);
resultAsSingleObj = reformat(res);
assert.eq(9, resultAsSingleObj.a.avg, () => tojson(resultAsSingleObj));
assert.eq(16, resultAsSingleObj.b.avg, () => tojson(resultAsSingleObj));
assert.eq(18, resultAsSingleObj.c.avg, () => tojson(resultAsSingleObj));
outputColl.drop();
assert(!("result" in res), () => `Expected inline output with 'results': ${tojson(res)}`);

res = coll.mapReduce(mapFn, reduceFn, {finalize: finalizeFn, out: {inline: 1}});
assert.commandWorked(res);
resultAsSingleObj = reformat(res);
assert.eq(9, resultAsSingleObj.a.avg, () => tojson(resultAsSingleObj));
assert.eq(16, resultAsSingleObj.b.avg, () => tojson(resultAsSingleObj));
assert.eq(18, resultAsSingleObj.c.avg, () => tojson(resultAsSingleObj));
assert(!("result" in res), () => `Expected inline output with 'results': ${tojson(res)}`);
}());
