'use strict';

var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
var _ = require('lodash');

var cacheBuster = require('./cacheBuster');
var meta;

function step(target, callback) {
	var startTime = Date.now();
	winston.info('[build] ' + target + ' build started');

	return function (err) {
		if (err) {
			winston.error('[build] ' + target + ' build failed');
			return callback(err);
		}

		var time = (Date.now() - startTime) / 1000;

		winston.info('[build] ' + target + ' build completed in ' + time + 'sec');
		callback();
	};
}

var targetHandlers = {
	'plugin static dirs': function (parallel, callback) {
		meta.js.linkStatics(callback);
	},
	'requirejs modules': function (parallel, callback) {
		meta.js.buildModules(parallel, callback);
	},
	'client js bundle': function (parallel, callback) {
		meta.js.buildBundle('client', parallel, callback);
	},
	'admin js bundle': function (parallel, callback) {
		meta.js.buildBundle('admin', parallel, callback);
	},
	javascript: [
		'plugin static dirs',
		'requirejs modules',
		'client js bundle',
		'admin js bundle',
	],
	'client side styles': function (parallel, callback) {
		meta.css.buildBundle('client', parallel, callback);
	},
	'admin control panel styles': function (parallel, callback) {
		meta.css.buildBundle('admin', parallel, callback);
	},
	styles: [
		'client side styles',
		'admin control panel styles',
	],
	templates: function (parallel, callback) {
		meta.templates.compile(callback);
	},
	languages: function (parallel, callback) {
		meta.languages.build(callback);
	},
	sounds: function (parallel, callback) {
		meta.sounds.build(callback);
	},
};

var aliases = {
	'plugin static dirs': ['staticdirs'],
	'requirejs modules': ['rjs', 'modules'],
	'client js bundle': ['clientjs', 'clientscript', 'clientscripts'],
	'admin js bundle': ['adminjs', 'adminscript', 'adminscripts'],
	javascript: ['js'],
	'client side styles': [
		'clientcss', 'clientless', 'clientstyles', 'clientstyle',
	],
	'admin control panel styles': [
		'admincss', 'adminless', 'adminstyles', 'adminstyle', 'acpcss', 'acpless', 'acpstyles', 'acpstyle',
	],
	styles: ['css', 'less', 'style'],
	templates: ['tpl'],
	languages: ['lang', 'i18n'],
	sounds: ['sound'],
};

exports.aliases = aliases;

aliases = Object.keys(aliases).reduce(function (prev, key) {
	var arr = aliases[key];
	arr.forEach(function (alias) {
		prev[alias] = key;
	});
	prev[key] = key;
	return prev;
}, {});

function beforeBuild(targets, callback) {
	var db = require('../database');
	var plugins = require('../plugins');
	meta = require('../meta');

	process.stdout.write('  started'.green + '\n'.reset);

	async.series([
		db.init,
		meta.themes.setupPaths,
		async.apply(plugins.prepareForBuild, targets),
	], function (err) {
		if (err) {
			winston.error('[build] Encountered error preparing for build', err);
			return callback(err);
		}

		callback();
	});
}

var allTargets = Object.keys(targetHandlers).filter(function (name) {
	return typeof targetHandlers[name] === 'function';
});
function buildTargets(targets, parallel, callback) {
	var all = parallel ? async.each : async.eachSeries;

	var length = Math.max.apply(Math, targets.map(function (name) {
		return name.length;
	}));

	all(targets, function (target, next) {
		targetHandlers[target](parallel, step(_.padStart(target, length) + ' ', next));
	}, callback);
}

function build(targets, callback) {
	if (targets === true) {
		targets = allTargets;
	} else if (!Array.isArray(targets)) {
		targets = targets.split(',');
	}

	targets = targets
		// get full target name
		.map(function (target) {
			target = target.toLowerCase().replace(/-/g, '');
			if (!aliases[target]) {
				winston.warn('[build] Unknown target: ' + target);
				return false;
			}

			return aliases[target];
		})
		// filter nonexistent targets
		.filter(Boolean)
		// map multitargets to their sets
		.reduce(function (prev, target) {
			if (Array.isArray(targetHandlers[target])) {
				return prev.concat(targetHandlers[target]);
			}

			return prev.concat(target);
		}, [])
		// unique
		.filter(function (target, i, arr) {
			return arr.indexOf(target) === i;
		});

	winston.verbose('[build] building the following targets: ' + targets.join(', '));

	if (typeof callback !== 'function') {
		callback = function (err) {
			if (err) {
				winston.error(err);
				process.exit(1);
			} else {
				process.exit(0);
			}
		};
	}

	if (!targets) {
		winston.info('[build] No valid targets supplied. Aborting.');
		callback();
	}

	var startTime;
	var totalTime;
	async.series([
		async.apply(beforeBuild, targets),
		function (next) {
			var threads = parseInt(nconf.get('threads'), 10);
			if (threads) {
				require('./minifier').maxThreads = threads - 1;
			}

			var parallel = !nconf.get('series');
			if (parallel) {
				winston.info('[build] Building in parallel mode');
			} else {
				winston.info('[build] Building in series mode');
			}

			startTime = Date.now();
			buildTargets(targets, parallel, next);
		},
		function (next) {
			totalTime = (Date.now() - startTime) / 1000;
			cacheBuster.write(next);
		},
	], function (err) {
		if (err) {
			winston.error('[build] Encountered error during build step', err);
			return callback(err);
		}

		winston.info('[build] Asset compilation successful. Completed in ' + totalTime + 'sec.');
		callback(null, true);
	});
}

exports.build = build;

exports.buildAll = function (callback) {
	build(allTargets, callback);
};