// validation-type-stuff, missing params,
// bad implications, custom checks.
module.exports = function (yargs, usage) {
    var self = {};

    // validate appropriate # of non-option
    // arguments were provided, i.e., '_'.
    self.nonOptionCount = function(argv) {
        var demanded = yargs.getDemanded();

        if (demanded._ && argv._.length < demanded._.count) {
            if (demanded._.msg !== undefined) {
                usage.fail(demanded._.msg);
            } else {
                usage.fail('Not enough non-option arguments: got '
                    + argv._.length + ', need at least ' + demanded._.count
                );
            }
        }
    };

    // make sure that any args that require an
    // value (--foo=bar), have a value.
    self.missingArgumentValue = function(argv) {
        var options = yargs.getOptions();

        if (options.requiresArg.length > 0) {
            var missingRequiredArgs = [];

            options.requiresArg.forEach(function(key) {
                var value = argv[key];

                // parser sets --foo value to true / --no-foo to false
                if (value === true || value === false) {
                    missingRequiredArgs.push(key);
                }
            });

            if (missingRequiredArgs.length == 1) {
                usage.fail("Missing argument value: " + missingRequiredArgs[0]);
            }
            else if (missingRequiredArgs.length > 1) {
                var message = "Missing argument values: " + missingRequiredArgs.join(", ");
                usage.fail(message);
            }
        }
    };

    // make sure all the required arguments are present.
    self.requiredArguments = function(argv) {
        var demanded = yargs.getDemanded(),
          missing = null;

        Object.keys(demanded).forEach(function (key) {
            if (!argv.hasOwnProperty(key)) {
                missing = missing || {};
                missing[key] = demanded[key];
            }
        });

        if (missing) {
            var customMsgs = [];
            Object.keys(missing).forEach(function(key) {
                var msg = missing[key].msg;
                if (msg && customMsgs.indexOf(msg) < 0) {
                    customMsgs.push(msg);
                }
            });
            var customMsg = customMsgs.length ? '\n' + customMsgs.join('\n') : '';

            usage.fail('Missing required arguments: ' + Object.keys(missing).join(', ') + customMsg);
        }
    };

    // check for unknown arguments (strict-mode).
    self.unknownArguments = function(argv, aliases) {
        var descriptions = usage.getDescriptions(),
          demanded = yargs.getDemanded(),
          unknown = [],
          aliasLookup = {};

        Object.keys(aliases).forEach(function (key) {
            aliases[key].forEach(function (alias) {
                aliasLookup[alias] = key;
            });
        });

        Object.keys(argv).forEach(function (key) {
            if (key !== "$0" && key !== "_" &&
                !descriptions.hasOwnProperty(key) &&
                !demanded.hasOwnProperty(key) &&
                !aliasLookup.hasOwnProperty(key)) {
                unknown.push(key);
            }
        });

        if (unknown.length == 1) {
            usage.fail("Unknown argument: " + unknown[0]);
        }
        else if (unknown.length > 1) {
            usage.fail("Unknown arguments: " + unknown.join(", "));
        }
    };

    // custom checks, added using the `check` option on yargs.
    var checks = [];
    self.check = function (f) {
        checks.push(f);
    };

    self.customChecks = function(argv, aliases) {
        checks.forEach(function (f) {
            try {
                var result = f(argv, aliases);
                if (!result) {
                    usage.fail('Argument check failed: ' + f.toString());
                } else if (typeof result === 'string') {
                    usage.fail(result);
                }
            }
            catch (err) {
                usage.fail(err)
            }
        });
    };

    // check implications, argument foo implies => argument bar.
    var implied = {};
    self.implies = function (key, value) {
        if (typeof key === 'object') {
            Object.keys(key).forEach(function (k) {
                self.implies(k, key[k]);
            });
        } else {
            implied[key] = value;
        }
    };
    self.getImplied = function() {
        return implied;
    }

    self.implications = function(argv) {
        var implyFail = [];

        Object.keys(implied).forEach(function (key) {
            var num, origKey = key, value = implied[key];

            // convert string '1' to number 1
            var num = Number(key);
            key = isNaN(num) ? key : num;

            if (typeof key === 'number') {
                // check length of argv._
                key = argv._.length >= key;
            } else if (key.match(/^--no-.+/)) {
                // check if key doesn't exist
                key = key.match(/^--no-(.+)/)[1];
                key = !argv[key];
            } else {
                // check if key exists
                key = argv[key];
            }

            num = Number(value);
            value = isNaN(num) ? value : num;

            if (typeof value === 'number') {
                value = argv._.length >= value;
            } else if (value.match(/^--no-.+/)) {
                value = value.match(/^--no-(.+)/)[1];
                value = !argv[value];
            } else {
                value = argv[value];
            }

            if (key && !value) {
                implyFail.push(origKey);
            }
        });

        if (implyFail.length) {
            var msg = 'Implications failed:\n';

            implyFail.forEach(function (key) {
                msg += ('  ' + key + ' -> ' + implied[key]);
            });

            usage.fail(msg);
        }
    }

    return self;
}
