text: 'use strict';
  
  /**
   * The `kss/lib/cli` module is a wrapper around the code used by the
   * `bin/kss` command line utility.
   *
   * ```
   * const cli = require('kss/lib/cli');
   * ```
   *
   * @module kss/lib/cli
   */
  
  const Promise = require('bluebird'),
    kss = require('./kss'),
    KssBuilderBase = require('../builder'),
    path = require('path'),
    version = require('../package.json').version,
    yargs = require('yargs');
  
  const fs = Promise.promisifyAll(require('fs-extra'));
  
  /**
   * Parses command line arguments in `opts.argv` and outputs messages and errors
   * on `opts.stdout` and `opts.stderr`, respectively.
   *
   * @param {Object} opts The `stdout`, `stderr` and `argv` options to use.
   * @returns {Promise.<KssStyleGuide|null>} A `Promise` object resolving to a
   *   `KssStyleGuide` object, or to `null` if the clone option is used.
   */
  const cli = function(opts) {
    // First 2 args are "node" and path to kss script; we don't need them.
    const args = opts.argv.slice(2) || /* istanbul ignore next */ [];
  
    // Set up a logging function for any messages we need to send to stdout.
    // We will set up the error function after we know what options to use.
    const reportMessage = function() {
      let message = '';
      for (let i = 0; i < arguments.length; i++) {
        message += arguments[i];
      }
      opts.stdout.write(message + '\n');
    };
  
    // If the demo is requested, load the settings from its config file.
    if (args.indexOf('--demo') !== -1) {
      // Add the configuration file to the raw arguments list.
      args.push('--config', path.join(__dirname, '../demo/kss-config.json'));
      reportMessage('WELCOME to the kss demo! We\'ve turned on the --verbose flag so you can see what kss is doing.');
    }
  
    const cliOptionDefinitions = {
      'config': {
        group: 'File locations:',
        alias: 'c',
        config: true,
        multiple: false,
        describe: 'Load the kss options from a json file'
      },
      'demo': {
        multiple: false,
        boolean: true,
        describe: 'Builds a KSS demo.',
        default: false
      },
      // Prevent yargs from complaining about JSON comments in the config file.
      '//': {
        describe: 'Comments in JSON files will be ignored'
      }
    };
  
    // We need to know which builder to use, so we do a quick first parse of the
    // arguments using yargs.
    let options = yargs(args).options(
      // We merge the CLI option definitions with the default KssBuilderBase
      // option definitions.
      (new KssBuilderBase()).addOptionDefinitions(cliOptionDefinitions).getOptionDefinitions()
    ).argv;
  
    // Check if there are settings coming from a JSON config file. We need to note
    // the config file's values and the directory where it is located.
    let configFileOptions = {},
      configFileDirectory = '',
      checkBuilderPath = Promise.resolve();
    if (options.config) {
      let configFilePath = path.resolve(options.config);
      configFileOptions = require(configFilePath);
      configFileDirectory = path.dirname(configFilePath);
      // First, ensure the path to the builder is relative to the config file's
      // location. Later, we will do the same for other paths in the config file.
      if (configFileOptions.builder) {
        options.builder = path.resolve(configFileDirectory, configFileOptions.builder);
        checkBuilderPath = fs.statAsync(options.builder).then(stats => {
          if (!stats.isDirectory()) {
            throw new Error();
          }
          return Promise.resolve();
        }).catch(() => {
          // If the resolved builder path does not work, check if the Twig builder
          // was desired.
          if (configFileOptions.builder === 'builder/twig') {
            options.builder = path.resolve(__dirname, '..', 'builder', 'twig');
            return Promise.resolve();
          } else {
            throw new Error('The builder path, "' + options.builder + '", is not a directory.');
          }
        });
      }
    }
  
    // Set up an error handler for Promised tasks in this module; kss() will
    // handle its own errors, so we don't want to double-catch/report its errors.
    const reportError = function(error) {
      // Make sure the standard error handler is writable.
      if (opts && opts.stderr && opts.stderr.write) {
        // Show the full error stack if the verbose option is used twice or more.
        opts.stderr.write(((error.stack && options.verbose > 1) ? error.stack : error) + '\n');
      } else {
        // If the standard output for errors is not there, use console.error().
        console.error(error);
      }
    };
  
    // Confirm this is a compatible builder.
    return checkBuilderPath.then(() => {
      return KssBuilderBase.loadBuilder(options.builder);
    }).catch(error => {
      let catchLoadFailure;
      // If the builder specified was "builder/twig" and there was no existing
      // path relative to the working directory, we should try again while using
      // the path to kss-node's Twig builder.
      // istanbul ignore if
      if (options.builder === 'builder/twig') {
        options.builder = path.resolve(__dirname, '..', 'builder', 'twig');
        catchLoadFailure = KssBuilderBase.loadBuilder(options.builder);
      } else {
        catchLoadFailure = Promise.reject(error);
      }
  
      return catchLoadFailure.catch(error => {
        reportError(error);
        throw error;
      });
    }).then(builder => {
      builder.addOptionDefinitions(cliOptionDefinitions);
  
      // After the builder is loaded, we finally know all the option definitions.
      // So we re-run yargs one last time with all the yarg definitions we need.
      options = yargs(args)
        .options(builder.getOptionDefinitions())
        // Make a --help option available.
        .usage('Usage: kss [options]')
        .help('help')
        .alias('help', 'h')
        .alias('help', '?')
        .wrap(yargs.terminalWidth())
        // Make a --version option available.
        .version('version', version)
        // Complain if the user tries to configure a non-existent option.
        .strict()
        .argv;
  
      // If no arguments given, display help and exit.
      if (args.length === 0) {
        yargs.showHelp(reportMessage);
        return Promise.resolve();
      }
  
      // All paths from the config file are relative to the file.
      for (let key in configFileOptions) {
        if (configFileOptions.hasOwnProperty(key) && builder.getOptionDefinitions()[key] && builder.getOptionDefinitions()[key].path) {
          if (options[key] instanceof Array) {
            /* eslint-disable no-loop-func */
            options[key] = options[key].map(value => {
              return path.resolve(configFileDirectory, value);
            });
            /* eslint-enable no-loop-func */
          } else if (key !== 'builder' || options[key] !== 'builder/twig') {
            options[key] = path.resolve(configFileDirectory, options[key]);
          }
        }
      }
  
      // Check for source and destination set as unnamed parameters.
      if (options._.length > 0) {
        let positionalParams = options._;
        // Check for a second unnamed parameter, the destination.
        if (positionalParams.length > 1) {
          options.destination = positionalParams[1];
        }
  
        // The source directory is the first unnamed parameter.
        if (!(options.source instanceof Array)) {
          options.source = (typeof options.source === 'undefined') ? [] : [options.source];
        }
        options.source.unshift(positionalParams[0]);
      }
  
      // If we are building the demo, copy the styles.css file to the destination.
      let demo = true;
      if (options.demo) {
        // We save the Promise for the end of this function.
        demo = fs.copyAsync(
          path.resolve(__dirname, '../demo/styles.css'),
          path.resolve(options.destination, 'styles.css'),
          {clobber: true}
        ).catch(/* istanbul ignore next @TODO change back to an arrow function when istanbul adds support */ function(error) {
          reportError(error);
          return Promise.reject(error);
        });
      }
  
      // Clean up the settings by removing object properties that yargs adds, but
      // that we don't need for kss().
      ['config', '_', '//', 'help', 'h', '?', 'version', '$0'].forEach(key => delete options[key]);
      let optionDefinitions = builder.getOptionDefinitions();
      for (let key in optionDefinitions) {
        if (optionDefinitions[key].alias) {
          delete options[optionDefinitions[key].alias];
        }
      }
      for (let key in options) {
        if (typeof options[key] === 'undefined') {
          delete options[key];
        }
      }
  
      // Pass on cli()'s stdout/stderr reporters to kss().
      options.logFunction = reportMessage;
      options.logErrorFunction = reportError;
  
      return Promise.all([
        demo,
        kss(options)
      ]);
    });
  };
  
  module.exports = cli;
  