//
// Utilities related to background operations while other operations are working
//

/**
 * Allows synchronization between background ops and the test operations
 */
var waitForLock = function( mongo, name ){
    
    var ts = new ObjectId()
    var lockColl = mongo.getCollection( "config.testLocks" )
    
    lockColl.update({ _id : name, state : 0 }, { $set : { state : 0 } }, true)
    
    //
    // Wait until we can set the state to 1 with our id
    //
    
    var startTime = new Date().getTime()
    
    assert.soon( function() {
        lockColl.update({ _id : name, state : 0 }, { $set : { ts : ts, state : 1 } })
        var gleObj = lockColl.getDB().getLastErrorObj()
        
        if( new Date().getTime() - startTime > 20 * 1000 ){
            print( "Waiting for..." )
            printjson( gleObj )
            printjson( lockColl.findOne() )
            printjson( ts )
        }
        
        return gleObj.n == 1 || gleObj.updatedExisting
    }, "could not acquire lock", 30 * 1000, 100 )
    
    print( "Acquired lock " + tojson( { _id : name, ts : ts } ) + " curr : " + 
           tojson( lockColl.findOne({ _id : name }) ) )
    
    // Set the state back to 0
    var unlock = function(){
        print( "Releasing lock " + tojson( { _id : name, ts : ts } ) + " curr : " + 
                tojson( lockColl.findOne({ _id : name }) ) )
        lockColl.update({ _id : name, ts : ts }, { $set : { state : 0 } })
    }
    
    // Return an object we can invoke unlock on
    return { unlock : unlock }
}

/**
 * Allows a test or background op to say it's finished
 */
var setFinished = function( mongo, name, finished ){
    if( finished || finished == undefined )
        mongo.getCollection( "config.testFinished" ).update({ _id : name }, { _id : name }, true )
    else
        mongo.getCollection( "config.testFinished" ).remove({ _id : name })
}

/**
 * Checks whether a test or background op is finished
 */
var isFinished = function( mongo, name ){
    return mongo.getCollection( "config.testFinished" ).findOne({ _id : name }) != null 
}

/**
 * Sets the result of a background op
 */
var setResult = function( mongo, name, result, err ){
    mongo.getCollection( "config.testResult" ).update({ _id : name }, { _id : name, result : result, err : err }, true )
}

/**
 * Gets the result for a background op
 */
var getResult = function( mongo, name ){
    return mongo.getCollection( "config.testResult" ).findOne({ _id : name })
}

/**
 * Overrides the parallel shell code in mongo
 */
function startParallelShell( jsCode, port ){
    
    var x;
    if ( port ) {
        x = startMongoProgramNoConnect( "mongo" , "--port" , port , "--eval" , jsCode );
    } else {
        x = startMongoProgramNoConnect( "mongo" , "--eval" , jsCode , db ? db.getMongo().host : null );        
    }
    
    return function(){
        jsTestLog( "Waiting for shell " + x + "..." )
        waitProgram( x );
        jsTestLog( "Shell " + x + " finished." )
    };
}

