text: /*jslint node: true */
  
  'use strict';
  
  var through2 = require('through2');
  var Combine = require('ordered-read-streams');
  var unique = require('unique-stream');
  
  var glob = require('glob');
  var Minimatch = require('minimatch').Minimatch;
  var glob2base = require('glob2base');
  var path = require('path');
  
  var gs = {
    // creates a stream for a single glob or filter
    createStream: function(ourGlob, negatives, opt) {
      // remove path relativity to make globs make sense
      ourGlob = unrelative(opt.cwd, ourGlob);
  
      // create globbing stuff
      var globber = new glob.Glob(ourGlob, opt);
  
      // extract base path from glob
      var basePath = opt.base || glob2base(globber);
  
      // create stream and map events from globber to it
      var stream = through2.obj(negatives.length ? filterNegatives : undefined);
  
      var found = false;
  
      globber.on('error', stream.emit.bind(stream, 'error'));
      globber.on('end', function(){
        if (opt.allowEmpty !== true && !found && globIsSingular(globber)) {
          stream.emit('error', new Error('File not found with singular glob'));
        }
  
        stream.end();
      });
      globber.on('match', function(filename) {
        found = true;
  
        stream.write({
          cwd: opt.cwd,
          base: basePath,
          path: path.resolve(opt.cwd, filename)
        });
      });
  
      return stream;
  
      function filterNegatives(filename, enc, cb) {
        var matcha = isMatch.bind(null, filename);
        if (negatives.every(matcha)) {
          cb(null, filename); // pass
        } else {
          cb(); // ignore
        }
      }
    },
  
    // creates a stream for multiple globs or filters
    create: function(globs, opt) {
      if (!opt) opt = {};
      if (typeof opt.cwd !== 'string') opt.cwd = process.cwd();
      if (typeof opt.dot !== 'boolean') opt.dot = false;
      if (typeof opt.silent !== 'boolean') opt.silent = true;
      if (typeof opt.nonull !== 'boolean') opt.nonull = false;
      if (typeof opt.cwdbase !== 'boolean') opt.cwdbase = false;
      if (opt.cwdbase) opt.base = opt.cwd;
  
      // only one glob no need to aggregate
      if (!Array.isArray(globs)) globs = [globs];
  
      var positives = [];
      var negatives = [];
  
      globs.forEach(function(glob, index) {
        if (typeof glob !== 'string' && !(glob instanceof RegExp)) {
          throw new Error('Invalid glob at index ' + index);
        }
  
        var globArray = isNegative(glob) ? negatives : positives;
  
        // create Minimatch instances for negative glob patterns
        if (globArray === negatives && typeof glob === 'string') {
          glob = new Minimatch(unrelative(opt.cwd, glob), opt);
        }
  
        globArray.push({
          index: index,
          glob: glob
        });
      });
  
      if (positives.length === 0) throw new Error('Missing positive glob');
  
      // only one positive glob no need to aggregate
      if (positives.length === 1) return streamFromPositive(positives[0]);
  
      // create all individual streams
      var streams = positives.map(streamFromPositive);
  
      // then just pipe them to a single unique stream and return it
      var aggregate = new Combine(streams);
      var uniqueStream = unique('path');
      var returnStream = aggregate.pipe(uniqueStream);
  
      aggregate.on('error', function (err) {
        returnStream.emit('error', err);
      });
  
      return returnStream;
  
      function streamFromPositive(positive) {
        var negativeGlobs = negatives.filter(indexGreaterThan(positive.index)).map(toGlob);
        return gs.createStream(positive.glob, negativeGlobs, opt);
      }
    }
  };
  
  function isMatch(file, matcher) {
    if (matcher instanceof Minimatch) return matcher.match(file.path);
    if (matcher instanceof RegExp) return matcher.test(file.path);
  }
  
  function isNegative(pattern) {
    if (typeof pattern === 'string') return pattern[0] === '!';
    if (pattern instanceof RegExp) return true;
  }
  
  function unrelative(cwd, glob) {
    var mod = '';
    if (glob[0] === '!') {
      mod = glob[0];
      glob = glob.slice(1);
    }
    return mod+path.resolve(cwd, glob);
  }
  
  function indexGreaterThan(index) {
    return function(obj) {
      return obj.index > index;
    };
  }
  
  function toGlob(obj) {
    return obj.glob;
  }
  
  function globIsSingular(glob) {
    var globSet = glob.minimatch.set;
  
    if (globSet.length !== 1) {
      return false;
    }
  
    return globSet[0].every(function isString(value) {
      return typeof value === 'string';
    });
  }
  
  module.exports = gs;
  