text: /*global define:true */
  
  if (typeof define !== 'function') {
    var define = require('amdefine')(module);
  }
  
  define(['./sprintf', './core', './parse', './task'],
         function (sprintf, core, parse, taskUtil) {
    'use strict';
    /*jshint regexp: false */
  
    var MISSING_NAME = 'first flow parameter should be the flow name, but found in/out def: %s';
    var INOUT_PARAMS_NO_MATCH = 'params in wrong format, wanted "foo, bar, cb -> err, baz" - found: %s';
    var MISSING_ERR = 'callback specified, but first out param was not "err", use for clarity. Found in/out def: %s';
    var MISSING_CB = 'found err param, but cb/callback is not specified, is this cb-style async or sync function? Found in/out def: %s';
    var EXTRA_TASKARG = 'extra unmatched task arg: %s';
  
    var INOUT_RE = /\->/;  // used to detect missing name, in/out as first arg
    var CB_NAMES_RE = /^cb|callback$/i;  //cb, Cb, CB, callback, Callback
    var ERR_NAMES_RE = /^err$/i; // err, ERR, Err, ...
  
    function filterOutTrailingCbParam(args) { // if has trailing cb | callback param, filter it out
      if (args.length && args[args.length - 1].match(CB_NAMES_RE)) args.pop();
      return args;
    }
  
    function filterOutLeadingErrParam(args) { // if leading err param, filter it out
      if (args.length && args[0].match(ERR_NAMES_RE)) args.shift();
      return args;
    }
  
    var inOutDefParse = {
      splitStr: '->',
      fn: function (m, origStr) {
        var inParams = parse.splitTrimFilterArgs(m[0]);
        var lastParam = inParams[inParams.length - 1];
        var type = (lastParam && CB_NAMES_RE.test(lastParam)) ? 'cb' : 'ret';
        var outParams = parse.splitTrimFilterArgs(m[1]);
        var firstOutParam = outParams[0];
        if (type === 'cb' && (!firstOutParam || !ERR_NAMES_RE.test(firstOutParam))) {
          throw new Error(sprintf(MISSING_ERR, origStr));  // found cb, but no err param
        } else if (type === 'ret' && firstOutParam && ERR_NAMES_RE.test(firstOutParam)) {
          throw new Error(sprintf(MISSING_CB, origStr));  // found err but not cb param
        }
        return {
          type: type,
          inDef: filterOutTrailingCbParam(inParams),
          outDef: filterOutLeadingErrParam(outParams)
        };
      }
    };
  
    function parseInOutParams(str) {
      var objDef = parse.parseStr(str, [inOutDefParse], INOUT_PARAMS_NO_MATCH);
      objDef.inDef = filterOutTrailingCbParam(objDef.inDef);
      return objDef;
    }
  
    function parseTasks(arr) {
      var tasks = [];
      var fn, obj, result;
      while (arr.length >= 2) {
        obj = {};
        fn = arr.shift();
        result = parseInOutParams(arr.shift());
        if (typeof(arr[0]) === 'object') obj = arr.shift(); // has options, use as obj
        obj.f = fn;
        obj.a = result.inDef;
        var type = result.type;
        obj.out = result.outDef;
        obj.type = type;
        tasks.push(obj);
      }
      if (arr.length) throw new Error(sprintf(EXTRA_TASKARG, arr[0]));
      return tasks;
    }
  
    /**
       Parse the variable arguments into in/out params, options, tasks
    */
    function parseVargs(vargs) {
      var inOutParamStr = vargs.shift() || '';
      // if next arg is object, shift it off as options
      var options = (vargs.length && typeof(vargs[0]) === 'object') ? vargs.shift() : { };
      var taskDefArr = vargs; // rest are for the tasks
      var defObj = {
        inOutParamStr: inOutParamStr,
        taskDefArr: taskDefArr,
        options: options
      };
      return defObj;
    }
  
  
    function dslDefine(name, arg1, arg2, argN) {
      var autoflowFn = core();
      if (name && INOUT_RE.test(name)) throw new Error(sprintf(MISSING_NAME, name));
      var defObj = parseVargs(Array.prototype.slice.call(arguments, 1)); // name, already used
      var inOutDef = parseInOutParams(defObj.inOutParamStr);
      var ast = {
        name: name,
        inParams: inOutDef.inDef,
        tasks: parseTasks(defObj.taskDefArr),
        outTask: { a: inOutDef.outDef }
      };
      if (defObj.options) Object.keys(defObj.options).forEach(function (k) { ast[k] = defObj.options[k]; });
      var errors = autoflowFn.setAndValidateAST(ast);
      if (errors.length) {
        var errorStr = errors.join('\n');
        throw new Error(errorStr);
      }
      return autoflowFn;
    }
  
    function selectFirst(name, arg1, arg2, argN) {
      var autoflowFn = core();
      var defObj = parseVargs(Array.prototype.slice.call(arguments, 1)); // name, already used
      var inOutDef = parseInOutParams(defObj.inOutParamStr);
      var tasks = taskUtil.serializeTasks(parseTasks(defObj.taskDefArr));
      var ast = {
        name: name,
        inParams: inOutDef.inDef,
        tasks: tasks,
        outTask: { type: 'finalcbFirst', a: inOutDef.outDef },
      };
      if (defObj.options) Object.keys(defObj.options).forEach(function (k) { ast[k] = defObj.options[k]; });
      var errors = autoflowFn.setAndValidateAST(ast);
      if (errors.length) {
        var errorStr = errors.join('\n');
        throw new Error(errorStr);
      }
      return autoflowFn;
    }
  
    dslDefine.selectFirst = selectFirst;
    return dslDefine;
  
  });