// Generated by CoffeeScript 1.6.3
var Cmd, Color, PATH, Q, UTIL,
  __slice = [].slice;

UTIL = require('util');

PATH = require('path');

Color = require('./color').Color;

Q = require('q');

/**
Command

Top level entity. Commands may have options and arguments.
@namespace
@class Presents command
*/


exports.Cmd = Cmd = (function() {
  /**
  @constructs
  @param {COA.Cmd} [cmd] parent command
  */

  function Cmd(cmd) {
    if (!(this instanceof Cmd)) {
      return new Cmd(cmd);
    }
    this._parent(cmd);
    this._cmds = [];
    this._cmdsByName = {};
    this._opts = [];
    this._optsByKey = {};
    this._args = [];
    this._ext = false;
  }

  Cmd.get = function(propertyName, func) {
    return Object.defineProperty(this.prototype, propertyName, {
      configurable: true,
      enumerable: true,
      get: func
    });
  };

  /**
  Returns object containing all its subcommands as methods
  to use from other programs.
  @returns {Object}
  */


  Cmd.get('api', function() {
    var c, _fn,
      _this = this;
    if (!this._api) {
      this._api = function() {
        return _this.invoke.apply(_this, arguments);
      };
    }
    _fn = function(c) {
      return _this._api[c] = _this._cmdsByName[c].api;
    };
    for (c in this._cmdsByName) {
      _fn(c);
    }
    return this._api;
  });

  Cmd.prototype._parent = function(cmd) {
    this._cmd = cmd || this;
    if (cmd) {
      cmd._cmds.push(this);
      if (this._name) {
        this._cmd._cmdsByName[this._name] = this;
      }
    }
    return this;
  };

  /**
  Set a canonical command identifier to be used anywhere in the API.
  @param {String} _name command name
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.name = function(_name) {
    this._name = _name;
    if (this._cmd !== this) {
      this._cmd._cmdsByName[_name] = this;
    }
    return this;
  };

  /**
  Set a long description for command to be used anywhere in text messages.
  @param {String} _title command title
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.title = function(_title) {
    this._title = _title;
    return this;
  };

  /**
  Create new or add existing subcommand for current command.
  @param {COA.Cmd} [cmd] existing command instance
  @returns {COA.Cmd} new subcommand instance
  */


  Cmd.prototype.cmd = function(cmd) {
    if (cmd) {
      return cmd._parent(this);
    } else {
      return new Cmd(this);
    }
  };

  /**
  Create option for current command.
  @returns {COA.Opt} new option instance
  */


  Cmd.prototype.opt = function() {
    return new (require('./opt').Opt)(this);
  };

  /**
  Create argument for current command.
  @returns {COA.Opt} new argument instance
  */


  Cmd.prototype.arg = function() {
    return new (require('./arg').Arg)(this);
  };

  /**
  Add (or set) action for current command.
  @param {Function} act action function,
      invoked in the context of command instance
      and has the parameters:
          - {Object} opts parsed options
          - {Array} args parsed arguments
          - {Object} res actions result accumulator
      It can return rejected promise by Cmd.reject (in case of error)
      or any other value treated as result.
  @param {Boolean} [force=false] flag for set action instead add to existings
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.act = function(act, force) {
    if (!act) {
      return this;
    }
    if (!force && this._act) {
      this._act.push(act);
    } else {
      this._act = [act];
    }
    return this;
  };

  /**
  Set custom additional completion for current command.
  @param {Function} completion generation function,
      invoked in the context of command instance.
      Accepts parameters:
          - {Object} opts completion options
      It can return promise or any other value treated as result.
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.comp = function(_comp) {
    this._comp = _comp;
    return this;
  };

  /**
  Apply function with arguments in context of command instance.
  @param {Function} fn
  @param {Array} args
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.apply = function() {
    var args, fn;
    fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
    fn.apply(this, args);
    return this;
  };

  /**
  Make command "helpful", i.e. add -h --help flags for print usage.
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.helpful = function() {
    return this.opt().name('help').title('Help').short('h').long('help').flag().only().act(function() {
      return this.usage();
    }).end();
  };

  /**
  Adds shell completion to command, adds "completion" subcommand,
  that makes all the magic.
  Must be called only on root command.
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.completable = function() {
    return this.cmd().name('completion').apply(require('./completion')).end();
  };

  /**
  Allow command to be extendable by external node.js modules.
  @param {String} [pattern]  Pattern of node.js module to find subcommands at.
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.extendable = function(pattern) {
    this._ext = pattern || true;
    return this;
  };

  Cmd.prototype._exit = function(msg, code) {
    return process.once('exit', function() {
      if (msg) {
        console.error(msg);
      }
      return process.exit(code || 0);
    });
  };

  /**
  Build full usage text for current command instance.
  @returns {String} usage text
  */


  Cmd.prototype.usage = function() {
    var res;
    res = [];
    if (this._title) {
      res.push(this._fullTitle());
    }
    res.push('', 'Usage:');
    if (this._cmds.length) {
      res.push(['', '', Color('lred', this._fullName()), Color('lblue', 'COMMAND'), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]')].join(' '));
    }
    if (this._opts.length + this._args.length) {
      res.push(['', '', Color('lred', this._fullName()), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]')].join(' '));
    }
    res.push(this._usages(this._cmds, 'Commands'), this._usages(this._opts, 'Options'), this._usages(this._args, 'Arguments'));
    return res.join('\n');
  };

  Cmd.prototype._usage = function() {
    return Color('lblue', this._name) + ' : ' + this._title;
  };

  Cmd.prototype._usages = function(os, title) {
    var o, res, _i, _len;
    if (!os.length) {
      return;
    }
    res = ['', title + ':'];
    for (_i = 0, _len = os.length; _i < _len; _i++) {
      o = os[_i];
      res.push('  ' + o._usage());
    }
    return res.join('\n');
  };

  Cmd.prototype._fullTitle = function() {
    return (this._cmd === this ? '' : this._cmd._fullTitle() + '\n') + this._title;
  };

  Cmd.prototype._fullName = function() {
    return (this._cmd === this ? '' : this._cmd._fullName() + ' ') + PATH.basename(this._name);
  };

  Cmd.prototype._ejectOpt = function(opts, opt) {
    var pos;
    if ((pos = opts.indexOf(opt)) >= 0) {
      if (opts[pos]._arr) {
        return opts[pos];
      } else {
        return opts.splice(pos, 1)[0];
      }
    }
  };

  Cmd.prototype._checkRequired = function(opts, args) {
    var all, i;
    if (!(this._opts.filter(function(o) {
      return o._only && o._name in opts;
    })).length) {
      all = this._opts.concat(this._args);
      while (i = all.shift()) {
        if (i._req && i._checkParsed(opts, args)) {
          return this.reject(i._requiredText());
        }
      }
    }
  };

  Cmd.prototype._parseCmd = function(argv, unparsed) {
    var c, cmd, cmdDesc, e, i, optSeen, pkg;
    if (unparsed == null) {
      unparsed = [];
    }
    argv = argv.concat();
    optSeen = false;
    while (i = argv.shift()) {
      if (!i.indexOf('-')) {
        optSeen = true;
      }
      if (!optSeen && /^\w[\w-_]*$/.test(i)) {
        cmd = this._cmdsByName[i];
        if (!cmd && this._ext) {
          if (typeof this._ext === 'string') {
            if (~this._ext.indexOf('%s')) {
              pkg = UTIL.format(this._ext, i);
            } else {
              pkg = this._ext + i;
            }
          } else if (this._ext === true) {
            pkg = i;
            c = this;
            while (true) {
              pkg = c._name + '-' + pkg;
              if (c._cmd === c) {
                break;
              }
              c = c._cmd;
            }
          }
          try {
            cmdDesc = require(pkg);
          } catch (_error) {
            e = _error;
          }
          if (cmdDesc) {
            if (typeof cmdDesc === 'function') {
              this.cmd().name(i).apply(cmdDesc).end();
            } else if (typeof cmdDesc === 'object') {
              this.cmd(cmdDesc);
              cmdDesc.name(i);
            } else {
              throw new Error('Error: Unsupported command declaration type, ' + 'should be function or COA.Cmd() object');
            }
            cmd = this._cmdsByName[i];
          }
        }
        if (cmd) {
          return cmd._parseCmd(argv, unparsed);
        }
      }
      unparsed.push(i);
    }
    return {
      cmd: this,
      argv: unparsed
    };
  };

  Cmd.prototype._parseOptsAndArgs = function(argv) {
    var a, arg, args, i, m, nonParsedArgs, nonParsedOpts, opt, opts, res;
    opts = {};
    args = {};
    nonParsedOpts = this._opts.concat();
    nonParsedArgs = this._args.concat();
    while (i = argv.shift()) {
      if (i !== '--' && !i.indexOf('-')) {
        if (m = i.match(/^(--\w[\w-_]*)=(.*)$/)) {
          i = m[1];
          if (!this._optsByKey[i]._flag) {
            argv.unshift(m[2]);
          }
        }
        if (opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i])) {
          if (Q.isRejected(res = opt._parse(argv, opts))) {
            return res;
          }
        } else {
          return this.reject("Unknown option: " + i);
        }
      } else {
        if (i === '--') {
          i = argv.splice(0);
        }
        i = Array.isArray(i) ? i : [i];
        while (a = i.shift()) {
          if (arg = nonParsedArgs.shift()) {
            if (arg._arr) {
              nonParsedArgs.unshift(arg);
            }
            if (Q.isRejected(res = arg._parse(a, args))) {
              return res;
            }
          } else {
            return this.reject("Unknown argument: " + a);
          }
        }
      }
    }
    return {
      opts: this._setDefaults(opts, nonParsedOpts),
      args: this._setDefaults(args, nonParsedArgs)
    };
  };

  Cmd.prototype._setDefaults = function(params, desc) {
    var i, _i, _len;
    for (_i = 0, _len = desc.length; _i < _len; _i++) {
      i = desc[_i];
      if (!(i._name in params) && '_def' in i) {
        i._saveVal(params, i._def);
      }
    }
    return params;
  };

  Cmd.prototype._processParams = function(params, desc) {
    var i, n, notExists, res, v, vals, _i, _j, _len, _len1;
    notExists = [];
    for (_i = 0, _len = desc.length; _i < _len; _i++) {
      i = desc[_i];
      n = i._name;
      if (!(n in params)) {
        notExists.push(i);
        continue;
      }
      vals = params[n];
      delete params[n];
      if (!Array.isArray(vals)) {
        vals = [vals];
      }
      for (_j = 0, _len1 = vals.length; _j < _len1; _j++) {
        v = vals[_j];
        if (Q.isRejected(res = i._saveVal(params, v))) {
          return res;
        }
      }
    }
    return this._setDefaults(params, notExists);
  };

  Cmd.prototype._parseArr = function(argv) {
    return Q.when(this._parseCmd(argv), function(p) {
      return Q.when(p.cmd._parseOptsAndArgs(p.argv), function(r) {
        return {
          cmd: p.cmd,
          opts: r.opts,
          args: r.args
        };
      });
    });
  };

  Cmd.prototype._do = function(input) {
    var _this = this;
    return Q.when(input, function(input) {
      var cmd;
      cmd = input.cmd;
      return [_this._checkRequired].concat(cmd._act || []).reduce(function(res, act) {
        return Q.when(res, function(res) {
          return act.call(cmd, input.opts, input.args, res);
        });
      }, void 0);
    });
  };

  /**
  Parse arguments from simple format like NodeJS process.argv
  and run ahead current program, i.e. call process.exit when all actions done.
  @param {Array} argv
  @returns {COA.Cmd} this instance (for chainability)
  */


  Cmd.prototype.run = function(argv) {
    var cb,
      _this = this;
    if (argv == null) {
      argv = process.argv.slice(2);
    }
    cb = function(code) {
      return function(res) {
        var _ref, _ref1;
        if (res) {
          return _this._exit((_ref = res.stack) != null ? _ref : res.toString(), (_ref1 = res.exitCode) != null ? _ref1 : code);
        } else {
          return _this._exit();
        }
      };
    };
    Q.when(this["do"](argv), cb(0), cb(1)).done();
    return this;
  };

  /**
  Convenient function to run command from tests.
  @param {Array} argv
  @returns {Q.Promise}
  */


  Cmd.prototype["do"] = function(argv) {
    return this._do(this._parseArr(argv || []));
  };

  /**
  Invoke specified (or current) command using provided
  options and arguments.
  @param {String|Array} cmds  subcommand to invoke (optional)
  @param {Object} opts  command options (optional)
  @param {Object} args  command arguments (optional)
  @returns {Q.Promise}
  */


  Cmd.prototype.invoke = function(cmds, opts, args) {
    var _this = this;
    if (cmds == null) {
      cmds = [];
    }
    if (opts == null) {
      opts = {};
    }
    if (args == null) {
      args = {};
    }
    if (typeof cmds === 'string') {
      cmds = cmds.split(' ');
    }
    if (arguments.length < 3) {
      if (!Array.isArray(cmds)) {
        args = opts;
        opts = cmds;
        cmds = [];
      }
    }
    return Q.when(this._parseCmd(cmds), function(p) {
      if (p.argv.length) {
        return _this.reject("Unknown command: " + cmds.join(' '));
      }
      return Q.all([_this._processParams(opts, _this._opts), _this._processParams(args, _this._args)]).spread(function(opts, args) {
        return _this._do({
          cmd: p.cmd,
          opts: opts,
          args: args
        }).fail(function(res) {
          if (res && res.exitCode === 0) {
            return res.toString();
          } else {
            return _this.reject(res);
          }
        });
      });
    });
  };

  /**
  Return reject of actions results promise with error code.
  Use in .act() for return with error.
  @param {Object} reject reason
      You can customize toString() method and exitCode property
      of reason object.
  @returns {Q.promise} rejected promise
  */


  Cmd.prototype.reject = function(reason) {
    return Q.reject(reason);
  };

  /**
  Finish chain for current subcommand and return parent command instance.
  @returns {COA.Cmd} parent command
  */


  Cmd.prototype.end = function() {
    return this._cmd;
  };

  return Cmd;

})();
