'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _bluebird = require('bluebird');

var _bluebird2 = _interopRequireDefault(_bluebird);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _fileJs = require('file-js');

var _fileJs2 = _interopRequireDefault(_fileJs);

var _functions = require('./functions');

var _files = require('./files');

var _arrays = require('./arrays');

var _unitCompare = require('unit-compare');

var _events = require('events');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function isDefined(value) {
  return value !== undefined;
}

function flatten(a, b) {
  return a.concat(b);
}

function getFilename(file) {
  return file.getName();
}

function isRegExpMatch(pattern) {
  return function (file) {
    return new RegExp(pattern).test(file.getName());
  };
}

function cleanExtension(ext) {
  if (_lodash2.default.startsWith(ext, '.')) {
    return ext.slice(1);
  }
  return ext;
}

/** @class */

var FileHound = function (_EventEmitter) {
  _inherits(FileHound, _EventEmitter);

  function FileHound() {
    _classCallCheck(this, FileHound);

    var _this = _possibleConstructorReturn(this, (FileHound.__proto__ || Object.getPrototypeOf(FileHound)).call(this));

    _this._filters = [];
    _this._searchPaths = [];
    _this._searchPaths.push(process.cwd());
    _this._ignoreHiddenDirectories = false;
    _this._isMatch = _lodash2.default.noop;
    _this._sync = false;
    _this._directoriesOnly = false;
    return _this;
  }

  /**
   * Static factory method to create an instance of FileHound
   *
   * @static
   * @memberOf FileHound
   * @method
   * create
   * @return FileHound instance
   * @example
   * import FileHound from 'filehound';
   *
   * const filehound = FileHound.create();
   */


  _createClass(FileHound, [{
    key: 'modified',


    /**
     * Filters by modifiction time
     *
     * @memberOf FileHound
     * @method
     * modified
     * @param {string} dateExpression - date expression
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .modified("< 2 days")
     *   .find()
     *   .each(console.log);
     */
    value: function modified(pattern) {
      this.addFilter(function (file) {
        var modified = file.lastModifiedSync();
        return (0, _unitCompare.isDate)(modified).assert(pattern);
      });
      return this;
    }

    /**
     * Filters by file access time
     *
     * @memberOf FileHound
     * @method
     * accessed
     * @param {string} dateExpression - date expression
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .accessed("< 10 minutes")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'accessed',
    value: function accessed(pattern) {
      this.addFilter(function (file) {
        var accessed = file.lastAccessedSync();
        return (0, _unitCompare.isDate)(accessed).assert(pattern);
      });
      return this;
    }

    /**
     * Filters change time
     *
     * @memberOf FileHound
     * @instance
     * @method
     * changed
     * @param {string} dateExpression - date expression
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .changed("< 10 minutes")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'changed',
    value: function changed(pattern) {
      this.addFilter(function (file) {
        var changed = file.lastChangedSync();
        return (0, _unitCompare.isDate)(changed).assert(pattern);
      });
      return this;
    }

    /**
     *
     * @memberOf FileHound
     * @instance
     * @method
     * addFilter
     * @param {function} function - custom filter function
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .addFilter(customFilter)
     *   .find()
     *   .each(consoe.log);
     */

  }, {
    key: 'addFilter',
    value: function addFilter(filter) {
      this._filters.push(filter);
      return this;
    }

    /**
     * Defines the search paths
     *
     * @memberOf FileHound
     * @instance
     * @method
     * paths
     * @param {array} path - array of paths
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .paths("/tmp", "/etc") // or ["/tmp", "/etc"]
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'paths',
    value: function paths() {
      this._searchPaths = _lodash2.default.uniq((0, _arrays.from)(arguments)).map(_path2.default.normalize);
      return this;
    }

    /**
     * Define the search path
     *
     * @memberOf FileHound
     * @instance
     * @method
     * path
     * @param {string} path - path
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .path("/tmp")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'path',
    value: function path() {
      return this.paths((0, _arrays.fromFirst)(arguments));
    }

    /**
     * Ignores files or sub-directories matching pattern
     *
     * @memberOf FileHound
     * @instance
     * @method
     * discard
     * @param {string|array} regex - regex or array of regex
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .discard("*.tmp*")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'discard',
    value: function discard() {
      var _this2 = this;

      var patterns = (0, _arrays.from)(arguments);
      patterns.forEach(function (pattern) {
        _this2.addFilter((0, _functions.negate)(isRegExpMatch(pattern)));
      });
      return this;
    }

    /**
     * Filter on file extension
     *
     * @memberOf FileHound
     * @instance
     * @method
     * ext
     * @param {string|array} extensions - extension or an array of extensions
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * let filehound = FileHound.create();
     * filehound
     *   .ext(".json")
     *   .find()
     *   .each(console.log);
     *
     * // array of extensions to filter by
     * filehound = FileHound.create();
     * filehound
     *   .ext([".json", ".txt"])
     *   .find()
     *   .each(console.log);
     *
     * // supports var args
     * filehound = FileHound.create();
     * filehound
     *   .ext(".json", ".txt")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'ext',
    value: function ext() {
      var extensions = (0, _arrays.from)(arguments).map(cleanExtension);

      this.addFilter(function (file) {
        return _lodash2.default.includes(extensions, file.getPathExtension());
      });
      return this;
    }

    /**
     * Filter by file size
     *
     * @memberOf FileHound
     * @instance
     * @method
     * size
     * @param {string} sizeExpression - a size expression
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .size("<10kb")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'size',
    value: function size(sizeExpression) {
      this.addFilter(function (file) {
        var size = file.sizeSync();
        return (0, _unitCompare.isNumber)(size).assert(sizeExpression);
      });
      return this;
    }

    /**
     * Filter by zero length files
     *
     * @memberOf FileHound
     * @instance
     * @method
     * isEmpty
     * @param {string} path - path
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .size("<10kb")
     *   .find()
     *   .each(console.log);
     */

  }, {
    key: 'isEmpty',
    value: function isEmpty() {
      this.size(0);
      return this;
    }

    /**
     * Filter by a file glob
     *
     * @memberOf FileHound
     * @instance
     * @method
     * glob
     * @param {string} glob - file glob
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .glob("*tmp*")
     *   .find()
     *   .each(console.log); // array of files names all containing 'tmp'
     */

  }, {
    key: 'glob',
    value: function glob(globPattern) {
      return this.match(globPattern);
    }

    /**
     * Same as glob
     * @see glob
     */

  }, {
    key: 'match',
    value: function match(globPattern) {
      this.addFilter(function (file) {
        return file.isMatch(globPattern);
      });
      return this;
    }

    /**
     * Negates filters
     *
     * @memberOf FileHound
     * @instance
     * @method
     * not
     * @param {string} glob - file glob
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .not()
     *   .glob("*tmp*")
     *   .find()
     *   .each(console.log); // array of files names NOT containing 'tmp'
     */

  }, {
    key: 'not',
    value: function not() {
      this.negateFilters = true;
      return this;
    }

    /**
     * Filter to ignore hidden files
     *
     * @memberOf FileHound
     * @instance
     * @method
     * ignoreHiddenFiles
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .ignoreHiddenFiles()
     *   .find()
     *   .each(console.log); // array of files names that are not hidden files
     */

  }, {
    key: 'ignoreHiddenFiles',
    value: function ignoreHiddenFiles() {
      this.addFilter(function (file) {
        return !file.isHiddenSync();
      });
      return this;
    }

    /**
     * Ignore hidden directories
     *
     * @memberOf FileHound
     * @instance
     * @method
     * ignoreHiddenDirectories
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .ignoreHiddenDirectories()
     *   .find()
     *   .each(console.log); // array of files names that are not hidden directories
     */

  }, {
    key: 'ignoreHiddenDirectories',
    value: function ignoreHiddenDirectories() {
      this._ignoreHiddenDirectories = true;
      return this;
    }

    /**
     * Find sub-directories
     *
     * @memberOf FileHound
     * @instance
     * @method
     * directory
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .directory()
     *   .find()
     *   .each(console.log); // array of matching sub-directories
     */

  }, {
    key: 'directory',
    value: function directory() {
      this._directoriesOnly = true;
      return this;
    }

    /**
     * Find sockets
     *
     * @memberOf FileHound
     * @instance
     * @method
     * socket
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .socket()
     *   .find()
     *   .each(console.log); // array of matching sockets
     */

  }, {
    key: 'socket',
    value: function socket() {
      this.addFilter(function (file) {
        return file.isSocket();
      });
      return this;
    }

    /**
     * Specify the directory search depth. If set to zero, recursive searching
     * will be disabled
     *
     * @memberOf FileHound
     * @instance
     * @method
     * depth
     * @return a FileHound instance
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .depth(0)
     *   .find()
     *   .each(console.log); // array of files names only in the current directory
     */

  }, {
    key: 'depth',
    value: function depth(_depth) {
      this.maxDepth = _depth;
      return this;
    }

    /**
     * Asynchronously executes a file search.
     *
     * @memberOf FileHound
     * @instance
     * @method
     * find
     * @param {function} function - Optionally accepts a callback function
     * @return Returns a Promise of all matches. If the Promise fulfils,
     * the fulfilment value is an array of all matching files
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * filehound
     *   .find()
     *   .each(console.log);
     *
     * // using a callback
     * filehound
     *   .find((err, files) => {
     *      if (err) return console.error(err);
     *
     *      console.log(files);
     *   });
     */

  }, {
    key: 'find',
    value: function find(cb) {
      var _this3 = this;

      this._initFilters();

      var searchAsync = this._searchAsync.bind(this);
      var searches = _bluebird2.default.map(this.getSearchPaths(), searchAsync);

      return _bluebird2.default.all(searches).reduce(flatten).map(getFilename).catch(function (e) {
        _this3.emit('error', e);
        throw e;
      }).finally(function () {
        _this3.emit('end');
      }).asCallback(cb);
    }

    /**
     * Synchronously executes a file search.
     *
     * @memberOf FileHound
     * @instance
     * @method
     * findSync
     * @return Returns an array of all matching files
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.create();
     * const files = filehound.findSync();
     * console.log(files);
     *
     */

  }, {
    key: 'findSync',
    value: function findSync() {
      this._initFilters();

      var searchSync = this._searchSync.bind(this);

      return this.getSearchPaths().map(searchSync).reduce(flatten).map(getFilename);
    }
  }, {
    key: '_atMaxDepth',
    value: function _atMaxDepth(root, dir) {
      var depth = dir.getDepthSync() - root.getDepthSync();
      return isDefined(this.maxDepth) && depth > this.maxDepth;
    }
  }, {
    key: '_shouldFilterDirectory',
    value: function _shouldFilterDirectory(root, dir) {
      return this._atMaxDepth(root, dir) || this._ignoreHiddenDirectories && dir.isHiddenSync();
    }
  }, {
    key: '_newMatcher',
    value: function _newMatcher() {
      var isMatch = (0, _functions.compose)(this._filters);
      if (this.negateFilters) {
        return (0, _functions.negate)(isMatch);
      }
      return isMatch;
    }
  }, {
    key: '_initFilters',
    value: function _initFilters() {
      this._isMatch = this._newMatcher();
    }
  }, {
    key: '_searchSync',
    value: function _searchSync(dir) {
      this._sync = true;
      var root = _fileJs2.default.create(dir);
      var trackedPaths = [];
      var files = this._search(root, root, trackedPaths);
      return this._directoriesOnly ? trackedPaths.filter(this._isMatch) : files;
    }
  }, {
    key: '_searchAsync',
    value: function _searchAsync(dir) {
      var _this4 = this;

      var root = _fileJs2.default.create(dir);
      var trackedPaths = [];
      var pending = this._search(root, root, trackedPaths);

      return pending.then(function (files) {
        if (_this4._directoriesOnly) return trackedPaths.filter(_this4._isMatch);

        files.forEach(function (file) {
          _this4.emit('match', file.getName());
        });
        return files;
      });
    }
  }, {
    key: '_search',
    value: function _search(root, path, trackedPaths) {
      var _this5 = this;

      if (this._shouldFilterDirectory(root, path)) return [];

      var getFiles = this._sync ? path.getFilesSync.bind(path) : path.getFiles.bind(path);
      return getFiles().map(function (file) {
        if (file.isDirectorySync()) {
          if (!_this5._shouldFilterDirectory(root, file)) trackedPaths.push(file);

          return _this5._search(root, file, trackedPaths);
        }
        return file;
      }).reduce(flatten, []).filter(this._isMatch);
    }
  }, {
    key: 'getSearchPaths',
    value: function getSearchPaths() {
      var paths = isDefined(this.maxDepth) ? this._searchPaths : (0, _files.reducePaths)(this._searchPaths);

      return (0, _arrays.copy)(paths);
    }
  }], [{
    key: 'create',
    value: function create() {
      return new FileHound();
    }

    /**
     * Returns all matches from one of more FileHound instances
     *
     * @static
     * @memberOf FileHound
     * @method
     * any
     * @return a promise containing all matches. If the Promise fulfils,
     * the fulfilment value is an array of all matching files.
     * @example
     * import FileHound from 'filehound';
     *
     * const filehound = FileHound.any(fh1, fh2);
     */

  }, {
    key: 'any',
    value: function any() {
      var args = (0, _arrays.from)(arguments);
      return _bluebird2.default.all(args).reduce(flatten, []);
    }
  }]);

  return FileHound;
}(_events.EventEmitter);

exports.default = FileHound;