class ConsistencyPatternRule extends TextLevelRule {

    protected final Language ruleLanguage;

    ConsistencyPatternRule(List<AbstractPatternRule> rules, Language lang) {
      this.rules = Collections.unmodifiableList(rules);
      this.ruleLanguage = lang;
      setPremium(rules.stream().anyMatch(r -> r.isPremium()));
    }

    private final List<AbstractPatternRule> rules;

    public List<AbstractPatternRule> getWrappedRules() {
      return rules;
    }

    @Override
    public String getId() {
      return getMainRuleId(rules.get(0).getId());
    }

    @Override
    public String getDescription() {
      return rules.get(0).getDescription();
    }
    
    @Override
    public RuleMatch[] match(List<AnalyzedSentence> sentences) throws IOException {<FILL_FUNCTION_BODY>}

    @Override
    public int minToCheckParagraph() {
      // TODO: what should we use here? calculate based on min_prev_matches?
      return 0;
    }

    @Override
    public boolean supportsLanguage(Language language) {
      return language.equalsConsiderVariantsIfSpecified(this.ruleLanguage);
    }

  }


      Map<String, Integer> countFeatures  = new HashMap<>();
      int offsetChars = 0;
      List<RuleMatch> matches = new ArrayList<>();
      for (AnalyzedSentence s : sentences) {
        List<RuleMatch> sentenceMatches = new ArrayList<>();
        for (AbstractPatternRule rule : rules) {
          RuleMatch[] ruleMatches = rule.match(s);
          sentenceMatches.addAll(Arrays.asList(ruleMatches));
        }
        sentenceMatches = new SameRuleGroupFilter().filter(sentenceMatches);
        // we need to adjust offsets since each pattern rule returns offsets relative to the sentence, not text
        List<RuleMatch> adjustedSentenceMatches = new ArrayList<>();
        for (RuleMatch rm : sentenceMatches) {
          rm.setSentencePosition(rm.getFromPos(), rm.getToPos());
          int fromPos = rm.getFromPos() + offsetChars;
          int toPos = rm.getToPos() + offsetChars;
          rm.setOffsetPosition(fromPos, toPos);
          adjustedSentenceMatches.add(rm);
        }
        matches.addAll(adjustedSentenceMatches);
        offsetChars += s.getText().length();
      }
      List<RuleMatch> resultMatches = new ArrayList<>();
      // count occurrences of features
      for (RuleMatch rm : matches) {
        String feature = getFeature(rm.getRule().getId());
        countFeatures.put(feature, countFeatures.getOrDefault(feature,0) + 1);
      }
      if (countFeatures.size()<2) {
        // there is no inconsistency
        return resultMatches.toArray(new RuleMatch[0]);
      }
      int max = Collections.max(countFeatures.values());
      ArrayList<String> featuresWithMax = new ArrayList<>();
      ArrayList<String> featuresToKeep = new ArrayList<>();
      ArrayList<String> featuresToSuggest = new ArrayList<>();
      for (Map.Entry<String, Integer> entry : countFeatures.entrySet()) {
        if (entry.getValue()==max) {
          featuresWithMax.add(entry.getKey());
        } else {
          featuresToKeep.add(entry.getKey());
        }
      }
      featuresToSuggest.addAll(featuresWithMax);
      if (featuresWithMax.size()>1) {
        featuresToKeep.addAll(featuresWithMax);
      }
      for (RuleMatch rm : matches) {
        if (featuresToKeep.contains(getFeature(rm.getRule().getId()))) {
          resultMatches.add(ruleLanguage.adjustMatch(rm, featuresToSuggest));
        }
      }
      return resultMatches.toArray(new RuleMatch[0]);
    
<no_super_class>