/**
 * This file tests the top-chunk optimization logic in splitChunk command.
 * Whenever a chunk is split, the shouldMigrate field should be set if the extreme chunk
 * has only a single document, where extreme chunk is defined as the chunk containing
 * either the upper or lower bound of the entire shard key space.
 */

var st = new ShardingTest({shards: 1});
st.stopBalancer();

var testDB = st.s.getDB('test');
testDB.adminCommand({enableSharding: 'test'});

var callSplit = function(db, minKey, maxKey, splitPoints) {
    var res = st.s.adminCommand({getShardVersion: "test.user"});
    assert.commandWorked(res);
    var shardVersion = [res.version, res.versionEpoch];
    return db.runCommand({
        splitChunk: 'test.user',
        from: 'shard0000',
        min: minKey,
        max: maxKey,
        keyPattern: {x: 1},
        splitKeys: splitPoints,
        shardVersion: shardVersion,
    });
};

var tests = [
    //
    // Lower extreme chunk tests.
    //

    // All chunks have 1 doc.
    //
    // Expected doc counts for new chunks:
    // [ MinKey, -2 ): 1
    // [ -2, -1 ): 1
    // [ -1, 0): 1
    //
    function(db) {
        var res = callSplit(db, {x: MinKey}, {x: 0}, [{x: -2}, {x: -1}]);
        assert.commandWorked(res);
        assert.neq(res.shouldMigrate, null, tojson(res));
        assert(bsonWoCompare(res.shouldMigrate.min, {x: MinKey}) == 0,
               tojson(res.shouldMigrate.min));
        assert(bsonWoCompare(res.shouldMigrate.max, {x: -2}) == 0, tojson(res.shouldMigrate.max));
    },

    // One chunk has single doc, extreme doesn't.
    //
    // Expected doc counts for new chunks:
    // [ MinKey, -1 ): 2
    // [ -1, 0): 1
    //
    function(db) {
        var res = callSplit(db, {x: MinKey}, {x: 0}, [{x: -1}]);
        assert.commandWorked(res);
        assert.eq(res.shouldMigrate, null, tojson(res));
    },

    // Only extreme has single doc.
    //
    // Expected doc counts for new chunks:
    // [ MinKey, -2 ): 1
    // [ -2, 0): 2
    //
    function(db) {
        var res = callSplit(db, {x: MinKey}, {x: 0}, [{x: -2}]);
        assert.commandWorked(res);
        assert.neq(res.shouldMigrate, null, tojson(res));
        assert(bsonWoCompare(res.shouldMigrate.min, {x: MinKey}) == 0,
               tojson(res.shouldMigrate.min));
        assert(bsonWoCompare(res.shouldMigrate.max, {x: -2}) == 0, tojson(res.shouldMigrate.max));
    },

    //
    // Upper extreme chunk tests.
    //

    // All chunks have 1 doc.
    //
    // Expected doc counts for new chunks:
    // [ 0, 1 ): 1
    // [ 1, 2 ): 1
    // [ 2, MaxKey): 1
    //
    function(db) {
        var res = callSplit(db, {x: 0}, {x: MaxKey}, [{x: 1}, {x: 2}]);
        assert.commandWorked(res);
        assert.neq(res.shouldMigrate, null, tojson(res));
        assert(bsonWoCompare(res.shouldMigrate.min, {x: 2}) == 0, tojson(res.shouldMigrate.min));
        assert(bsonWoCompare(res.shouldMigrate.max, {x: MaxKey}) == 0,
               tojson(res.shouldMigrate.max));
    },

    // One chunk has single doc, extreme doesn't.
    //
    // Expected doc counts for new chunks:
    // [ 0, 1 ): 1
    // [ 1, MaxKey): 2
    //
    function(db) {
        var res = callSplit(db, {x: 0}, {x: MaxKey}, [{x: 1}]);
        assert.commandWorked(res);
        assert.eq(res.shouldMigrate, null, tojson(res));
    },

    // Only extreme has single doc.
    //
    // Expected doc counts for new chunks:
    // [ 0, 2 ): 2
    // [ 2, MaxKey): 1
    //
    function(db) {
        var res = callSplit(db, {x: 0}, {x: MaxKey}, [{x: 2}]);
        assert.commandWorked(res);
        assert.neq(res.shouldMigrate, null, tojson(res));
        assert(bsonWoCompare(res.shouldMigrate.min, {x: 2}) == 0, tojson(res.shouldMigrate.min));
        assert(bsonWoCompare(res.shouldMigrate.max, {x: MaxKey}) == 0,
               tojson(res.shouldMigrate.max));
    },
];

tests.forEach(function(test) {
    // setup
    testDB.adminCommand({shardCollection: 'test.user', key: {x: 1}});
    testDB.adminCommand({split: 'test.user', middle: {x: 0}});

    for (var x = -3; x < 3; x++) {
        testDB.user.insert({x: x});
    }

    // run test
    test(st.d0.getDB('admin'));

    // teardown
    testDB.user.drop();
});

st.stop();
