var path = require('path');
var mout = require('mout');
var Q = require('q');
var Project = require('../core/Project');
var semver = require('../util/semver');
var defaultConfig = require('../config');

function list(logger, options, config) {
    var project;

    options = options || {};

    // Make relative option true by default when used with paths
    if (options.paths && options.relative == null)  {
        options.relative = true;
    }

    config = defaultConfig(config);
    project = new Project(config, logger);

    return project.getTree(options)
    .spread(function (tree, flattened) {
        // Relativize paths
        // Also normalize paths on windows
        project.walkTree(tree, function (node) {
            if (node.missing) {
                return;
            }

            if (options.relative) {
                node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
            }
            if (options.paths) {
                node.canonicalDir = normalize(node.canonicalDir);
            }
        }, true);

        // Note that we need to to parse the flattened tree because it might
        // contain additional packages
        mout.object.forOwn(flattened, function (node) {
            if (node.missing) {
                return;
            }

            if (options.relative) {
                node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
            }
            if (options.paths) {
                node.canonicalDir = normalize(node.canonicalDir);
            }
        });

        // Render paths?
        if (options.paths) {
            return paths(flattened);
        }

        // Do not check for new versions?
        if (config.offline) {
            return tree;
        }

        // Check for new versions
        return checkVersions(project, tree, logger)
        .then(function () {
            return tree;
        });
    });
}

function checkVersions(project, tree, logger) {
    var promises;
    var nodes = [];
    var repository = project.getPackageRepository();

    // Gather all nodes, ignoring linked nodes
    project.walkTree(tree, function (node) {
        if (!node.linked) {
            nodes.push(node);
        }
    }, true);

    if (nodes.length) {
        logger.info('check-new', 'Checking for new versions of the project dependencies...');
    }

    // Check for new versions for each node
    promises = nodes.map(function (node) {
        var target = node.endpoint.target;

        return repository.versions(node.endpoint.source)
        .then(function (versions) {
            node.versions = versions;

            // Do not check if node's target is not a valid semver one
            if (versions.length && semver.validRange(target)) {
                node.update = {
                    target: semver.maxSatisfying(versions, target),
                    latest: semver.maxSatisfying(versions, '*')
                };
            }
        });
    });

    // Set the versions also for the root node
    tree.versions = [];

    return Q.all(promises);
}

function paths(flattened) {
    var ret = {};

    mout.object.forOwn(flattened, function (pkg, name) {
        var main;

        if (pkg.missing) {
            return;
        }

        main = pkg.pkgMeta.main;

        // If no main was specified, fallback to canonical dir
        if (!main) {
            ret[name] = pkg.canonicalDir;
            return;
        }

        // Normalize main
        if (typeof main === 'string') {
            main = [main];
        }

        // Concatenate each main entry with the canonical dir
        main = main.map(function (part) {
            return normalize(path.join(pkg.canonicalDir, part).trim());
        });

        // If only one main file, use a string
        // Otherwise use an array
        ret[name] = main.length === 1 ? main[0] : main;
    });

    return ret;
}

function normalize(src) {
    return src.replace(/\\/g, '/');
}

// -------------------

list.readOptions = function (argv) {
    var cli = require('../util/cli');

    var options = cli.readOptions({
        'paths': { type: Boolean, shorthand: 'p' },
        'relative': { type: Boolean, shorthand: 'r' }
    }, argv);

    delete options.argv;

    return [options];
};

module.exports = list;
