/**
 * Apeman build command.
 * @function apeman.commands.build
 * @param {string} taskName - Task name to execute.
 * @param {object} options
 * @param {function} callback - Callback when done.
 * @author Taka Okunishi
 *
 */
var print = require('../_print'),
    format = require('util').format,
    apeman = require('../../lib'),
    async = require('async');

exports = module.exports = function (taskName, options, callback) {

    var project = exports._loadProject();

    //Mode to apemanfile base directory so that relative paths resolve properly.
    process.chdir(project.basedir);


    if (!taskName) {
        exports._printTasks(project.tasks);
        callback && callback(null);
        return;
    }

    var startAt = new Date;
    var taskSeries = [].concat(taskName.split && taskName.split(',') || taskName)
        .map(function (taskName) {
            return exports._tasksSeries(project.tasks || {}, taskName);
        })
        .reduce(function (a, b) {
            return [].concat(a).concat(b);
        }, []);

    var done = false;
    async.series(
        taskSeries.map(function (task) {
            return exports._runTask(task);
        }),
        function (err) {
            if (done) {
                throw new Error('Done already called.');
            }
            done = true;
            var now = require('dateformat')(new Date, 'yyyy-mm-dd HH:MM');
            if (err) {
                print(format('Task "%s" failed.', taskName));
                print(format('Working directory:', process.cwd()));
                print.ng(err.stack || err);
            } else {
                var tookTime = new Date() - startAt,
                    msg = format('\n[%s] Task "%s" done. (%sms)\n', now, taskName, tookTime);
                print.ok(msg);
            }
            print('');
            callback && callback(err);
        });
};

/**
 * Run a single task.
 * @param task - Task data.
 * @returns {Function}
 * @protected
 * @ignore
 */
exports._runTask = function (task) {
    return function (callback) {
        print.info(format('\nRun task "%s"...', task.taskName));
        var startAt = new Date();
        var workerData = task.worker,
            worker = exports._requireModule(workerData),
            config = task.config || {};
        if (!worker) {
            throw new Error('Unknown worker:' + workerData.key || workerData);
        }
        try {
            worker(config, function (err) {
                var tookTime = new Date() - startAt;
                print.info('...done! (' + tookTime + 'ms)');
                callback(err);
            });
        } catch (e) {
            callback(e);
        }
    }
};

exports._loadProject = require('./../load_project');


exports._requireModule = function (data) {
    if (!data) return null;
    var project = apeman.currentProject;
    if (typeof(data) === 'string') {
        return project.lookupRequire(data);
    } else {
        var required = project.lookupRequire(data.path),
            keys = (data.key || '').split(',')
                .filter(function (key) {
                    return !!key;
                });
        while (keys.length && required) {
            required = required[keys.shift()];
        }
        return required;
    }
};


exports._printTasks = function (tasks) {
    print('');
    print('--- available tasks ---');
    Object.keys(tasks)
        .sort(function (a, b) {
            return a.localeCompare(b);
        })
        .forEach(function (taskName) {
            var msg = taskName;
            var task = tasks[taskName];
            if (Array.isArray(task)) {
                msg = [msg, '->', task.join(',')].join(' ');
            }
            print(msg);
        });
    print('');
};

exports._tasksSeries = function (tasksData, taskName) {
    return [].concat(taskName)
        .map(function (name) {
            var task = tasksData[name];
            if (!task) throw new Error('Task with name "' + name + '" not found.');
            return [].concat(task)
                .filter(function (task) {
                    return !!task;
                })
                .map(function (task) {
                    if (typeof task === 'string') {
                        return exports._tasksSeries(tasksData, task);
                    } else {
                        task.taskName = taskName;
                        return task;
                    }

                })
                .reduce(function (a, b) {
                    return [].concat(a).concat(b);
                }, []);
        })
        .reduce(function (a, b) {
            return [].concat(a).concat(b);
        }, []);
}