text: var format = require('./helper').format;
  
  var PasswordPolicyError = require('./policy_error');
  
  function isString(value) {
    return typeof value === 'string' || value instanceof String;
  }
  
  var defaultRuleset = {
    length:           require('./rules/length'),
    contains:         require('./rules/contains'),
    containsAtLeast:  require('./rules/containsAtLeast'),
    identicalChars:   require('./rules/identicalChars'),
  };
  
  function flatDescriptions (descriptions, index) {
    if (!descriptions.length) {
      return '';
    }
  
    function flatSingleDescription (description, index) {
      var spaces = (new Array(index+1)).join(' ');
      var result = spaces + '* ';
      if (description.format) {
        result += format.apply(null, [description.message].concat(description.format));
      } else {
        result += description.message;
      }
  
      if (description.items) {
        result += '\n' + spaces + flatDescriptions(description.items, index + 1);
      }
      return result;
    }
  
    var firstDescription = flatSingleDescription(descriptions[0], index);
  
    descriptions = descriptions.slice(1).reduce(function (result, description) {
      result += '\n' + flatSingleDescription(description, index);
  
      return result;
    }, firstDescription);
  
    return descriptions;
  }
  
  /**
   * Creates a PasswordPolicy which is a set of rules.
   *
   * @class PasswordPolicy
   * @constructor
   */
  function PasswordPolicy(rules, ruleset) {
    this.rules = rules;
    this.ruleset = ruleset || defaultRuleset;
  
    this._reduce(function (result, ruleOptions, rule) {
      rule.validate(ruleOptions);
    });
  }
  
  PasswordPolicy.prototype = {};
  
  PasswordPolicy.prototype._reduce = function (fn, value) {
    var self = this;
    return Object.keys(this.rules).reduce(function (result, ruleName) {
      var ruleOptions = self.rules[ruleName];
      var rule = self.ruleset[ruleName];
  
      return fn(result, ruleOptions, rule);
  
    }, value);
  };
  
  PasswordPolicy.prototype._applyRules = function (password) {
    return this._reduce(function (result, ruleOptions, rule) {
      // If previous result was false as this an &&, then nothing to do here!
      if (!result) {
        return false;
      }
  
      if (!rule) {
        return false;
      }
  
      return rule.assert(ruleOptions, password);
    }, true);
  };
  
  PasswordPolicy.prototype.missing = function (password) {
    return this._reduce(function (result, ruleOptions, rule) {
      var missingRule = rule.missing(ruleOptions, password);
      result.rules.push(missingRule);
      result.verified = result.verified && !!missingRule.verified;
      return result;
    }, {rules: [], verified: true});
  };
  
  PasswordPolicy.prototype.explain = function () {
    return this._reduce(function (result, ruleOptions, rule) {
      result.push(rule.explain(ruleOptions));
      return result;
    }, []);
  };
  
  PasswordPolicy.prototype.missingAsMarkdown = function (password) {
    return flatDescriptions(this.missing(password), 1);
  };
  
  PasswordPolicy.prototype.toString = function () {
    var descriptions = this.explain();
    return flatDescriptions(descriptions, 0);
  };
  
  PasswordPolicy.prototype.check = function (password) {
    if (!isString(password)) {
      return false;
    }
  
    return this._applyRules(password);
  };
  
  PasswordPolicy.prototype.assert = function (password) {
    if (!this.check(password)) {
      throw new PasswordPolicyError('Password does not meet password policy');
    }
  };
  
  module.exports = PasswordPolicy;
  