const gulp = require('gulp'),
	concat = require('gulp-concat'),
	git = require('gulp-git'),
	log = require('fancy-log-levels'),
	noop = require('gulp-noop'),
	postcss = require('gulp-postcss'),
	shell = require('gulp-shell'),
	sort = require('gulp-sort'),
	sourcemaps = require('gulp-sourcemaps'),
	autoprefixer = require('autoprefixer'),
	which = require('which'),
	fs = require('fs'),
	path = require('path'),
	os = require('os'),
	yargs = require('yargs'),
	cfg = require('./build.config.json');

const args = yargs.options({
	verbosity: {type: 'number', default: 0},
	release: {type: 'boolean', default: false},
	embedsourcemaps: {type: 'boolean', default: false},
	sourcemapsincludecontent: {type: 'boolean', default: false}
}).argv;

const htmlOut = "tmp.html";

log(args.verbosity);

function tweeCompilerExecutable() {
	const systemTweego = which.sync('tweego', {nothrow: true});
	if (systemTweego) {
		log.info('Found system tweego at ', systemTweego);
		return systemTweego;
	}
	const archSuffix = os.arch() === 'x64' ? '64' : '86';
	const platformSuffix = {
		'Darwin': 'osx',
		'Linux': 'nix',
		'Windows_NT': 'win'
	}[os.type()];
	const extension = os.type() === 'Windows_NT' ? '.exe' : '';
	const res = path.join('.', 'devTools', 'tweeGo', `tweego_${platformSuffix}${archSuffix}${extension}`);
	log.info('Using bundled tweego at ', res);
	return res;
}

function concatFiles(srcGlob, destDir, destFileName) {
	return gulp.src(srcGlob)
		.pipe(sort())
		.pipe(concat(destFileName))
		.pipe(gulp.dest(destDir));
}

function processScripts(srcGlob, destDir, destFileName) {
	const addSourcemaps = !args.release;
	const prefix = path.relative(destDir, srcGlob.substr(0, srcGlob.indexOf('*')));
	return gulp.src(srcGlob)
		.pipe(sort())
		.pipe(addSourcemaps ? sourcemaps.init() : noop())
		.pipe(concat(destFileName))
		.pipe(addSourcemaps ?
			sourcemaps.write(args.embedsourcemaps ? undefined : '.', {
				includeContent: args.sourcemapsincludecontent,
				sourceRoot: prefix,
				sourceMappingURLPrefix: path.relative(cfg.dirs.output, destDir)
			}) :
			noop())
		.pipe(gulp.dest(destDir));
}

function processStylesheets(srcGlob, destDir, destFileName) {
	const addSourcemaps = !args.release;
	const prefix = path.relative(destDir, srcGlob.substr(0, srcGlob.indexOf('*')));
	return gulp.src(srcGlob)
		.pipe(sort())
		.pipe(addSourcemaps ? sourcemaps.init() : noop())
		.pipe(concat(destFileName))
		.pipe(cfg.options.css.autoprefix ?
			postcss([autoprefixer({overrideBrowserslist: ['last 2 versions']})]) :
			noop())
		.pipe(addSourcemaps ?
			sourcemaps.write(args.embedsourcemaps ? undefined : '.', {
				includeContent: args.sourcemapsincludecontent,
				sourceRoot: prefix,
				sourceMappingURLPrefix: path.relative(cfg.dirs.output, destDir)
			}) :
			noop())
		.pipe(gulp.dest(destDir));
}

function processSrc(name, processorFunc, globs, destDir, destFileName, ...args) {
	let tasks = [];
	if (!Array.isArray(globs) || globs.length === 1) {
		const src = Array.isArray(globs) ? globs[0] : globs;
		tasks.push(() => processorFunc(src, destDir, destFileName, args));
		tasks[tasks.length - 1].displayName = "process-" + name;
	} else { // many globs
		const ext = path.extname(destFileName);
		const bn = path.basename(destFileName, ext);
		for (let i = 0; i < globs.length; ++i) {
			tasks.push(() => processorFunc(globs[i], destDir, `${bn}-${i}${ext}`, args));
			tasks[tasks.length - 1].displayName = `process-${name}-${i}`;
		}
	}
	const res = gulp.parallel(...tasks);
	res.displayName = name;
	return res;
}

function injectGitCommit(cb) {
	git.revParse({args: '--short HEAD'}, function(err, hash) {
		if (!err) {
			log.info('current git hash: ', hash);
			fs.writeFile(cfg.gitVersionFile, `App.Version.commitHash = '${hash}';\n`, cb);
		} else {
			cb();
		}
	});
}

function cleanupGit(cb) {
	if (fs.existsSync(cfg.gitVersionFile)) {
		fs.unlink(cfg.gitVersionFile, cb);
	} else {
		cb();
	}
}

function compileStory() {
	let sources = [path.join(cfg.dirs.intermediate, "story")];
	sources.push(...cfg.sources.story.media);

	let modules = [path.join(cfg.dirs.intermediate, "module")];
	let moduleArgs = modules.map(fn => `--module=${fn}`);
	const cmdLine = `${tweeCompilerExecutable()} -f ${cfg.twineformat} --head=${cfg.sources.head} -o ${path.join(cfg.dirs.intermediate, htmlOut)} ${moduleArgs.join(' ')} ${sources.join(' ')}`;

	log.info(cmdLine);
	return gulp.src(sources, {read: false})
		.pipe(shell(cmdLine, {env: cfg.options.twee.environment}));
}

const processors = {
	"css": {
		func: processStylesheets,
		output: "styles.css"
	},
	"js": {
		func: processScripts,
		output: "script.js"
	},
	"twee": {
		func: concatFiles,
		output: "story.twee"
	},
	"media": {
		func: null
	}
};

function prepareComponent(name) {
	const c = cfg.sources[name];
	const outDir = path.join(cfg.dirs.intermediate, name);
	const subTasks = [];
	for (const srcType in c) {
		const proc = processors[srcType];
		if (proc.func) {
			subTasks.push(processSrc(`${name}-${srcType}`, proc.func, c[srcType], outDir, proc.output, cfg.options[srcType]));
		}
	}
	let r = gulp.parallel(subTasks);
	r.displayName = "prepare-" + name;
	return r;
}

function moveHTMLInPlace(cb) {
	fs.rename(path.join(cfg.dirs.intermediate, htmlOut), path.join(cfg.dirs.output, cfg.output), cb);
}

function clean(cb) {
	if (fs.existsSync(cfg.gitVersionFile)) {
		fs.unlinkSync(cfg.gitVersionFile);
	}
	fs.rmdirSync(cfg.dirs.intermediate, {recursive: true});
	cb();
}

function prepare(cb) {
	return gulp.series(clean, injectGitCommit, gulp.parallel(prepareComponent("module"), prepareComponent("story")))(cb);
}

if (fs.statSync('.git').isDirectory()) {
	gulp.task('buildHTML', gulp.series(prepare, compileStory, cleanupGit));
} else {
	gulp.task('buildHTML', gulp.series(prepare, compileStory));
}

exports.clean = clean;
exports.default = gulp.series('buildHTML', moveHTMLInPlace);