'use strict';

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

/* eslint no-sync:0 */

// Import
var pathUtil = require('path');
var fsUtil = require('fs');
var safeps = require('safeps');
var extractOptsAndCallback = require('extract-opts');

// Prepare
function complete(error, result, next) {
	if (next) {
		next(error, result);
		return null;
	} else {
		return error || result;
	}
}

// =====================================
// Define Module

// Require or install a package synchronously or asynchronously
// Also export this function as the default
function lazyRequire(name, opts, next) {

	// If we have a callback, then do async

	var _extractOptsAndCallba = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba2 = _slicedToArray(_extractOptsAndCallba, 2);

	opts = _extractOptsAndCallba2[0];
	next = _extractOptsAndCallba2[1];
	return next ? lazyRequire.async(name, opts, next) : lazyRequire.sync(name, opts);
}

// Require or install a package synchronously or asynchronously
lazyRequire.auto = function auto(name, opts, next) {

	// If we have no callback, or if we support sync, then do sync

	var _extractOptsAndCallba3 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba4 = _slicedToArray(_extractOptsAndCallba3, 2);

	opts = _extractOptsAndCallba4[0];
	next = _extractOptsAndCallba4[1];
	if (!next || lazyRequire.canSyncInstall()) {
		return lazyRequire.requireOrInstallSync(name, opts, next);
	} else {
		return lazyRequire.requireOrInstallAsync(name, opts, next);
	}
};

// Require or install a package synchronously
lazyRequire.sync = function sync(name, opts, next) {
	var _extractOptsAndCallba5 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba6 = _slicedToArray(_extractOptsAndCallba5, 2);

	opts = _extractOptsAndCallba6[0];
	next = _extractOptsAndCallba6[1];

	var result = lazyRequire.require(name, opts);
	var error = null;

	// Synchronous
	if (result instanceof Error) {
		result = lazyRequire.installSync(name, opts);
		if (result instanceof Error) {
			error = result;
			result = null;
		}
	}

	// Complete
	return complete(error, result, next);
};

// Require or install a package asynchronously
lazyRequire.async = function async(name, opts, next) {

	// Asynchronous

	var _extractOptsAndCallba7 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba8 = _slicedToArray(_extractOptsAndCallba7, 2);

	opts = _extractOptsAndCallba8[0];
	next = _extractOptsAndCallba8[1];
	lazyRequire.require(name, opts, function (error, result) {
		if (result) return next(error, result);
		lazyRequire.installAsync(name, opts, next);
	});

	// Exit
	return null;
};

// Attempt to require a module
lazyRequire.require = function _require(name, opts, next) {
	var _extractOptsAndCallba9 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba10 = _slicedToArray(_extractOptsAndCallba9, 2);

	opts = _extractOptsAndCallba10[0];
	next = _extractOptsAndCallba10[1];

	var result = null;
	var error = null;

	// Fetch
	try {
		result = require(name);
	} catch (e1) {
		error = e1;

		if (opts.cwd) {
			var path = pathUtil.join(opts.cwd, 'node_modules', name);
			try {
				result = require(path);
				error = null;
			} catch (e2) {
				error = e2;
			}
		}
	}

	// Complete
	return complete(error, result, next);
};

// Can Save
lazyRequire.canSave = function canSave(name, opts, next) {
	var _extractOptsAndCallba11 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba12 = _slicedToArray(_extractOptsAndCallba11, 2);

	opts = _extractOptsAndCallba12[0];
	next = _extractOptsAndCallba12[1];

	var result = null;
	var error = null;

	// Options
	if (opts.cwd == null) {
		opts.cwd = process.cwd();
	}
	opts.packagePath = pathUtil.join(opts.cwd, 'package.json');

	// Fetch
	try {
		result = fsUtil.existsSync(opts.packagePath) === true;
	} catch (err) {
		error = err;
	}

	// Complete
	return complete(error, result, next);
};

// Can install synchronously
lazyRequire.canSyncInstall = function canSyncInstall(opts, next) {
	var _extractOptsAndCallba13 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba14 = _slicedToArray(_extractOptsAndCallba13, 2);

	opts = _extractOptsAndCallba14[0];
	next = _extractOptsAndCallba14[1];

	var result = safeps.hasSpawnSync();
	var error = null;

	// Fetch
	if (result instanceof Error) {
		error = result;
		result = null;
	}

	// Complete
	return complete(error, result, next);
};

// Attempt to require a module (will install if missing)
// Asynchronous with optional callback
lazyRequire.installAsync = function installAsync(name, opts, next) {
	var _extractOptsAndCallba15 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba16 = _slicedToArray(_extractOptsAndCallba15, 2);

	opts = _extractOptsAndCallba16[0];
	next = _extractOptsAndCallba16[1];

	var error = null;

	// Defaults
	if (opts.save == null) {
		opts.save = false;
	}
	if (opts.cwd == null) {
		opts.cwd = process.cwd();
	}

	// Check that we are not in the browser
	if (process.browser === true) {
		error = new Error('lazy-require: installing in the browser is not possible');
	}

	// Check saving
	else if (opts.save === true && lazyRequire.canSave() === false) {
			error = new Error('lazy-require: cannot save the module as `opts.cwd` did not contain a `package.json` file');
		}

		// Install
		else {
				// Arguments
				var args = ['npm', 'install', name];
				if (opts.save === true) {
					args.push('--save');
					opts.save = null; // {delete opts.save} is very slow
				}

				// Install
				safeps.spawn(args, opts, function (err) {
					// Check
					if (err) return next(err);

					// Try to load the module
					lazyRequire.require(name, opts, next);
				});

				// Exit
				return null;
			}

	// Complete
	return complete(error, null, next);
};

// Attempt to require a module (will install if missing)
// Synchronous with optional callback
lazyRequire.installSync = function installSync(name, opts, next) {
	var _extractOptsAndCallba17 = extractOptsAndCallback(opts, next);
	// Prepare


	var _extractOptsAndCallba18 = _slicedToArray(_extractOptsAndCallba17, 2);

	opts = _extractOptsAndCallba18[0];
	next = _extractOptsAndCallba18[1];

	var error = null;

	// Defaults
	if (opts.save == null) {
		opts.save = false;
	}
	if (opts.cwd == null) {
		opts.cwd = process.cwd();
	}

	// Check that we are not in the browser
	if (process.browser === true) {
		error = new Error('lazy-require: installing in the browser is not possible');
	}

	// Check that spawnSync exists, check that the project's package.json exists
	else if (lazyRequire.canSyncInstall() === false) {
			error = new Error('lazy-require: installing synchronously is not possible');
		}

		// Check saving
		else if (opts.save === true && lazyRequire.canSave() === false) {
				error = new Error('lazy-require: cannot save the module as `opts.cwd` did not contain a `package.json` file');
			}

			// Install
			else {
					// Arguments
					var args = ['npm', 'install', name];
					if (opts.save === true) {
						args.push('--save');
						opts.save = null; // {delete opts.save} is very slow
					}

					// Install
					var spawnResult = safeps.spawnSync(args, opts);
					if (spawnResult instanceof Error) {
						error = spawnResult;
					} else {
						// Exit
						return lazyRequire.require(name, opts, next);
					}
				}

	// Complete
	return complete(error, null, next);
};

// Export
module.exports = lazyRequire;