startParallelOps = function( mongo, proc, args, context ){
                 
    var procName = proc.name + "-" + new ObjectId()
    var seed = new ObjectId( new ObjectId().valueOf().split("").reverse().join("") )
                                                                           .getTimestamp().getTime()
       
    // Make sure we aren't finished before we start
    setFinished( mongo, procName, false )
    setResult( mongo, procName, undefined, undefined )
    
    // TODO: Make this a context of its own
    var procContext = { procName : procName,
                        seed : seed,
                        waitForLock : waitForLock,
                        setFinished : setFinished,
                        isFinished : isFinished,
                        setResult : setResult,
                        
                        setup : function( context, stored ){
                            
                            waitForLock = function(){
                                return context.waitForLock( db.getMongo(), context.procName )
                            }
                            setFinished = function( finished ){
                                return context.setFinished( db.getMongo(), context.procName, finished )
                            }
                            isFinished = function(){
                                return context.isFinished( db.getMongo(), context.procName )
                            }
                            setResult = function( result, err ){
                                return context.setResult( db.getMongo(), context.procName, result, err )
                            }
                        }}
                    
    var bootstrapper = function( stored ){
                
        var procContext = stored.procContext        
        procContext.setup( procContext, stored )
        
        var contexts = stored.contexts                                            
        eval( "contexts = " + contexts )
        
        for( var i = 0; i < contexts.length; i++ ){
            if( typeof( contexts[i] ) != "undefined" ){                
                // Evaluate all contexts
                contexts[i]( procContext )
            }
        }
        
        var operation = stored.operation
        eval( "operation = " + operation )
        
        var args = stored.args
        eval( "args = " + args )
        
        result = undefined
        err = undefined
        
        try{
            result = operation.apply( null, args )
        }
        catch( e ){
            err = e 
        }
        
        setResult( result, err )
    }
    
    var contexts = [ RandomFunctionContext, context ]
    
    var testDataColl = mongo.getCollection( "config.parallelTest" )
    
    testDataColl.insert({ _id : procName,
                          bootstrapper : tojson( bootstrapper ),
                          operation : tojson( proc ),
                          args : tojson( args ),
                          procContext : procContext,
                          contexts : tojson( contexts ) })
                          
    assert.eq( null, testDataColl.getDB().getLastError() )
    
    var bootstrapStartup =
        "{ var procName = '" + procName + "'; " +    
          "var stored = db.getMongo().getCollection( '" + testDataColl + "' )" + 
                         ".findOne({ _id : procName }); " +
          "var bootstrapper = stored.bootstrapper; " +
          "eval( 'bootstrapper = ' + bootstrapper ); " +
          "bootstrapper( stored ); " +
        "}"
    
          
    var oldDB = db
    db = mongo.getDB( "test" )
    
    jsTest.log( "Starting " + proc.name + " operations..." )
    
    var rawJoin = startParallelShell( bootstrapStartup )
                               
    db = oldDB
          
    
    var join = function(){ 
        setFinished( mongo, procName, true )
        
        rawJoin();
        result = getResult( mongo, procName )
                           
        assert.neq( result, null )                 
                           
        if( result.err ) throw "Error in parallel ops " + procName + " : " 
                                                        + tojson( result.err )
                                                        
        else return result.result
    }
    
    join.isFinished = function(){
        return isFinished( mongo, procName )
    }
    
    join.setFinished = function( finished ){
        return setFinished( mongo, procName, finished )
    }
    
    join.waitForLock = function( name ){
        return waitForLock( mongo, name )
    }
    
    return join
}

var RandomFunctionContext = function( context ){
    
    Random.srand( context.seed );
    
    Random.randBool = function(){ return Random.rand() > 0.5 }
    
    Random.randInt = function( min, max ){ 
        
        if( max == undefined ){
            max = min
            min = 0
        }
        
        return min + Math.floor( Random.rand() * max ) 
    }
    
    Random.randShardKey = function(){
        
        var numFields = 2 //Random.randInt(1, 3)
        
        var key = {}
        for( var i = 0; i < numFields; i++ ){
            var field = String.fromCharCode( "a".charCodeAt() + i )
            key[ field ] = 1
        }
        
        return key
    }
    
    Random.randShardKeyValue = function( shardKey ){
        
        var keyValue = {}
        for( field in shardKey ){
            keyValue[ field ] = Random.randInt(1, 100)
        }
        
        return keyValue
    }
    
    Random.randCluster = function(){

        var numShards = 2 //Random.randInt( 1, 10 )
        var rs = false //Random.randBool()
        var st = new ShardingTest({ shards : numShards, 
                                    mongos : 4, 
                                    other : { separateConfig : true, rs : rs } })
        
        return st
    }
}


//
// Some utility operations
//

function moveOps( collName, options ){
    
    options = options || {}
    
    var admin = db.getMongo().getDB( "admin" )
    var config = db.getMongo().getDB( "config" )
    var shards = config.shards.find().toArray()
    var shardKey = config.collections.findOne({ _id : collName }).key
    
    while( ! isFinished() ){
        
        var findKey = Random.randShardKeyValue( shardKey )
        var toShard = shards[ Random.randInt( shards.length ) ]._id
        
        try {
            printjson( admin.runCommand({ moveChunk : collName, 
                                          find : findKey,
                                          to : toShard }) )
        }
        catch( e ){
            printjson( e )
        }
        
        sleep( 1000 )
    }
    
    jsTest.log( "Stopping moveOps..." )
}

function splitOps( collName, options ){
        
    options = options || {}
    
    var admin = db.getMongo().getDB( "admin" )
    var config = db.getMongo().getDB( "config" )
    var shards = config.shards.find().toArray()
    var shardKey = config.collections.findOne({ _id : collName }).key
    
    while( ! isFinished() ){
        
        var middleKey = Random.randShardKeyValue( shardKey )
        
        try {
            printjson( admin.runCommand({ split : collName, 
                                          middle : middleKey }) )
        }
        catch( e ){
            printjson( e )
        }
        
        sleep( 1000 )
    }
    
    jsTest.log( "Stopping splitOps..." )
}

