text: // Generated by CoffeeScript 1.10.0
  var EventEmitter, TaskGroup, Watcher, createWatcher, eachr, extendr, extractOpts, fsUtil, ignorefs, pathUtil, scandir, typeChecker, watch, watchers, watchersTotal, watchrUtil,
    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty,
    slice = [].slice,
    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
  
  pathUtil = require('path');
  
  scandir = require('scandirectory');
  
  fsUtil = require('safefs');
  
  ignorefs = require('ignorefs');
  
  extendr = require('extendr');
  
  eachr = require('eachr');
  
  extractOpts = require('extract-opts');
  
  typeChecker = require('typechecker');
  
  TaskGroup = require('taskgroup').TaskGroup;
  
  watchrUtil = require('./watchr-util');
  
  EventEmitter = require('events').EventEmitter;
  
  
  /*
  Now to make watching files more convient and managed, we'll create a class which we can use to attach to each file.
  It'll provide us with the API and abstraction we need to accomplish difficult things like recursion.
  We'll also store a global store of all the watchers and their paths so we don't have multiple watchers going at the same time
  for the same file - as that would be quite ineffecient.
  Events:
  - `log` for debugging, receives the arguments `logLevel ,args...`
  - `error` for gracefully listening to error events, receives the arguments `err`
  - `watching` for when watching of the path has completed, receives the arguments `err, watcherInstance, isWatching`
  - `change` for listening to change events, receives the arguments `changeType, fullPath, currentStat, previousStat`
   */
  
  watchersTotal = 0;
  
  watchers = {};
  
  Watcher = (function(superClass) {
    extend(Watcher, superClass);
  
    Watcher.prototype.path = null;
  
    Watcher.prototype.stat = null;
  
    Watcher.prototype.fswatcher = null;
  
    Watcher.prototype.children = null;
  
    Watcher.prototype.state = 'pending';
  
    Watcher.prototype.method = null;
  
    Watcher.prototype.config = {
      path: null,
      listener: null,
      listeners: null,
      stat: null,
      outputLog: false,
      interval: 5007,
      persistent: true,
      catchupDelay: 2 * 1000,
      preferredMethods: null,
      followLinks: true,
      ignorePaths: false,
      ignoreHiddenFiles: false,
      ignoreCommonPatterns: true,
      ignoreCustomPatterns: null
    };
  
    function Watcher(opts, next) {
      this.listener = bind(this.listener, this);
      this.bubbler = bind(this.bubbler, this);
      this.bubble = bind(this.bubble, this);
      this.getStat = bind(this.getStat, this);
      this.isIgnoredPath = bind(this.isIgnoredPath, this);
      this.log = bind(this.log, this);
      var ref;
      this.children = {};
      this.config = extendr.extend({}, this.config);
      this.config.preferredMethods = ['watch', 'watchFile'];
      ref = extractOpts(opts, next), opts = ref[0], next = ref[1];
      if (opts) {
        this.setConfig(opts);
      }
      if (next) {
        this.watch(next);
      }
      this;
    }
  
    Watcher.prototype.setConfig = function(opts) {
      extendr.extend(this.config, opts);
      this.path = this.config.path;
      if (this.config.stat) {
        this.stat = this.config.stat;
        this.isDirectory = this.stat.isDirectory();
        delete this.config.stat;
      }
      if (this.config.listener || this.config.listeners) {
        this.removeAllListeners();
        if (this.config.listener) {
          this.listen(this.config.listener);
          delete this.config.listener;
        }
        if (this.config.listeners) {
          this.listen(this.config.listeners);
          delete this.config.listeners;
        }
      }
      return this;
    };
  
    Watcher.prototype.log = function() {
      var args, config, watchr;
      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
      watchr = this;
      config = this.config;
      if (config.outputLog === true) {
        console.log.apply(console, args);
      }
      watchr.emit.apply(watchr, ['log'].concat(slice.call(args)));
      return this;
    };
  
    Watcher.prototype.getIgnoredOptions = function(opts) {
      var config, ref, ref1, ref2, ref3;
      if (opts == null) {
        opts = {};
      }
      config = this.config;
      return {
        ignorePaths: (ref = opts.ignorePaths) != null ? ref : config.ignorePaths,
        ignoreHiddenFiles: (ref1 = opts.ignoreHiddenFiles) != null ? ref1 : config.ignoreHiddenFiles,
        ignoreCommonPatterns: (ref2 = opts.ignoreCommonPatterns) != null ? ref2 : config.ignoreCommonPatterns,
        ignoreCustomPatterns: (ref3 = opts.ignoreCustomPatterns) != null ? ref3 : config.ignoreCustomPatterns
      };
    };
  
    Watcher.prototype.isIgnoredPath = function(path, opts) {
      var ignore, watchr;
      watchr = this;
      ignore = ignorefs.isIgnoredPath(path, watchr.getIgnoredOptions(opts));
      watchr.log('debug', "ignore: " + path + " " + (ignore ? 'yes' : 'no'));
      return ignore;
    };
  
    Watcher.prototype.getStat = function(next) {
      var config, method, watchr;
      watchr = this;
      config = this.config;
      method = config.followLinks ? 'stat' : 'lstat';
      fsUtil[method](watchr.path, next);
      return this;
    };
  
    Watcher.prototype.isDirectory = function() {
      var watchr;
      watchr = this;
      return watchr.stat.isDirectory();
    };
  
    Watcher.prototype.bubble = function() {
      var args, watchr;
      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
      watchr = this;
      watchr.emit.apply(watchr, args);
      return this;
    };
  
    Watcher.prototype.bubbler = function(eventName) {
      var watchr;
      watchr = this;
      return function() {
        var args;
        args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
        return watchr.bubble.apply(watchr, [eventName].concat(slice.call(args)));
      };
    };
  
  
    /*
    	Listen
    	Add listeners to our watcher instance.
    	Overloaded to also accept the following:
    	- `changeListener` a single change listener
    	- `[changeListener]` an array of change listeners
    	- `{eventName:eventListener}` an object keyed with the event names and valued with a single event listener
    	- `{eventName:[eventListener]}` an object keyed with the event names and valued with an array of event listeners
     */
  
    Watcher.prototype.listen = function(eventName, listener) {
      var i, j, len, len1, listenerArray, listeners, watchr;
      watchr = this;
      if (listener == null) {
        listeners = eventName;
        if (typeChecker.isArray(listeners)) {
          for (i = 0, len = listeners.length; i < len; i++) {
            listener = listeners[i];
            watchr.listen('change', listener);
          }
        } else if (typeChecker.isPlainObject(listeners)) {
          for (eventName in listeners) {
            if (!hasProp.call(listeners, eventName)) continue;
            listenerArray = listeners[eventName];
            if (typeChecker.isArray(listenerArray)) {
              for (j = 0, len1 = listenerArray.length; j < len1; j++) {
                listener = listenerArray[j];
                watchr.listen(eventName, listener);
              }
            } else {
              watchr.listen(eventName, listenerArray);
            }
          }
        } else {
          watchr.listen('change', listeners);
        }
      } else {
        watchr.removeListener(eventName, listener);
        watchr.on(eventName, listener);
        watchr.log('debug', "added a listener: on " + watchr.path + " for event " + eventName);
      }
      return this;
    };
  
  
    /*
    	Listener
    	A change event has fired
    
    	Things to note:
    	- watchFile method
    		- Arguments
    			- currentStat - the updated stat of the changed file
    				- Exists even for deleted/renamed files
    			- previousStat - the last old stat of the changed file
    				- Is accurate, however we already have this
    		- For renamed files, it will will fire on the directory and the file
    	- watch method
    		- Arguments
    			- eventName - either 'rename' or 'change'
    				- THIS VALUE IS ALWAYS UNRELIABLE AND CANNOT BE TRUSTED
    			- filename - child path of the file that was triggered
    				- This value can also be unrealiable at times
    	- Both methods
    		- For deleted and changed files, it will fire on the file
    		- For new files, it will fire on the directory
    
    	Output arguments for your emitted event will be:
    	- for updated files the arguments will be: `'update', fullPath, currentStat, previousStat`
    	- for created files the arguments will be: `'create', fullPath, currentStat, null`
    	- for deleted files the arguments will be: `'delete', fullPath, null, previousStat`
    
    	In the future we will add:
    	- for renamed files: 'rename', fullPath, currentStat, previousStat, newFullPath
    	- rename is possible as the stat.ino is the same for the delete and create
     */
  
    Watcher.prototype.listenerTasks = null;
  
    Watcher.prototype.listenerTimeout = null;
  
    Watcher.prototype.listener = function(opts, next) {
      var config, currentStat, fileExists, previousStat, ref, tasks, watchr;
      watchr = this;
      config = this.config;
      ref = extractOpts(opts, next), opts = ref[0], next = ref[1];
      currentStat = null;
      fileExists = null;
      previousStat = watchr.stat;
      watchr.log('debug', "Watch triggered on: " + watchr.path);
      if (watchr.listenerTimeout != null) {
        clearTimeout(watchr.listenerTimeout);
      }
      watchr.listenerTimeout = setTimeout(function() {
        var listenerTasks;
        listenerTasks = watchr.listenerTasks;
        watchr.listenerTasks = null;
        watchr.listenerTimeout = null;
        return listenerTasks.run();
      }, config.catchupDelay || 0);
      if (watchr.listenerTasks != null) {
        if (next) {
          watchr.listenerTasks.done(next);
        }
        return this;
      }
      watchr.listenerTasks = tasks = new TaskGroup().done(function(err) {
        watchr.listenersExecuting -= 1;
        if (err) {
          watchr.emit('error', err);
        }
        return typeof next === "function" ? next(err) : void 0;
      });
      tasks.addTask(function(complete) {
        watchr.log('debug', "Watch followed through on: " + watchr.path);
        return fsUtil.exists(watchr.path, function(exists) {
          fileExists = exists;
          if (fileExists === false) {
            watchr.log('debug', "Determined delete: " + watchr.path);
            watchr.close('deleted');
            watchr.stat = null;
            tasks.clearRemaining();
            return complete();
          }
          return watchr.getStat(function(err, stat) {
            if (err) {
              return watchr.emit('error', err);
            }
            watchr.stat = currentStat = stat;
            if (watchr.stat.birthtime !== previousStat.birthtime) {
              return createWatcher(this, complete);
            } else {
              return complete();
            }
          });
        });
      });
      tasks.addTask(function() {
        if (watchrUtil.statChanged(previousStat, currentStat) === false) {
          watchr.log('debug', "Determined same: " + watchr.path, previousStat, currentStat);
          return tasks.clearRemaining();
        }
      });
      tasks.addGroup(function(addGroup, addTask, complete) {
        this.setConfig({
          concurrency: 0
        });
        if (watchr.isDirectory() === false) {
          watchr.log('debug', "Determined update: " + watchr.path);
          watchr.emit('change', 'update', watchr.path, currentStat, previousStat);
          return complete();
        }
        return fsUtil.readdir(watchr.path, function(err, newFileRelativePaths) {
          if (err) {
            return complete(err);
          }
          if (watchr.method === 'watch') {
            eachr(watchr.children, function(childFileWatcher, childFileRelativePath) {
              if (indexOf.call(newFileRelativePaths, childFileRelativePath) < 0) {
                return;
              }
              if (!childFileWatcher) {
                return;
              }
              tasks.addTask(function(complete) {
                watchr.log('debug', "Forwarding extensive change detection to child: " + childFileRelativePath + " via: " + watchr.path);
                return childFileWatcher.listener(null, complete);
              });
            });
          }
          eachr(watchr.children, function(childFileWatcher, childFileRelativePath) {
            var childFileFullPath;
            if (indexOf.call(newFileRelativePaths, childFileRelativePath) >= 0) {
              return;
            }
            childFileFullPath = pathUtil.join(watchr.path, childFileRelativePath);
            if (watchr.isIgnoredPath(childFileFullPath)) {
              watchr.log('debug', "Ignored delete: " + childFileFullPath + " via: " + watchr.path);
              return;
            }
            watchr.log('debug', "Determined delete: " + childFileFullPath + " via: " + watchr.path);
            watchr.closeChild(childFileRelativePath, 'deleted');
          });
          eachr(newFileRelativePaths, function(childFileRelativePath) {
            var childFileFullPath;
            if (watchr.children[childFileRelativePath] != null) {
              return;
            }
            watchr.children[childFileRelativePath] = false;
            childFileFullPath = pathUtil.join(watchr.path, childFileRelativePath);
            if (watchr.isIgnoredPath(childFileFullPath)) {
              watchr.log('debug', "Ignored create: " + childFileFullPath + " via: " + watchr.path);
              return;
            }
            addTask(function(complete) {
              watchr.log('debug', "Determined create: " + childFileFullPath + " via: " + watchr.path);
              return watchr.watchChild({
                fullPath: childFileFullPath,
                relativePath: childFileRelativePath,
                next: function(err, childFileWatcher) {
                  if (err) {
                    return complete(err);
                  }
                  watchr.emit('change', 'create', childFileFullPath, childFileWatcher.stat, null);
                  return complete();
                }
              });
            });
          });
          return complete();
        });
      });
      return this;
    };
  
  
    /*
    	Close
    	We will need something to close our listener for removed or renamed files
    	As renamed files are a bit difficult we will want to close and delete all the watchers for all our children too
    	Essentially it is a self-destruct
     */
  
    Watcher.prototype.close = function(reason) {
      var childRelativePath, ref, watchr;
      watchr = this;
      if (watchr.state !== 'active') {
        return this;
      }
      watchr.log('debug', "close: " + watchr.path);
      ref = watchr.children;
      for (childRelativePath in ref) {
        if (!hasProp.call(ref, childRelativePath)) continue;
        watchr.closeChild(childRelativePath, reason);
      }
      if (watchr.method === 'watchFile') {
        fsUtil.unwatchFile(watchr.path);
      }
      if (watchr.fswatcher != null) {
        watchr.fswatcher.close();
        watchr.fswatcher = null;
      }
      if (reason === 'deleted') {
        watchr.state = 'deleted';
        watchr.emit('change', 'delete', watchr.path, null, watchr.stat);
      } else if (reason === 'failure') {
        watchr.state = 'closed';
        watchr.log('warn', "Failed to watch the path " + watchr.path);
      } else {
        watchr.state = 'closed';
      }
      if (watchers[watchr.path] != null) {
        delete watchers[watchr.path];
        watchersTotal--;
      }
      return this;
    };
  
    Watcher.prototype.closeChild = function(fileRelativePath, reason) {
      var watcher, watchr;
      watchr = this;
      if (watchr.children[fileRelativePath] != null) {
        watcher = watchr.children[fileRelativePath];
        if (watcher) {
          watcher.close(reason);
        }
        delete watchr.children[fileRelativePath];
      }
      return this;
    };
  
  
    /*
    	Watch Child
    	Setup watching for a child
    	Bubble events of the child into our instance
    	Also instantiate the child with our instance's configuration where applicable
    	next(err, watchr)
     */
  
    Watcher.prototype.watchChild = function(opts, next) {
      var config, ref, watchr;
      watchr = this;
      config = this.config;
      ref = extractOpts(opts, next), opts = ref[0], next = ref[1];
      if (watchr.children[opts.relativePath]) {
        if (typeof next === "function") {
          next(null, watchr.children[opts.relativePath]);
        }
      } else {
        watchr.children[opts.relativePath] = watch({
          path: opts.fullPath,
          stat: opts.stat,
          listeners: {
            'log': watchr.bubbler('log'),
            'change': function() {
              var args, changeType, path;
              args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
              changeType = args[0], path = args[1];
              if (changeType === 'delete' && path === opts.fullPath) {
                watchr.closeChild(opts.relativePath, 'deleted');
              }
              return watchr.bubble.apply(watchr, ['change'].concat(slice.call(args)));
            },
            'error': watchr.bubbler('error')
          },
          next: next,
          outputLog: config.outputLog,
          interval: config.interval,
          persistent: config.persistent,
          catchupDelay: config.catchupDelay,
          preferredMethods: config.preferredMethods,
          ignorePaths: config.ignorePaths,
          ignoreHiddenFiles: config.ignoreHiddenFiles,
          ignoreCommonPatterns: config.ignoreCommonPatterns,
          ignoreCustomPatterns: config.ignoreCustomPatterns,
          followLinks: config.followLinks
        });
      }
      return watchr.children[opts.relativePath];
    };
  
  
    /*
    	Watch Children
    	next(err, watching)
     */
  
    Watcher.prototype.watchChildren = function(next) {
      var config, watchr;
      watchr = this;
      config = this.config;
      if (watchr.isDirectory()) {
        scandir({
          path: watchr.path,
          ignorePaths: config.ignorePaths,
          ignoreHiddenFiles: config.ignoreHiddenFiles,
          ignoreCommonPatterns: config.ignoreCommonPatterns,
          ignoreCustomPatterns: config.ignoreCustomPatterns,
          recurse: false,
          next: function(err) {
            var watching;
            watching = !err;
            return next(err, watching);
          },
          action: function(fullPath, relativePath, nextFile) {
            if (watchr.state !== 'active') {
              return nextFile(null, true);
            }
            return watchr.watchChild({
              fullPath: fullPath,
              relativePath: relativePath
            }, function(err, watcher) {
              return nextFile(err);
            });
          }
        });
      } else {
        next(null, true);
      }
      return this;
    };
  
  
    /*
    	Watch Self
    	next(err, watching)
     */
  
    Watcher.prototype.watchSelf = function(next) {
      var config, watchr;
      watchr = this;
      config = this.config;
      watchr.method = null;
      watchrUtil.watchMethods({
        path: watchr.path,
        methods: config.preferredMethods,
        persistent: config.persistent,
        interval: config.interval,
        listener: function() {
          return watchr.listener();
        },
        next: function(err, success, method, fswatcher) {
          watchr.fswatcher = fswatcher;
          if (err) {
            watchr.emit('error', err);
          }
          if (!success) {
            watchr.close('failure');
            return next(null, false);
          }
          watchr.method = method;
          watchr.state = 'active';
          return next(null, true);
        }
      });
      return this;
    };
  
  
    /*
    	Watch
    	Setup the native watching handlers for our path so we can receive updates on when things happen
    	If the next argument has been received, then add it is a once listener for the watching event
    	If we are already watching this path then let's start again (call close)
    	If we are a directory, let's recurse
    	If we are deleted, then don't error but return the isWatching argument of our completion callback as false
    	Once watching has completed for this directory and all children, then emit the watching event
    	next(err, watchr, watching)
     */
  
    Watcher.prototype.watch = function(next) {
      var complete, config, watchr;
      watchr = this;
      config = this.config;
      complete = function(err, watching) {
        if (err == null) {
          err = null;
        }
        if (watching == null) {
          watching = true;
        }
        if (err || !watching) {
          watchr.close();
          if (typeof next === "function") {
            next(err, watchr, false);
          }
          return watchr.emit('watching', err, watchr, false);
        } else {
          if (typeof next === "function") {
            next(null, watchr, true);
          }
          return watchr.emit('watching', null, watchr, true);
        }
      };
      if ((watchr.stat != null) === false) {
        watchr.getStat(function(err, stat) {
          if (err || !stat) {
            return complete(err, false);
          }
          watchr.stat = stat;
          return watchr.watch(next);
        });
        return this;
      }
      watchr.close();
      watchr.log('debug', "watch: " + this.path);
      watchr.watchSelf(function(err, watching) {
        if (err || !watching) {
          return complete(err, watching);
        }
        return watchr.watchChildren(function(err, watching) {
          return complete(err, watching);
        });
      });
      return this;
    };
  
    return Watcher;
  
  })(EventEmitter);
  
  
  /*
  Create Watcher
  Checks to see if the path actually exists, if it doesn't then exit gracefully
  If it does exist, then lets check our cache for an already existing watcher instance
  If we have an already existing watching instance, then just add our listeners to that
  If we don't, then create a watching instance
  Fire the next callback once done
  opts = {path, listener, listeners}
  opts = watcher instance
  next(err,watcherInstance)
   */
  
  createWatcher = function(opts, next) {
    var attempt, ref, watcher;
    ref = extractOpts(opts, next), opts = ref[0], next = ref[1];
    if (!fsUtil.existsSync(opts.path)) {
      if (typeof next === "function") {
        next(null, null);
      }
      return;
    }
    if (opts instanceof Watcher) {
      opts.close();
      opts = extendr.extend({}, opts.config, {
        listener: opts.listener,
        listeners: opts.listeners
      });
    }
    if (watchers[opts.path] != null) {
      watcher = watchers[opts.path];
      if (opts.listener) {
        watcher.listen(opts.listener);
      }
      if (opts.listeners) {
        watcher.listen(opts.listeners);
      }
      if (typeof next === "function") {
        next(null, watcher);
      }
    } else {
      attempt = 0;
      watcher = new Watcher(opts, function(err) {
        if (!err || attempt !== 0) {
          return typeof next === "function" ? next(err, watcher) : void 0;
        }
        ++attempt;
        watcher.log('debug', "Preferred method failed, trying methods in reverse order", err);
        return watcher.setConfig({
          preferredMethods: watcher.config.preferredMethods.reverse()
        }).watch();
      });
      watchers[opts.path] = watcher;
      ++watchersTotal;
    }
    return watcher;
  };
  
  
  /*
  Watch
  Provides an abstracted API that supports multiple paths
  If you are passing in multiple paths then do not rely on the return result containing all of the watchers
  you must rely on the result inside the completion callback instead
  If you used the paths option, then your results will be an array of watcher instances, otherwise they will be a single watcher instance
  next(err,results)
   */
  
  watch = function(opts, next) {
    var paths, ref, result, tasks;
    ref = extractOpts(opts, next), opts = ref[0], next = ref[1];
    result = [];
    if (opts.paths) {
      paths = opts.paths;
      delete opts.paths;
      if (typeChecker.isArray(paths)) {
        tasks = new TaskGroup({
          concurrency: 0
        }).whenDone(function(err) {
          return typeof next === "function" ? next(err, result) : void 0;
        });
        paths.forEach(function(path) {
          return tasks.addTask(function(complete) {
            var localOpts, watcher;
            localOpts = extendr.extend({}, opts, {
              path: path
            });
            watcher = createWatcher(localOpts, complete);
            if (watcher) {
              return result.push(watcher);
            }
          });
        });
        tasks.run();
      } else {
        opts.path = paths;
        result.push(createWatcher(opts, function(err) {
          return typeof next === "function" ? next(err, result) : void 0;
        }));
      }
    } else {
      result = createWatcher(opts, next);
    }
    return result;
  };
  
  module.exports = {
    watch: watch,
    Watcher: Watcher
  };
  