text: var util = require('util') // Native Node util module
    , fs = require('fs')
    , path = require('path')
    , existsSync = typeof fs.existsSync == 'function' ?
        fs.existsSync : path.existsSync
    , EventEmitter = require('events').EventEmitter
    , Task
    , TaskBase
    , utils = require('../utils');
  
  /**
    @name jake
    @namespace jake
  */
  /**
    @name jake.Task
    @constructor
    @augments EventEmitter
    @description A Jake Task
  
    @param {String} name The name of the Task
    @param {Array} [prereqs] Prerequisites to be run before this task
    @param {Function} [action] The action to perform for this task
    @param {Object} [opts]
      @param {Array} [opts.asyc=false] Perform this task asynchronously.
      If you flag a task with this option, you must call the global
      `complete` method inside the task's action, for execution to proceed
      to the next task.
   */
  Task = function () {
    // Do constructor-work only on actual instances, not when used
    // for inheritance
    if (arguments.length) {
      this.init.apply(this, arguments);
    }
  };
  
  util.inherits(Task, EventEmitter);
  
  TaskBase = new (function () {
  
    // Parse any positional args attached to the task-name
    var parsePrereqName = function (name) {
          var taskArr = name.split('[')
            , taskName = taskArr[0]
            , taskArgs = [];
          if (taskArr[1]) {
            taskArgs = taskArr[1].replace(/\]$/, '');
            taskArgs = taskArgs.split(',');
          }
          return {
            name: taskName
          , args: taskArgs
          };
        };
  
    /**
      @name jake.Task#event:complete
      @event
     */
  
    this.init = function (name, prereqs, action, options) {
      var opts = options || {};
  
      this._currentPrereqIndex = 0;
  
      this.name = name;
      this.prereqs = prereqs;
      this.action = action;
      this.async = false;
      this.done = false;
      this.fullName = null;
      this.description = null;
      this.args = [];
      this.namespace = null;
  
      // Support legacy async-flag -- if not explicitly passed or falsy, will
      // be set to empty-object
      if (typeof opts == 'boolean' && opts === true) {
        this.async = true;
      }
      else {
        if (opts.async) {
          this.async = true;
        }
      }
    };
  
    /**
      @name jake.Task#invoke
      @function
      @description Runs prerequisites, then this task. If the task has already
      been run, will not run the task again.
     */
    this.invoke = function () {
      jake._invocationChain.push(this);
      this.args = Array.prototype.slice.call(arguments);
      this.runPrereqs();
    };
  
    /**
      @name jake.Task#reenable
      @function
      @description Runs this task, without running any prerequisites. If the task
      has already been run, it will still run it again.
     */
    this.execute = function () {
      jake._invocationChain.push(this);
      this.reenable();
      this.run();
    };
  
    this.runPrereqs = function () {
      if (this.prereqs && this.prereqs.length) {
        this.nextPrereq();
      }
      else {
        this.run();
      }
    };
  
    this.nextPrereq = function () {
      var self = this
        , index = this._currentPrereqIndex
        , name = this.prereqs[index]
        , absolute
        , prereq
        , parsed
        , filePath
        , stats;
  
      if (name) {
        parsed = parsePrereqName(name);
        absolute = parsed.name[0] === '^';
  
        if (absolute) {
          parsed.name = parsed.name.slice(1);
          prereq = jake.Task[parsed.name];
        } else {
          prereq = this.namespace.resolve(parsed.name);
        }
  
        // Task doesn't exist, may be a static file
        if (!prereq) {
          // May be namespaced
          filePath = name.split(':').pop();
          // Create a dummy FileTask if file actually exists
          if (existsSync(filePath)) {
            // If there's not already an existing dummy FileTask for it,
            // create one
            prereq = jake.Task[filePath];
            if (!prereq) {
              stats = fs.statSync(filePath);
              prereq = new jake.FileTask(filePath);
              prereq.modTime = stats.mtime;
              prereq.dummy = true;
              // Put this dummy Task in the global Tasks list so
              // modTime will be eval'd correctly
              jake.Task[filePath] = prereq;
            }
          }
          // Otherwise it's not a valid task
          else {
              throw new Error('Unknown task "' + name + '"');
          }
        }
  
        // Do when done
        if (prereq.done) {
          self.handlePrereqComplete(prereq);
        } else {
          prereq.once('complete', function () {
            self.handlePrereqComplete(prereq);
          });
          prereq.invoke.apply(prereq, parsed.args);
        }
      }
    };
  
    this.reenable = function (deep) {
      var prereqs
        , prereq;
      this.done = false;
      if (deep && this.prereqs) {
        prereqs = this.prereqs;
        for (var i = 0, ii = prereqs.length; i < ii; i++) {
          prereq = jake.Task[prereqs[i]];
          if (prereq) {
            prereq.reenable(deep);
          }
        }
      }
    };
  
    this.handlePrereqComplete = function (prereq) {
      var self = this;
      this._currentPrereqIndex++;
      if (this._currentPrereqIndex < this.prereqs.length) {
        setTimeout(function () {
          self.nextPrereq();
        }, 0);
      }
      else {
        this.run();
      }
    };
  
    this.shouldRunAction = function () {
      if (this.done || typeof this.action != 'function') {
        return false
      }
      return true;
    };
  
    this.run = function () {
      var runAction = this.shouldRunAction();
      if (runAction) {
        this.emit('start');
        try {
          this.action.apply(this, this.args);
        }
        catch (e) {
          this.emit('error', e);
          return; // Bail out, not complete
        }
      }
      if (!(runAction && this.async)) {
        complete();
      }
    };
  
    this.complete = function () {
      this._currentPrereqIndex = 0;
      this.done = true;
      this.emit('complete');
    };
  
  })();
  utils.mixin(Task.prototype, TaskBase);
  
  exports.Task = Task;
  