'use strict';
const fs = require('fs');
const path = require('path');
const decompressTar = require('decompress-tar');
const decompressTarbz2 = require('decompress-tarbz2');
const decompressTargz = require('decompress-targz');
const decompressUnzip = require('decompress-unzip');
const mkdirp = require('mkdirp');
const pify = require('pify');
const stripDirs = require('strip-dirs');
const fsP = pify(fs);

const runPlugins = (input, opts) => {
	if (opts.plugins.length === 0) {
		return Promise.resolve([]);
	}

	return Promise.all(opts.plugins.map(x => x(input, opts))).then(files => files.reduce((a, b) => a.concat(b)));
};

const extractFile = (input, output, opts) => runPlugins(input, opts).then(files => {
	if (opts.strip > 0) {
		files = files
			.map(x => {
				x.path = stripDirs(x.path, opts.strip);
				return x;
			})
			.filter(x => x.path !== '.');
	}

	if (typeof opts.filter === 'function') {
		files = files.filter(opts.filter);
	}

	if (typeof opts.map === 'function') {
		files = files.map(opts.map);
	}

	if (!output) {
		return files;
	}

	return Promise.all(files.map(x => {
		if (x.type === 'directory') {
			return pify(mkdirp)(path.join(output, x.path)).then(() => x);
		}

		const dest = path.join(output, x.path);
		const mode = x.mode & ~process.umask();

		return pify(mkdirp)(path.dirname(dest))
			.then(() => {
				if (x.type === 'link') {
					return fsP.link(x.linkname, dest);
				}

				if (x.type === 'symlink') {
					return fsP.symlink(x.linkname, dest);
				}

				return fsP.writeFile(dest, x.data, {mode});
			})
			.then(() => x);
	}));
});

module.exports = (input, output, opts) => {
	if (typeof input !== 'string' && !Buffer.isBuffer(input)) {
		return Promise.reject(new TypeError('Input file required'));
	}

	if (typeof output === 'object') {
		opts = output;
		output = null;
	}

	opts = Object.assign({plugins: [
		decompressTar(),
		decompressTarbz2(),
		decompressTargz(),
		decompressUnzip()
	]}, opts);

	const read = typeof input === 'string' ? fsP.readFile(input) : Promise.resolve(input);

	return read.then(buf => extractFile(buf, output, opts));
};
