var mout = require('mout');
var cmd = require('../util/cmd');
var Q = require('q');
var shellquote = require('shell-quote');

var orderByDependencies = function(packages, installed, json) {
    var ordered = [];
    installed = mout.object.keys(installed);

    var depsSatisfied = function(packageName) {
        return (
            mout.array.difference(
                mout.object.keys(packages[packageName].dependencies),
                installed,
                ordered
            ).length === 0
        );
    };

    var depsFromBowerJson =
        json && json.dependencies ? mout.object.keys(json.dependencies) : [];
    var packageNames = mout.object.keys(packages);

    //get the list of the packages that are specified in bower.json in that order
    //its nice to maintain that order for users
    var desiredOrder = mout.array.intersection(depsFromBowerJson, packageNames);
    //then add to the end any remaining packages that werent in bower.json
    desiredOrder = desiredOrder.concat(
        mout.array.difference(packageNames, desiredOrder)
    );

    //the desired order isn't necessarily a correct dependency specific order
    //so we ensure that below
    var resolvedOne = true;
    while (resolvedOne) {
        resolvedOne = false;

        for (var i = 0; i < desiredOrder.length; i++) {
            var packageName = desiredOrder[i];
            if (depsSatisfied(packageName)) {
                ordered.push(packageName);
                mout.array.remove(desiredOrder, packageName);
                //as soon as we resolve a package start the loop again
                resolvedOne = true;
                break;
            }
        }

        if (!resolvedOne && desiredOrder.length > 0) {
            //if we're here then some package(s) doesn't have all its deps satisified
            //so lets just jam those names on the end
            ordered = ordered.concat(desiredOrder);
        }
    }

    return ordered;
};

var run = function(cmdString, action, logger, config) {
    logger.action(action, cmdString);

    //pass env + BOWER_PID so callees can identify a preinstall+postinstall from the same bower instance
    var env = mout.object.mixIn({ BOWER_PID: process.pid }, process.env);
    var args = shellquote.parse(cmdString, env);
    var cmdName = args[0];
    mout.array.remove(args, cmdName); //no rest() in mout

    var options = {
        cwd: config.cwd,
        env: env
    };

    var promise = cmd(cmdName, args, options);

    promise.progress(function(progress) {
        progress.split('\n').forEach(function(line) {
            if (line) {
                logger.action(action, line);
            }
        });
    });

    return promise;
};

var hook = function(
    action,
    ordered,
    config,
    logger,
    packages,
    installed,
    json
) {
    if (
        mout.object.keys(packages).length === 0 ||
        !config.scripts ||
        !config.scripts[action]
    ) {
        return Q();
    }

    var orderedPackages = ordered
        ? orderByDependencies(packages, installed, json)
        : mout.object.keys(packages);
    var placeholder = new RegExp('%', 'g');
    var cmdString = mout.string.replace(
        config.scripts[action],
        placeholder,
        orderedPackages.join(' ')
    );
    return run(cmdString, action, logger, config);
};

module.exports = {
    preuninstall: mout.function.partial(hook, 'preuninstall', false),
    postuninstall: mout.function.partial(hook, 'postuninstall', false),
    preinstall: mout.function.partial(hook, 'preinstall', true),
    postinstall: mout.function.partial(hook, 'postinstall', true),
    //only exposed for test
    _orderByDependencies: orderByDependencies
};
