(function() {
  var Ci, Command, _, async, config, fs, path, yargs,
    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty,
    slice = [].slice;

  path = require('path');

  fs = require('./fs');

  yargs = require('yargs');

  async = require('async');

  _ = require('underscore-plus');

  config = require('./apm');

  Command = require('./command');

  module.exports = Ci = (function(superClass) {
    extend(Ci, superClass);

    Ci.commandNames = ['ci'];

    function Ci() {
      this.installNode = bind(this.installNode, this);
      this.installDependencies = bind(this.installDependencies, this);
      Ci.__super__.constructor.call(this);
      this.atomDirectory = config.getAtomDirectory();
      this.atomNodeDirectory = path.join(this.atomDirectory, '.node-gyp');
      this.atomNpmPath = require.resolve('npm/bin/npm-cli');
      this.atomNodeGypPath = process.env.ATOM_NODE_GYP_PATH || require.resolve('node-gyp/bin/node-gyp');
    }

    Ci.prototype.parseOptions = function(argv) {
      var options;
      options = yargs(argv).wrap(100);
      options.usage("Usage: apm ci\n\nInstall a package with a clean slate.\n\nIf you have an up-to-date package-lock.json file created by apm install,\napm ci will install its locked contents exactly. It is substantially\nfaster than apm install and produces consistently reproduceable builds,\nbut cannot be used to install new packages or dependencies.");
      options.alias('h', 'help').describe('help', 'Print this usage message');
      return options.boolean('verbose')["default"]('verbose', false).describe('verbose', 'Show verbose debug information');
    };

    Ci.prototype.installDependencies = function(options, callback) {
      return async.waterfall([
        (function(_this) {
          return function(cb) {
            return _this.installNode(options, cb);
          };
        })(this), (function(_this) {
          return function(cb) {
            return _this.installModules(options, cb);
          };
        })(this)
      ], callback);
    };

    Ci.prototype.installNode = function(options, callback) {
      var env, installNodeArgs, opts, proxy, ref, useStrictSsl;
      installNodeArgs = ['install'];
      installNodeArgs.push.apply(installNodeArgs, this.getNpmBuildFlags());
      installNodeArgs.push("--ensure");
      env = _.extend({}, process.env, {
        HOME: this.atomNodeDirectory,
        RUSTUP_HOME: config.getRustupHomeDirPath()
      });
      if (config.isWin32()) {
        env.USERPROFILE = env.HOME;
      }
      fs.makeTreeSync(this.atomDirectory);
      useStrictSsl = (ref = this.npm.config.get('strict-ssl')) != null ? ref : true;
      if (!useStrictSsl) {
        env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
      }
      proxy = this.npm.config.get('https-proxy') || this.npm.config.get('proxy') || env.HTTPS_PROXY || env.HTTP_PROXY;
      if (proxy) {
        installNodeArgs.push("--proxy=" + proxy);
      }
      opts = {
        env: env,
        cwd: this.atomDirectory,
        streaming: options.argv.verbose
      };
      return this.fork(this.atomNodeGypPath, installNodeArgs, opts, function(code, stderr, stdout) {
        if (stderr == null) {
          stderr = '';
        }
        if (stdout == null) {
          stdout = '';
        }
        if (code === 0) {
          return callback();
        } else {
          return callback(stdout + "\n" + stderr);
        }
      });
    };

    Ci.prototype.installModules = function(options, callback) {
      var env, installArgs, installOptions, vsArgs;
      process.stdout.write('Installing locked modules');
      if (options.argv.verbose) {
        process.stdout.write('\n');
      } else {
        process.stdout.write(' ');
      }
      installArgs = ['ci', '--globalconfig', config.getGlobalConfigPath(), '--userconfig', config.getUserConfigPath()].concat(slice.call(this.getNpmBuildFlags()));
      if (options.argv.verbose) {
        installArgs.push('--verbose');
      }
      if (vsArgs = this.getVisualStudioFlags()) {
        installArgs.push(vsArgs);
      }
      env = _.extend({}, process.env, {
        HOME: this.atomNodeDirectory,
        RUSTUP_HOME: config.getRustupHomeDirPath()
      });
      if (config.isWin32()) {
        this.updateWindowsEnv(env);
      }
      this.addNodeBinToEnv(env);
      this.addProxyToEnv(env);
      installOptions = {
        env: env,
        streaming: options.argv.verbose
      };
      return this.fork(this.atomNpmPath, installArgs, installOptions, (function(_this) {
        return function() {
          var args;
          args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
          return _this.logCommandResults.apply(_this, [callback].concat(slice.call(args)));
        };
      })(this));
    };

    Ci.prototype.run = function(options) {
      var callback, commands, iteratee, opts;
      callback = options.callback;
      opts = this.parseOptions(options.commandArgs);
      commands = [];
      commands.push((function(_this) {
        return function(callback) {
          return config.loadNpm(function(error, npm) {
            _this.npm = npm;
            return callback(error);
          });
        };
      })(this));
      commands.push((function(_this) {
        return function(cb) {
          return _this.loadInstalledAtomMetadata(cb);
        };
      })(this));
      commands.push((function(_this) {
        return function(cb) {
          return _this.installDependencies(opts, cb);
        };
      })(this));
      iteratee = function(item, next) {
        return item(next);
      };
      return async.mapSeries(commands, iteratee, function(err) {
        if (err) {
          return callback(err);
        }
        return callback(null);
      });
    };

    return Ci;

  })(Command);

}).call(this);
