text: /*
  	MIT License http://www.opensource.org/licenses/mit-license.php
  	Author Tobias Koppers @sokra
  */
  "use strict";
  
  const asyncLib = require("neo-async");
  const path = require("path");
  
  const {
  	Tapable,
  	AsyncSeriesWaterfallHook,
  	SyncWaterfallHook
  } = require("tapable");
  const ContextModule = require("./ContextModule");
  const ContextElementDependency = require("./dependencies/ContextElementDependency");
  
  const EMPTY_RESOLVE_OPTIONS = {};
  
  module.exports = class ContextModuleFactory extends Tapable {
  	constructor(resolverFactory) {
  		super();
  		this.hooks = {
  			beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
  			afterResolve: new AsyncSeriesWaterfallHook(["data"]),
  			contextModuleFiles: new SyncWaterfallHook(["files"]),
  			alternatives: new AsyncSeriesWaterfallHook(["modules"])
  		};
  		this._pluginCompat.tap("ContextModuleFactory", options => {
  			switch (options.name) {
  				case "before-resolve":
  				case "after-resolve":
  				case "alternatives":
  					options.async = true;
  					break;
  			}
  		});
  		this.resolverFactory = resolverFactory;
  	}
  
  	create(data, callback) {
  		const context = data.context;
  		const dependencies = data.dependencies;
  		const resolveOptions = data.resolveOptions;
  		const dependency = dependencies[0];
  		this.hooks.beforeResolve.callAsync(
  			Object.assign(
  				{
  					context: context,
  					dependencies: dependencies,
  					resolveOptions
  				},
  				dependency.options
  			),
  			(err, beforeResolveResult) => {
  				if (err) return callback(err);
  
  				// Ignored
  				if (!beforeResolveResult) return callback();
  
  				const context = beforeResolveResult.context;
  				const request = beforeResolveResult.request;
  				const resolveOptions = beforeResolveResult.resolveOptions;
  
  				let loaders,
  					resource,
  					loadersPrefix = "";
  				const idx = request.lastIndexOf("!");
  				if (idx >= 0) {
  					let loadersRequest = request.substr(0, idx + 1);
  					let i;
  					for (
  						i = 0;
  						i < loadersRequest.length && loadersRequest[i] === "!";
  						i++
  					) {
  						loadersPrefix += "!";
  					}
  					loadersRequest = loadersRequest
  						.substr(i)
  						.replace(/!+$/, "")
  						.replace(/!!+/g, "!");
  					if (loadersRequest === "") {
  						loaders = [];
  					} else {
  						loaders = loadersRequest.split("!");
  					}
  					resource = request.substr(idx + 1);
  				} else {
  					loaders = [];
  					resource = request;
  				}
  
  				const contextResolver = this.resolverFactory.get(
  					"context",
  					resolveOptions || EMPTY_RESOLVE_OPTIONS
  				);
  				const loaderResolver = this.resolverFactory.get(
  					"loader",
  					EMPTY_RESOLVE_OPTIONS
  				);
  
  				asyncLib.parallel(
  					[
  						callback => {
  							contextResolver.resolve(
  								{},
  								context,
  								resource,
  								{},
  								(err, result) => {
  									if (err) return callback(err);
  									callback(null, result);
  								}
  							);
  						},
  						callback => {
  							asyncLib.map(
  								loaders,
  								(loader, callback) => {
  									loaderResolver.resolve(
  										{},
  										context,
  										loader,
  										{},
  										(err, result) => {
  											if (err) return callback(err);
  											callback(null, result);
  										}
  									);
  								},
  								callback
  							);
  						}
  					],
  					(err, result) => {
  						if (err) return callback(err);
  
  						this.hooks.afterResolve.callAsync(
  							Object.assign(
  								{
  									addon:
  										loadersPrefix +
  										result[1].join("!") +
  										(result[1].length > 0 ? "!" : ""),
  									resource: result[0],
  									resolveDependencies: this.resolveDependencies.bind(this)
  								},
  								beforeResolveResult
  							),
  							(err, result) => {
  								if (err) return callback(err);
  
  								// Ignored
  								if (!result) return callback();
  
  								return callback(
  									null,
  									new ContextModule(result.resolveDependencies, result)
  								);
  							}
  						);
  					}
  				);
  			}
  		);
  	}
  
  	resolveDependencies(fs, options, callback) {
  		const cmf = this;
  		let resource = options.resource;
  		let resourceQuery = options.resourceQuery;
  		let recursive = options.recursive;
  		let regExp = options.regExp;
  		let include = options.include;
  		let exclude = options.exclude;
  		if (!regExp || !resource) return callback(null, []);
  
  		const addDirectory = (directory, callback) => {
  			fs.readdir(directory, (err, files) => {
  				if (err) return callback(err);
  				files = cmf.hooks.contextModuleFiles.call(files);
  				if (!files || files.length === 0) return callback(null, []);
  				asyncLib.map(
  					files.filter(p => p.indexOf(".") !== 0),
  					(segment, callback) => {
  						const subResource = path.join(directory, segment);
  
  						if (!exclude || !subResource.match(exclude)) {
  							fs.stat(subResource, (err, stat) => {
  								if (err) {
  									if (err.code === "ENOENT") {
  										// ENOENT is ok here because the file may have been deleted between
  										// the readdir and stat calls.
  										return callback();
  									} else {
  										return callback(err);
  									}
  								}
  
  								if (stat.isDirectory()) {
  									if (!recursive) return callback();
  									addDirectory.call(this, subResource, callback);
  								} else if (
  									stat.isFile() &&
  									(!include || subResource.match(include))
  								) {
  									const obj = {
  										context: resource,
  										request:
  											"." +
  											subResource.substr(resource.length).replace(/\\/g, "/")
  									};
  
  									this.hooks.alternatives.callAsync(
  										[obj],
  										(err, alternatives) => {
  											if (err) return callback(err);
  											alternatives = alternatives
  												.filter(obj => regExp.test(obj.request))
  												.map(obj => {
  													const dep = new ContextElementDependency(
  														obj.request + resourceQuery,
  														obj.request
  													);
  													dep.optional = true;
  													return dep;
  												});
  											callback(null, alternatives);
  										}
  									);
  								} else {
  									callback();
  								}
  							});
  						} else {
  							callback();
  						}
  					},
  					(err, result) => {
  						if (err) return callback(err);
  
  						if (!result) return callback(null, []);
  
  						callback(
  							null,
  							result.filter(Boolean).reduce((a, i) => a.concat(i), [])
  						);
  					}
  				);
  			});
  		};
  
  		addDirectory(resource, callback);
  	}
  };
  