text: "use strict";
  
  var f = require('util').format
    , crypto = require('crypto')
    , MongoError = require('../error');
  
  var AuthSession = function(db, username, password, options) {
    this.db = db;
    this.username = username;
    this.password = password;
    this.options = options;
  }
  
  AuthSession.prototype.equal = function(session) {
    return session.db == this.db 
      && session.username == this.username
      && session.password == this.password;
  }
  
  // Kerberos class
  var Kerberos = null;
  var MongoAuthProcess = null;
  
  // Try to grab the Kerberos class
  try {
    Kerberos = require('kerberos').Kerberos
    // Authentication process for Mongo
    MongoAuthProcess = require('kerberos').processes.MongoAuthProcess
  } catch(err) {}
  
  /**
   * Creates a new GSSAPI authentication mechanism
   * @class
   * @return {GSSAPI} A cursor instance
   */
  var GSSAPI = function() {
    this.authStore = [];
  }
  
  /**
   * Authenticate
   * @method
   * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
   * @param {Pool} pool Connection pool for this topology
   * @param {string} db Name of the database
   * @param {string} username Username
   * @param {string} password Password
   * @param {authResultCallback} callback The callback to return the result from the authentication
   * @return {object}
   */
  GSSAPI.prototype.auth = function(server, pool, db, username, password, options, callback) {
    var self = this;
    // We don't have the Kerberos library
    if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));  
    var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
    // Get all the connections
    var connections = pool.getAll();
    // Total connections
    var count = connections.length;
    if(count == 0) return callback(null, null);
  
    // Valid connections
    var numberOfValidConnections = 0;
    var credentialsValid = false;
    var errorObject = null;
  
    // For each connection we need to authenticate
    while(connections.length > 0) {    
      // Execute MongoCR
      var execute = function(connection) {
        // Start Auth process for a connection
        GSSAPIInitialize(db, username, password, db, gssapiServiceName, server, connection, function(err, r) {
          // Adjust count
          count = count - 1;
  
          // If we have an error
          if(err) {
            errorObject = err;
          } else if(r.result['$err']) {
            errorObject = r.result;
          } else if(r.result['errmsg']) {
            errorObject = r.result;
          } else {
            credentialsValid = true;
            numberOfValidConnections = numberOfValidConnections + 1;
          }
  
          // We have authenticated all connections
          if(count == 0 && numberOfValidConnections > 0) {
            // Store the auth details
            addAuthSession(self.authStore, new AuthSession(db, username, password, options));
            // Return correct authentication
            callback(null, true);
          } else if(count == 0) {
            if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
            callback(errorObject, false);
          }
        });
      }
  
      // Get the connection
      execute(connections.shift());
    }
  }
  
  //
  // Initialize step
  var GSSAPIInitialize = function(db, username, password, authdb, gssapiServiceName, server, connection, callback) {
    // Create authenticator
    var mongo_auth_process = new MongoAuthProcess(connection.host, connection.port, gssapiServiceName);
  
    // Perform initialization
    mongo_auth_process.init(username, password, function(err, context) {
      if(err) return callback(err, false);
  
      // Perform the first step
      mongo_auth_process.transition('', function(err, payload) {
        if(err) return callback(err, false);
  
        // Call the next db step
        MongoDBGSSAPIFirstStep(mongo_auth_process, payload, db, username, password, authdb, server, connection, callback);
      });
    });
  }
  
  //
  // Perform first step against mongodb
  var MongoDBGSSAPIFirstStep = function(mongo_auth_process, payload, db, username, password, authdb, server, connection, callback) {
    // Build the sasl start command
    var command = {
        saslStart: 1
      , mechanism: 'GSSAPI'
      , payload: payload
      , autoAuthorize: 1
    };
  
    // Execute first sasl step
    server.command("$external.$cmd"
      , command
      , { connection: connection }, function(err, r) {
      if(err) return callback(err, false);    
      var doc = r.result;
      // Execute mongodb transition
      mongo_auth_process.transition(r.result.payload, function(err, payload) {
        if(err) return callback(err, false);
  
        // MongoDB API Second Step
        MongoDBGSSAPISecondStep(mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback);
      });
    });
  }
  
  //
  // Perform first step against mongodb
  var MongoDBGSSAPISecondStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback) {
    // Build Authentication command to send to MongoDB
    var command = {
        saslContinue: 1
      , conversationId: doc.conversationId
      , payload: payload
    };
  
    // Execute the command
    server.command("$external.$cmd"
      , command
      , { connection: connection }, function(err, r) {
      if(err) return callback(err, false);
      var doc = r.result;
      // Call next transition for kerberos
      mongo_auth_process.transition(doc.payload, function(err, payload) {
        if(err) return callback(err, false);
  
        // Call the last and third step
        MongoDBGSSAPIThirdStep(mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback);
      });    
    });
  }
  
  var MongoDBGSSAPIThirdStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback) {
    // Build final command
    var command = {
        saslContinue: 1
      , conversationId: doc.conversationId
      , payload: payload
    };
  
    // Execute the command
    server.command("$external.$cmd"
      , command
      , { connection: connection }, function(err, r) {
      if(err) return callback(err, false);
      mongo_auth_process.transition(null, function(err, payload) {
        if(err) return callback(err, null);
        callback(null, r);
      });
    });
  }
  
  // Add to store only if it does not exist
  var addAuthSession = function(authStore, session) {
    var found = false;
  
    for(var i = 0; i < authStore.length; i++) {
      if(authStore[i].equal(session)) {
        found = true;
        break;
      }
    }
  
    if(!found) authStore.push(session);
  }
  
  /**
   * Re authenticate pool
   * @method
   * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
   * @param {Pool} pool Connection pool for this topology
   * @param {authResultCallback} callback The callback to return the result from the authentication
   * @return {object}
   */
  GSSAPI.prototype.reauthenticate = function(server, pool, callback) {
    var count = this.authStore.length;
    if(count == 0) return callback(null, null);
    // Iterate over all the auth details stored
    for(var i = 0; i < this.authStore.length; i++) {
      this.auth(server, pool, this.authStore[i].db, this.authStore[i].username, this.authStore[i].password, this.authStore[i].options, function(err, r) {
        count = count - 1;
        // Done re-authenticating
        if(count == 0) {
          callback(null, null);
        }
      });
    }
  }
  
  /**
   * This is a result from a authentication strategy
   *
   * @callback authResultCallback
   * @param {error} error An error object. Set to null if no error present
   * @param {boolean} result The result of the authentication process
   */
  
  module.exports = GSSAPI;