text: #include <zxcvbn/feedback.hpp>
  
  #include <zxcvbn/frequency_lists.hpp>
  #include <zxcvbn/optional.hpp>
  #include <zxcvbn/scoring.hpp>
  #include <zxcvbn/util.hpp>
  
  #include <regex>
  
  namespace zxcvbn {
  
  const Feedback DEFAULT_FEEDBACK = {
    "",
    {
      "Use a few words, avoid common phrases",
      "No need for symbols, digits, or uppercase letters",
    },
  };
  
  static
  optional::optional<Feedback> get_match_feedback(const Match & match, bool is_sole_match);
  
  static
  Feedback get_dictionary_match_feedback(const Match & match, bool is_sole_match);
  
  Feedback get_feedback(score_t score,
                        const std::vector<Match> & sequence) {
    // starting feedback
    if (!sequence.size()) return DEFAULT_FEEDBACK;
  
    // no feedback if score is good or great.
    if (score > 2) return {"", {}};
  
    // tie feedback to the longest match for longer sequences
    auto longest_match = sequence.begin();
    for (auto match = longest_match + 1; match != sequence.end(); ++match) {
      if (match->token.length() > longest_match->token.length()) {
        longest_match = match;
      }
    }
  
    auto maybe_feedback = get_match_feedback(*longest_match, sequence.size() == 1);
    auto extra_feedback = "Add another word or two. Uncommon words are better.";
    if (maybe_feedback) {
      auto & feedback = *maybe_feedback;
  
      feedback.suggestions.insert(maybe_feedback->suggestions.begin(),
                                  extra_feedback);
  
      return feedback;
    }
    else {
      return {"", {extra_feedback}};
    }
  }
  
  optional::optional<Feedback> get_match_feedback(const Match & match_, bool is_sole_match) {
    switch (match_.get_pattern()) {
    case MatchPattern::DICTIONARY: {
      return get_dictionary_match_feedback(match_, is_sole_match);
    }
  
    case MatchPattern::SPATIAL: {
      auto & match = match_.get_spatial();
      auto warning = (match.turns == 1)
        ? "Straight rows of keys are easy to guess"
        : "Short keyboard patterns are easy to guess";
  
      return Feedback{warning, {
          "Use a longer keyboard pattern with more turns",
            }};
    }
  
    case MatchPattern::REPEAT: {
      auto warning = (match_.get_repeat().base_token.length() == 1)
        ? "Repeats like \"aaa\" are easy to guess"
        : "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"";
  
      return Feedback{warning, {
          "Avoid repeated words and characters",
            }};
    }
  
    case MatchPattern::SEQUENCE: {
      return Feedback{"Sequences like abc or 6543 are easy to guess",
          {"Avoid sequences"},
          };
    }
  
    case MatchPattern::REGEX: {
      auto & match = match_.get_regex();
      if (match.regex_tag == RegexTag::RECENT_YEAR) {
        return Feedback{"Recent years are easy to guess", {
            "Avoid recent years",
            "Avoid years that are associated with you",
          }};
      }
      break;
    }
  
    case MatchPattern::DATE: {
      return Feedback{"Dates are often easy to guess", {
          "Avoid dates and years that are associated with you",
        }};
    }
    default:
      break;
    }
    return optional::nullopt;
  }
  
  static
  Feedback get_dictionary_match_feedback(const Match & match_, bool is_sole_match) {
    assert(match_.get_pattern() == MatchPattern::DICTIONARY);
    auto & match = match_.get_dictionary();
    auto warning = [&] {
      if (match.dictionary_tag == DictionaryTag::PASSWORDS) {
        if (is_sole_match and !match.l33t and !match.reversed) {
          if (match.rank <= 10) {
            return "This is a top-10 common password";
          }
          else if (match.rank <= 100) {
            return "This is a top-100 common password";
          }
          else {
            return "This is a very common password";
          }
        }
        else if (match_.guesses_log10 <= 4) {
          return "This is similar to a commonly used password";
        }
      }
      else if (match.dictionary_tag == DictionaryTag::ENGLISH_WIKIPEDIA) {
        if (is_sole_match) {
          return "A word by itself is easy to guess";
        }
      }
      else if (match.dictionary_tag == DictionaryTag::SURNAMES ||
               match.dictionary_tag == DictionaryTag::MALE_NAMES ||
               match.dictionary_tag == DictionaryTag::FEMALE_NAMES) {
        if (is_sole_match) {
          return "Names and surnames by themselves are easy to guess";
        }
        else {
          return "Common names and surnames are easy to guess";
        }
      }
  
      return "";
    }();
  
    std::vector<std::string> suggestions;
    auto & word = match_.token;
    if (std::regex_search(word, START_UPPER)) {
      suggestions.push_back("Capitalization doesn't help very much");
    }
    else if (std::regex_search(word, ALL_UPPER) and
             // XXX: UTF-8
             util::ascii_lower(word) == word) {
      suggestions.push_back("All-uppercase is almost as easy to guess as all-lowercase");
    }
  
    if (match.reversed and match_.token.length() >= 4) {
      suggestions.push_back("Reversed words aren't much harder to guess");
    }
    if (match.l33t) {
      suggestions.push_back("Predictable substitutions like '@' instead of 'a' don't help very much");
    }
  
    return {warning, suggestions};
  }
  
  }
  
  