t = db.borders
t.drop()

epsilon = 0.0001;

// For these tests, *required* that step ends exactly on max
min = -1
max = 1
step = 1
numItems = 0;

for ( var x = min; x <= max; x += step ) {
    for ( var y = min; y <= max; y += step ) {
        t.insert( { loc : { x : x, y : y } } )
        numItems++;
    }
}

overallMin = -1
overallMax = 1

// Create a point index slightly smaller than the points we have
var res = t.ensureIndex({ loc: "2d" },
                        { max: overallMax - epsilon / 2,
                          min: overallMin + epsilon / 2 });
assert.commandFailed(res);

// Create a point index only slightly bigger than the points we have
res = t.ensureIndex( { loc : "2d" }, { max : overallMax + epsilon, min : overallMin - epsilon } );
assert.commandWorked(res);

// ************
// Box Tests
// ************

// If the bounds are bigger than the box itself, just clip at the borders
assert.eq( numItems, t.find(
        { loc : { $within : { $box : [
                [ overallMin - 2 * epsilon, overallMin - 2 * epsilon ],
                [ overallMax + 2 * epsilon, overallMax + 2 * epsilon ] ] } } } ).count() );

// Check this works also for bounds where only a single dimension is off-bounds
assert.eq( numItems - 5, t.find(
        { loc : { $within : { $box : [
                [ overallMin - 2 * epsilon, overallMin - 0.5 * epsilon ],
                [ overallMax - epsilon, overallMax - epsilon ] ] } } } ).count() );

// Make sure we can get at least close to the bounds of the index
assert.eq( numItems, t.find(
        { loc : { $within : { $box : [
                [ overallMin - epsilon / 2, overallMin - epsilon / 2 ],
                [ overallMax + epsilon / 2, overallMax + epsilon / 2 ] ] } } } ).count() );

// Make sure we can get at least close to the bounds of the index
assert.eq( numItems, t.find(
        { loc : { $within : { $box : [
                [ overallMax + epsilon / 2, overallMax + epsilon / 2 ],
                [ overallMin - epsilon / 2, overallMin - epsilon / 2 ] ] } } } ).count() );

// Check that swapping min/max has good behavior
assert.eq( numItems, t.find(
        { loc : { $within : { $box : [
                [ overallMax + epsilon / 2, overallMax + epsilon / 2 ],
                [ overallMin - epsilon / 2, overallMin - epsilon / 2 ] ] } } } ).count() );

assert.eq( numItems, t.find(
        { loc : { $within : { $box : [
                [ overallMax + epsilon / 2, overallMin - epsilon / 2 ],
                [ overallMin - epsilon / 2, overallMax + epsilon / 2 ] ] } } } ).count() );

// **************
// Circle tests
// **************

center = ( overallMax + overallMin ) / 2
center = [ center, center ]
radius = overallMax

offCenter = [ center[0] + radius, center[1] + radius ]
onBounds = [ offCenter[0] + epsilon, offCenter[1] + epsilon ]
offBounds = [ onBounds[0] + epsilon, onBounds[1] + epsilon ]
onBoundsNeg = [ -onBounds[0], -onBounds[1] ]

// Make sure we can get all points when radius is exactly at full bounds
assert.lt( 0, t.find( { loc : { $within : { $center : [ center, radius + epsilon ] } } } ).count() );

// Make sure we can get points when radius is over full bounds
assert.lt( 0, t.find( { loc : { $within : { $center : [ center, radius + 2 * epsilon ] } } } ).count() );

// Make sure we can get points when radius is over full bounds, off-centered
assert.lt( 0, t.find( { loc : { $within : { $center : [ offCenter, radius + 2 * epsilon ] } } } ).count() );

// Make sure we get correct corner point when center is in bounds
// (x bounds wrap, so could get other corner)
cornerPt = t.findOne( { loc : { $within : { $center : [ offCenter, step / 2 ] } } } );
assert.eq( cornerPt.loc.y, overallMax )

// Make sure we get correct corner point when center is on bounds
// NOTE: Only valid points on MIN bounds
cornerPt = t
        .findOne( { loc : { $within : { $center : [ onBoundsNeg, Math.sqrt( 2 * epsilon * epsilon ) + ( step / 2 ) ] } } } );
assert.eq( cornerPt.loc.y, overallMin )

// Make sure we can't get corner point when center is over bounds
assert.throws(function(){
    t.findOne( { loc : { $within : { $center : [ offBounds, Math.sqrt( 8 * epsilon * epsilon ) + ( step / 2 ) ] } } } );
});

// Make sure we can't get corner point when center is on max bounds
// Broken - see SERVER-13581
//assert.throws(function(){
//    t.findOne( { loc : { $within : { $center : [ onBounds, Math.sqrt( 8 * epsilon * epsilon ) + ( step / 2 ) ] } } } );
//});

// ***********
// Near tests
// ***********

// Make sure we can get all nearby points to point in range
assert.eq( overallMax, t.find( { loc : { $near : offCenter } } ).next().loc.y );

// Make sure we can get all nearby points to point on boundary
assert.eq( overallMin, t.find( { loc : { $near : onBoundsNeg } } ).next().loc.y );

// Make sure we can't get all nearby points to point over boundary
assert.throws(function(){
    t.findOne( { loc : { $near : offBounds } } );
});

// Make sure we can't get all nearby points to point on max boundary
//Broken - see SERVER-13581
//assert.throws(function(){
//    t.findOne( { loc : { $near : onBoundsNeg } } );
//});

// Make sure we can get all nearby points within one step (4 points in top
// corner)
assert.eq( 4, t.find( { loc : { $near : offCenter, $maxDistance : step * 1.9 } } ).count() );

// **************
// Command Tests
// **************
// Make sure we can get all nearby points to point in range
assert.eq( overallMax, db.runCommand( { geoNear : "borders", near : offCenter } ).results[0].obj.loc.y );

// Make sure we can get all nearby points to point on boundary
assert.eq( overallMin, db.runCommand( { geoNear : "borders", near : onBoundsNeg } ).results[0].obj.loc.y );

// Make sure we can't get all nearby points to point over boundary
assert.commandFailed( db.runCommand( { geoNear : "borders", near : offBounds } ));

// Make sure we can't get all nearby points to point on max boundary
assert.commandWorked( db.runCommand( { geoNear : "borders", near : onBounds } ));

// Make sure we can get all nearby points within one step (4 points in top
// corner)
assert.eq( 4, db.runCommand( { geoNear : "borders", near : offCenter, maxDistance : step * 1.5 } ).results.length );
