text: /*global process */
  /**
   * @file Command line interface for JSforce
   * @author Shinichi Tomita <shinichi.tomita@gmail.com>
   */
  
  'use strict';
  
  var http = require('http'),
      url = require('url'),
      crypto = require('crypto'),
      openUrl = require('opn'),
      commander = require('commander'),
      coprompt = require('co-prompt'),
      request = require('request'),
      base64url = require('base64-url'),
      Repl = require('./repl'),
      jsforce = require('../jsforce'),
      registry = jsforce.registry,
      Promise = require('../promise'),
      pkg = require('../../package.json');
  
  var repl;
  var conn = null;
  var connName = null;
  var outputEnabled = true;
  var defaultLoginUrl = null;
  
  /**
   * @private
   */
  function start() {
    var program = new commander.Command();
    program.option('-u, --username [username]', 'Salesforce username')
           .option('-p, --password [password]', 'Salesforce password (and security token, if available)')
           .option('-c, --connection [connection]', 'Connection name stored in connection registry')
           .option('-e, --evalScript [evalScript]', 'Script to evaluate')
           .option('-l, --loginUrl [loginUrl]', 'Salesforce login url')
           .option('--sandbox', 'Login to Salesforce sandbox')
           .option('--coffee', 'Using CoffeeScript')
           .version(pkg.version)
           .parse(process.argv);
    var replModule = program.coffee ? require('coffee-script/lib/coffee-script/repl') : require('repl');
    repl = new Repl(cli, replModule);
    outputEnabled = !program.evalScript;
    var options = { username: program.username, password: program.password };
    var loginUrl = program.loginUrl ? program.loginUrl :
                   program.sandbox ? 'sandbox' :
                   null;
    setLoginServer(loginUrl);
    connect(program.connection, options, function(err, res) {
      if (err) {
        console.error(err.message);
        process.exit();
      } else {
        if (program.evalScript) {
          repl.start({
            interactive: false,
            evalScript: program.evalScript
          });
        } else {
          repl.start();
        }
      }
    });
  }
  
  /**
   * @private
   */
  function getCurrentConnection() {
    return conn;
  }
  
  function print(message) {
    if (outputEnabled) { console.log(message); }
  }
  
  /**
   * @private
   */
  function saveCurrentConnection() {
    if (conn && connName) {
      var connConfig = {
        oauth2: conn.oauth2 && {
          clientId: conn.oauth2.clientId,
          clientSecret: conn.oauth2.clientSecret,
          redirectUri: conn.oauth2.redirectUri,
          loginUrl: conn.oauth2.loginUrl
        },
        accessToken: conn.accessToken,
        instanceUrl: conn.instanceUrl,
        refreshToken: conn.refreshToken
      };
      registry.saveConnectionConfig(connName, connConfig);
    }
  }
  
  /**
   * @private
   */
  function setLoginServer(loginServer) {
    if (!loginServer) { return; }
    if (loginServer === 'production') {
      defaultLoginUrl = 'https://login.salesforce.com';
    } else if (loginServer === 'sandbox') {
      defaultLoginUrl = 'https://test.salesforce.com';
    } else if (loginServer.indexOf('https://') !== 0) {
      defaultLoginUrl = 'https://' + loginServer;
    } else {
      defaultLoginUrl = loginServer;
    }
    print('Using "' + defaultLoginUrl + '" as default login URL.');
  }
  
  /**
   * @private
   */
  function connect(name, options, callback) {
    connName = name;
    options = options || {};
    var connConfig = registry.getConnectionConfig(name);
    var username, password;
    if (!connConfig) {
      connConfig = {};
      if (defaultLoginUrl) {
        connConfig.loginUrl = defaultLoginUrl;
      }
      username = name;
    }
    conn = new jsforce.Connection(connConfig);
    username = username || options.username;
    password = options.password;
    var handleLogin = function(err) {
      if (err) { return callback(err); }
      saveCurrentConnection();
      callback();
    };
    if (username) {
      loginByPassword(username, password, 2, handleLogin);
    } else {
      if (connName && conn.accessToken) {
        conn.on('refresh', function(accessToken) {
          print('Refreshing access token ... ');
          saveCurrentConnection();
        });
        conn.identity(function(err, identity) {
          if (err) {
            print(err.message);
            if (conn.oauth2) {
              callback(new Error('Please re-authorize connection.'));
            } else {
              loginByPassword(connName, null, 2, handleLogin);
            }
          } else {
            print('Logged in as : ' + identity.username);
            callback();
          }
        });
      } else {
        callback();
      }
    }
  }
  
  /**
   * @private
   */
  function loginByPassword(username, password, retry, callback) {
    if (!password) {
      promptPassword('Password: ', function(err, pass) {
        if (err) { return callback(err); }
        loginByPassword(username, pass, retry, callback);
      });
      return;
    }
    conn.login(username, password, function(err, result) {
      if (err) {
        console.error(err.message);
        if (retry > 0) {
          loginByPassword(username, null, --retry, callback);
        } else {
          callback(new Error());
        }
      } else {
        print("Logged in as : " + username);
        callback(null, result);
      }
    });
  }
  
  /**
   * @private
   */
  function disconnect(name) {
    name = name || connName;
    if (registry.getConnectionConfig(name)) {
      registry.removeConnectionConfig(name);
      print("Disconnect connection '" + name + "'");
    }
    connName = null;
    conn = new jsforce.Connection();
  }
  
  /**
   * @private
   */
  function authorize(clientName, callback) {
    clientName = clientName || 'default';
    var oauth2Config = registry.getClient(clientName);
    if (!oauth2Config || !oauth2Config.clientId) {
      if (clientName === 'default' || clientName === 'sandbox') {
        print('No client information registered. Downloading JSforce default client information...');
        return downloadDefaultClientInfo(clientName, callback);
      }
      return callback(new Error("No OAuth2 client information registered : '"+clientName+"'. Please register client info first."));
    }
    var oauth2 = new jsforce.OAuth2(oauth2Config);
    var verifier = base64url.encode(crypto.randomBytes(32));
    var challenge = base64url.encode(crypto.createHash('sha256').update(verifier).digest());
    var state = base64url.encode(crypto.randomBytes(32));
    var authzUrl = oauth2.getAuthorizationUrl({ code_challenge: challenge, state: state });
    print('Opening authorization page in browser...');
    print('URL: ' + authzUrl);
    openUrl(authzUrl);
    waitCallback(oauth2Config.redirectUri, state, function(err, params) {
      if (err) { return callback(err); }
      conn = new jsforce.Connection({ oauth2: oauth2 });
      if (!params.code) {
        return callback(new Error('No authorization code returned.'));
      }
      if (params.state !== state) {
        return callback(new Error('Invalid state parameter returned.'));
      }
      print('Received authorization code. Please close the opened browser window.');
      conn.authorize(params.code, { code_verifier: verifier }).then(function(res) {
        print('Authorized. Fetching user info...');
        return conn.identity();
      }).then(function(identity) {
        print('Logged in as : ' + identity.username);
        connName = identity.username;
        saveCurrentConnection();
      }).thenCall(callback);
    });
  }
  
  /**
   * @private
   */
  function downloadDefaultClientInfo(clientName, callback) {
    var configUrl = 'https://jsforce.github.io/client-config/default.json';
    request(configUrl, function(err, res) {
      if (err) { return callback(err); }
      var clientConfig = JSON.parse(res.body);
      if (clientName === 'sandbox') {
        clientConfig.loginUrl = 'https://test.salesforce.com';
      }
      registry.registerClient(clientName, clientConfig);
      print("Client information downloaded successfully.");
      authorize(clientName, callback);
    });
  }
  
  
  /**
   * @private
   */
  function waitCallback(serverUrl, state, callback) {
    if (serverUrl.indexOf('http://localhost:') === 0) {
      var server = http.createServer(function(req, res) {
        var qparams = url.parse(req.url, true).query;
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write('<html><script>location.href="about:blank";</script></html>');
        res.end();
        callback(null, qparams);
        server.close();
        req.connection.end();
        req.connection.destroy();
      });
      var port = url.parse(serverUrl).port;
      server.listen(port, "localhost");
    } else {
      var msg = 'Copy & paste authz code passed in redirected URL: ';
      promptMessage(msg, function(err, code) {
        if (err) {
          callback(err);
        } else {
          callback(null, { code: decodeURIComponent(code), state: state });
        }
      });
    }
  }
  
  /**
   * @private
   */
  function register(clientName, clientConfig, callback) {
    if (!clientName) {
      clientName = "default";
    }
  
    var prompts = {
      "clientId": "Input client ID : ",
      "clientSecret": "Input client secret (optional) : ",
      "redirectUri": "Input redirect URI : ",
      "loginUrl": "Input login URL (default is https://login.salesforce.com) : "
    };
    new Promise(function(resolve, reject) {
      if (registry.getClient(clientName)) {
        var msg = "Client '"+clientName+"' is already registered. Are you sure you want to override ? [yN] : ";
        promptConfirm(msg, function(err, ok) {
          if (ok) {
            resolve();
          } else {
            reject(new Error('Registration canceled.'));
          }
        });
      } else {
        resolve();
      }
    })
    .then(function() {
      return Object.keys(prompts).reduce(function(promise, name) {
        return promise.then(function() {
          var message = prompts[name];
          return new Promise(function(resolve, reject) {
            if (!clientConfig[name]) {
              promptMessage(message, function(err, value) {
                if (err) { return reject(err); }
                if (value) { clientConfig[name] = value; }
                resolve();
              });
            } else {
              resolve();
            }
          });
        });
      }, Promise.resolve());
    }).then(function() {
      registry.registerClient(clientName, clientConfig);
      print("Client registered successfully.");
    }).thenCall(callback);
  }
  
  /**
   * @private
   */
  function listConnections() {
    var names = registry.getConnectionNames();
    for (var i=0; i<names.length; i++) {
      var name = names[i];
      print((name === connName ? '* ' : '  ') + name);
    }
  }
  
  /**
   * @private
   */
  function getConnectionNames() {
    return registry.getConnectionNames();
  }
  
  /**
   * @private
   */
  function getClientNames() {
    return registry.getClientNames();
  }
  
  /**
   * @private
   */
  function promptMessage(message, callback) {
    repl.pause();
    coprompt(message)(function(err, res) {
      repl.resume();
      callback(err, res);
    });
  }
  
  /**
   * @private
   */
  function promptPassword(message, callback) {
    repl.pause();
    coprompt.password(message)(function(err, res) {
      repl.resume();
      callback(err, res);
    });
  }
  
  /**
   * @private
   */
  function promptConfirm(message, callback) {
    repl.pause();
    coprompt.confirm(message)(function(err, res) {
      repl.resume();
      callback(err, res);
    });
  }
  
  /**
   * @private
   */
  function openUrlUsingSession(url) {
    var frontdoorUrl = conn.instanceUrl + '/secur/frontdoor.jsp?sid=' + conn.accessToken;
    if (url) {
      frontdoorUrl += "&retURL=" + encodeURIComponent(url);
    }
    openUrl(frontdoorUrl);
  }
  
  /* ------------------------------------------------------------------------- */
  
  var cli = {
    start: start,
    getCurrentConnection: getCurrentConnection,
    saveCurrentConnection: saveCurrentConnection,
    listConnections: listConnections,
    setLoginServer: setLoginServer,
    getConnectionNames: getConnectionNames,
    getClientNames: getClientNames,
    connect: connect,
    disconnect: disconnect,
    authorize: authorize,
    register: register,
    openUrl: openUrlUsingSession
  };
  
  module.exports = cli;
  