text: // Copyright 2012 The Obvious Corporation.
  
  /*
   * This simply fetches the right version of phantom for the current platform.
   */
  
  'use strict'
  
  var requestProgress = require('request-progress')
  var progress = require('progress')
  var extractZip = require('extract-zip')
  var cp = require('child_process')
  var fs = require('fs-extra')
  var helper = require('./lib/phantomjs')
  var kew = require('kew')
  var path = require('path')
  var request = require('request')
  var url = require('url')
  var util = require('./lib/util')
  var which = require('which')
  var os = require('os')
  
  var originalPath = process.env.PATH
  
  var checkPhantomjsVersion = util.checkPhantomjsVersion
  var getTargetPlatform = util.getTargetPlatform
  var getTargetArch = util.getTargetArch
  var getDownloadSpec = util.getDownloadSpec
  var findValidPhantomJsBinary = util.findValidPhantomJsBinary
  var verifyChecksum = util.verifyChecksum
  var writeLocationFile = util.writeLocationFile
  
  // If the process exits without going through exit(), then we did not complete.
  var validExit = false
  
  process.on('exit', function () {
    if (!validExit) {
      console.log('Install exited unexpectedly')
      exit(1)
    }
  })
  
  // NPM adds bin directories to the path, which will cause `which` to find the
  // bin for this package not the actual phantomjs bin.  Also help out people who
  // put ./bin on their path
  process.env.PATH = helper.cleanPath(originalPath)
  
  var libPath = path.join(__dirname, 'lib')
  var pkgPath = path.join(libPath, 'phantom')
  var phantomPath = null
  
  // If the user manually installed PhantomJS, we want
  // to use the existing version.
  //
  // Do not re-use a manually-installed PhantomJS with
  // a different version.
  //
  // Do not re-use an npm-installed PhantomJS, because
  // that can lead to weird circular dependencies between
  // local versions and global versions.
  // https://github.com/Obvious/phantomjs/issues/85
  // https://github.com/Medium/phantomjs/pull/184
  kew.resolve(true)
    .then(tryPhantomjsInLib)
    .then(tryPhantomjsOnPath)
    .then(downloadPhantomjs)
    .then(extractDownload)
    .then(function (extractedPath) {
      return copyIntoPlace(extractedPath, pkgPath)
    })
    .then(function () {
      var location = getTargetPlatform() === 'win32' ?
          path.join(pkgPath, 'bin', 'phantomjs.exe') :
          path.join(pkgPath, 'bin' ,'phantomjs')
  
      try {
        // Ensure executable is executable by all users
        fs.chmodSync(location, '755')
      } catch (err) {
        if (err.code == 'ENOENT') {
          console.error('chmod failed: phantomjs was not successfully copied to', location)
          exit(1)
        }
        throw err
      }
  
      var relativeLocation = path.relative(libPath, location)
      writeLocationFile(relativeLocation)
  
      console.log('Done. Phantomjs binary available at', location)
      exit(0)
    })
    .fail(function (err) {
      console.error('Phantom installation failed', err, err.stack)
      exit(1)
    })
  
  function exit(code) {
    validExit = true
    process.env.PATH = originalPath
    process.exit(code || 0)
  }
  
  
  function findSuitableTempDirectory() {
    var now = Date.now()
    var candidateTmpDirs = [
      process.env.npm_config_tmp,
      os.tmpdir(),
      path.join(process.cwd(), 'tmp')
    ]
  
    for (var i = 0; i < candidateTmpDirs.length; i++) {
      var candidatePath = candidateTmpDirs[i]
      if (!candidatePath) continue
  
      try {
        candidatePath = path.join(path.resolve(candidatePath), 'phantomjs')
        fs.mkdirsSync(candidatePath, '0777')
        // Make double sure we have 0777 permissions; some operating systems
        // default umask does not allow write by default.
        fs.chmodSync(candidatePath, '0777')
        var testFile = path.join(candidatePath, now + '.tmp')
        fs.writeFileSync(testFile, 'test')
        fs.unlinkSync(testFile)
        return candidatePath
      } catch (e) {
        console.log(candidatePath, 'is not writable:', e.message)
      }
    }
  
    console.error('Can not find a writable tmp directory, please report issue ' +
        'on https://github.com/Medium/phantomjs/issues with as much ' +
        'information as possible.')
    exit(1)
  }
  
  
  function getRequestOptions() {
    var strictSSL = !!process.env.npm_config_strict_ssl
    if (process.version == 'v0.10.34') {
      console.log('Node v0.10.34 detected, turning off strict ssl due to https://github.com/joyent/node/issues/8894')
      strictSSL = false
    }
  
    var options = {
      uri: getDownloadUrl(),
      encoding: null, // Get response as a buffer
      followRedirect: true, // The default download path redirects to a CDN URL.
      headers: {},
      strictSSL: strictSSL
    }
  
    var proxyUrl = process.env.npm_config_https_proxy ||
        process.env.npm_config_http_proxy ||
        process.env.npm_config_proxy
    if (proxyUrl) {
  
      // Print using proxy
      var proxy = url.parse(proxyUrl)
      if (proxy.auth) {
        // Mask password
        proxy.auth = proxy.auth.replace(/:.*$/, ':******')
      }
      console.log('Using proxy ' + url.format(proxy))
  
      // Enable proxy
      options.proxy = proxyUrl
    }
  
    // Use the user-agent string from the npm config
    options.headers['User-Agent'] = process.env.npm_config_user_agent
  
    // Use certificate authority settings from npm
    var ca = process.env.npm_config_ca
    if (!ca && process.env.npm_config_cafile) {
      try {
        ca = fs.readFileSync(process.env.npm_config_cafile, {encoding: 'utf8'})
          .split(/\n(?=-----BEGIN CERTIFICATE-----)/g)
  
        // Comments at the beginning of the file result in the first
        // item not containing a certificate - in this case the
        // download will fail
        if (ca.length > 0 && !/-----BEGIN CERTIFICATE-----/.test(ca[0])) {
          ca.shift()
        }
  
      } catch (e) {
        console.error('Could not read cafile', process.env.npm_config_cafile, e)
      }
    }
  
    if (ca) {
      console.log('Using npmconf ca')
      options.agentOptions = {
        ca: ca
      }
      options.ca = ca
    }
  
    return options
  }
  
  function handleRequestError(error) {
    if (error && error.stack && error.stack.indexOf('SELF_SIGNED_CERT_IN_CHAIN') != -1) {
        console.error('Error making request, SELF_SIGNED_CERT_IN_CHAIN. ' +
            'Please read https://github.com/Medium/phantomjs#i-am-behind-a-corporate-proxy-that-uses-self-signed-ssl-certificates-to-intercept-encrypted-traffic')
        exit(1)
    } else if (error) {
      console.error('Error making request.\n' + error.stack + '\n\n' +
          'Please report this full log at https://github.com/Medium/phantomjs')
      exit(1)
    } else {
      console.error('Something unexpected happened, please report this full ' +
          'log at https://github.com/Medium/phantomjs')
      exit(1)
    }
  }
  
  function requestBinary(requestOptions, filePath) {
    var deferred = kew.defer()
  
    var writePath = filePath + '-download-' + Date.now()
  
    console.log('Receiving...')
    var bar = null
    requestProgress(request(requestOptions, function (error, response, body) {
      console.log('')
      if (!error && response.statusCode === 200) {
        fs.writeFileSync(writePath, body)
        console.log('Received ' + Math.floor(body.length / 1024) + 'K total.')
        fs.renameSync(writePath, filePath)
        deferred.resolve(filePath)
  
      } else if (response) {
        console.error('Error requesting archive.\n' +
            'Status: ' + response.statusCode + '\n' +
            'Request options: ' + JSON.stringify(requestOptions, null, 2) + '\n' +
            'Response headers: ' + JSON.stringify(response.headers, null, 2) + '\n' +
            'Make sure your network and proxy settings are correct.\n\n' +
            'If you continue to have issues, please report this full log at ' +
            'https://github.com/Medium/phantomjs')
        exit(1)
      } else {
        handleRequestError(error)
      }
    })).on('progress', function (state) {
      try {
        if (!bar) {
          bar = new progress('  [:bar] :percent', {total: state.size.total, width: 40})
        }
        bar.curr = state.size.transferred
        bar.tick()
      } catch (e) {
        // It doesn't really matter if the progress bar doesn't update.
      }
    })
    .on('error', handleRequestError)
  
    return deferred.promise
  }
  
  
  function extractDownload(filePath) {
    var deferred = kew.defer()
    // extract to a unique directory in case multiple processes are
    // installing and extracting at once
    var extractedPath = filePath + '-extract-' + Date.now()
    var options = {cwd: extractedPath}
  
    fs.mkdirsSync(extractedPath, '0777')
    // Make double sure we have 0777 permissions; some operating systems
    // default umask does not allow write by default.
    fs.chmodSync(extractedPath, '0777')
  
    if (filePath.substr(-4) === '.zip') {
      console.log('Extracting zip contents')
      extractZip(path.resolve(filePath), {dir: extractedPath}, function(err) {
        if (err) {
          console.error('Error extracting zip')
          deferred.reject(err)
        } else {
          deferred.resolve(extractedPath)
        }
      })
  
    } else {
      console.log('Extracting tar contents (via spawned process)')
      cp.execFile('tar', ['jxf', path.resolve(filePath)], options, function (err) {
        if (err) {
          console.error('Error extracting archive')
          deferred.reject(err)
        } else {
          deferred.resolve(extractedPath)
        }
      })
    }
    return deferred.promise
  }
  
  
  function copyIntoPlace(extractedPath, targetPath) {
    console.log('Removing', targetPath)
    return kew.nfcall(fs.remove, targetPath).then(function () {
      // Look for the extracted directory, so we can rename it.
      var files = fs.readdirSync(extractedPath)
      for (var i = 0; i < files.length; i++) {
        var file = path.join(extractedPath, files[i])
        if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) {
          console.log('Copying extracted folder', file, '->', targetPath)
          return kew.nfcall(fs.move, file, targetPath)
        }
      }
  
      console.log('Could not find extracted file', files)
      throw new Error('Could not find extracted file')
    })
  }
  
  /**
   * Check to see if the binary in lib is OK to use. If successful, exit the process.
   */
  function tryPhantomjsInLib() {
    return kew.fcall(function () {
      return findValidPhantomJsBinary(path.resolve(__dirname, './lib/location.js'))
    }).then(function (binaryLocation) {
      if (binaryLocation) {
        console.log('PhantomJS is previously installed at', binaryLocation)
        exit(0)
      }
    }).fail(function () {
      // silently swallow any errors
    })
  }
  
  /**
   * Check to see if the binary on PATH is OK to use. If successful, exit the process.
   */
  function tryPhantomjsOnPath() {
    if (getTargetPlatform() != process.platform || getTargetArch() != process.arch) {
      console.log('Building for target platform ' + getTargetPlatform() + '/' + getTargetArch() +
                  '. Skipping PATH search')
      return kew.resolve(false)
    }
  
    return kew.nfcall(which, 'phantomjs')
    .then(function (result) {
      phantomPath = result
      console.log('Considering PhantomJS found at', phantomPath)
  
      // Horrible hack to avoid problems during global install. We check to see if
      // the file `which` found is our own bin script.
      if (phantomPath.indexOf(path.join('npm', 'phantomjs')) !== -1) {
        console.log('Looks like an `npm install -g` on windows; skipping installed version.')
        return
      }
  
      var contents = fs.readFileSync(phantomPath, 'utf8')
      if (/NPM_INSTALL_MARKER/.test(contents)) {
        console.log('Looks like an `npm install -g`')
  
        var phantomLibPath = path.resolve(fs.realpathSync(phantomPath), '../../lib/location')
        return findValidPhantomJsBinary(phantomLibPath)
        .then(function (binaryLocation) {
          if (binaryLocation) {
            writeLocationFile(binaryLocation)
            console.log('PhantomJS linked at', phantomLibPath)
            exit(0)
          }
          console.log('Could not link global install, skipping...')
        })
      } else {
        return checkPhantomjsVersion(phantomPath).then(function (matches) {
          if (matches) {
            writeLocationFile(phantomPath)
            console.log('PhantomJS is already installed on PATH at', phantomPath)
            exit(0)
          }
        })
      }
    }, function () {
      console.log('PhantomJS not found on PATH')
    })
    .fail(function (err) {
      console.error('Error checking path, continuing', err)
      return false
    })
  }
  
  /**
   * @return {?string} Get the download URL for phantomjs.
   *     May return null if no download url exists.
   */
  function getDownloadUrl() {
    var spec = getDownloadSpec()
    return spec && spec.url
  }
  
  /**
   * Download phantomjs, reusing the existing copy on disk if available.
   * Exits immediately if there is no binary to download.
   * @return {Promise.<string>} The path to the downloaded file.
   */
  function downloadPhantomjs() {
    var downloadSpec = getDownloadSpec()
    if (!downloadSpec) {
      console.error(
          'Unexpected platform or architecture: ' + getTargetPlatform() + '/' + getTargetArch() + '\n' +
          'It seems there is no binary available for your platform/architecture\n' +
          'Try to install PhantomJS globally')
      exit(1)
    }
  
    var downloadUrl = downloadSpec.url
    var downloadedFile
  
    return kew.fcall(function () {
      // Can't use a global version so start a download.
      var tmpPath = findSuitableTempDirectory()
      var fileName = downloadUrl.split('/').pop()
      downloadedFile = path.join(tmpPath, fileName)
  
      if (fs.existsSync(downloadedFile)) {
        console.log('Download already available at', downloadedFile)
        return verifyChecksum(downloadedFile, downloadSpec.checksum)
      }
      return false
    }).then(function (verified) {
      if (verified) {
        return downloadedFile
      }
  
      // Start the install.
      console.log('Downloading', downloadUrl)
      console.log('Saving to', downloadedFile)
      return requestBinary(getRequestOptions(), downloadedFile)
    })
  }
  