
package de.ivu.elect.business.stimmeingabe.control.plausi;

import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;

import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescriptionResolver;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfiguration;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfigurationContext;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeKopfzahl;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeKopfzahlErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfigurationCustomData;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.util.TriFunction;

public abstract class AbstractKopfzahlPlausiBuilder extends AbstractPlausiBuilder {

  protected AbstractKopfzahlPlausiBuilder() {
    super();
  }

  @Deprecated // nur für Tests
  protected AbstractKopfzahlPlausiBuilder(Messages messages, StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver,
      StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration) {
    super(messages, stimmeingabeErfassenDescriptionResolver, stimmeingabeKopfzahlKonfiguration);
  }

  public void fillKopfzahlPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    fillKopfzahlPlausisAllgemein(model, context);
    fillKopfzahlPlausisWahlartSpezifisch(model, context);

    logStimmeingabePlausis(context, model.getAllStimmeingaben());
  }

  protected void fillKopfzahlPlausisAllgemein(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    fillPruefkennzeichenWahlberechtigtePlausis(model, context);
  }

  protected abstract void fillKopfzahlPlausisWahlartSpezifisch(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context);

  protected void fillKopfzahlEndgueltigMitVorlaeufigerStimmeingabeVergleichPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    if (context.isErgebniseingangsartEndgueltig()) {
      if (isWahlberechtigteGesamtZuPlausibilisieren(model)) {
        model.getWahlberechtigteGesamtStimmeingabe().ifPresent(stimmeingabeWahlberechtigteGesamtEndgueltig -> {
          Stimmeingabe stimmeingabeWahlberechtigteGesamtVorlaeufig = context.getVorlaeufigeStimmeingabePerTk(stimmeingabeWahlberechtigteGesamtEndgueltig.getTk());

          if (stimmeingabeWahlberechtigteGesamtVorlaeufig != null) {
            addPlausiKopfzahl(model.getWahlberechtigteGesamt(), new StimmeingabeAnzahlEndgueltigGleichStimmeingabeAnzahlVorlaeufigPlausi(
                StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_GESAMT, stimmeingabeWahlberechtigteGesamtVorlaeufig,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.STIMMEINGABE_ANZAHL_ENDGUELTIG_GLEICH_STIMMEINGABE_ANZAHL_VORLAEUFIG, StimmeingabePlausiFehlerart.WARNING),
                getMessages()), context);
          }
        });
      }

      if (isWaehlerGesamtZuPlausibilisieren(model)) {
        model.getWaehlerGesamtStimmeingabe().ifPresent(stimmeingabeWaehlerGesamtEndgueltig -> {
          Stimmeingabe stimmeingabeWaehlerGesamtVorlaeufig = context.getVorlaeufigeStimmeingabePerTk(stimmeingabeWaehlerGesamtEndgueltig.getTk());

          if (stimmeingabeWaehlerGesamtVorlaeufig != null) {
            addPlausiKopfzahl(model.getWaehlerGesamt(), new StimmeingabeAnzahlEndgueltigGleichStimmeingabeAnzahlVorlaeufigPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAEHLER_GESAMT,
                stimmeingabeWaehlerGesamtVorlaeufig,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.STIMMEINGABE_ANZAHL_ENDGUELTIG_GLEICH_STIMMEINGABE_ANZAHL_VORLAEUFIG, StimmeingabePlausiFehlerart.WARNING),
                getMessages()), context);
          }
        });
      }
    }
  }

  protected void fillUrnenwahlbezirkKopfzahlSummenPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    fillUrnenwahlbezirkKopfzahlSummenWahlberechtigtePlausis(model, context);
    fillUrnenwahlbezirkKopfzahlSummenWaehlerPlausis(model, context);
  }

  // TODO ELECT-6493, sollte private sein, cf. DdKommunalwahlUndLtwKopfzahlPlausiBuilder!
  protected void fillUrnenwahlbezirkKopfzahlSummenWahlberechtigtePlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    fillUrnenwahlbezirkKopfzahlSummenWahlberechtigtePlausis(model, context, StimmeingabePlausiFehlerart.ERROR);
  }

  protected void fillUrnenwahlbezirkKopfzahlSummenWahlberechtigtePlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context,
      StimmeingabePlausiFehlerart stimmeingabePlausiFehlerart) {
    if (isWahlberechtigteGesamtZuPlausibilisieren(getStimmeingabeKopfzahlKonfiguration(), model.getBezugGebiet(), model.getErgebniseingangsart(), context)) {
      Stimmeingabe stimmeingabeWahlberechtigteOhneWahlschein = model.getWahlberechtigteOhneWahlscheinStimmeingabe().orElse(null);
      Stimmeingabe stimmeingabeWahlberechtigteMitWahlschein = model.getWahlberechtigteMitWahlscheinStimmeingabe().orElse(null);

      if (stimmeingabeWahlberechtigteOhneWahlschein != null && stimmeingabeWahlberechtigteMitWahlschein != null) {
        Stimmeingabe stimmeingabeWahlberechtigteNichtImWaehlerverzeichnis = model.getWahlberechtigteNichtImWaehlerverzeichnisStimmeingabe().orElse(null);

        addPlausiKopfzahl(model.getWahlberechtigteGesamt(), // A = A1 + A2 + ...
            new WahlberechtigteGesamtAnzahlGleichWahlberechtigteAnzahlenSummePlausi(stimmeingabeWahlberechtigteOhneWahlschein, stimmeingabeWahlberechtigteMitWahlschein,
                stimmeingabeWahlberechtigteNichtImWaehlerverzeichnis,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_WAHLBERECHTIGTE_ANZAHLEN_SUMME, stimmeingabePlausiFehlerart),
                getMessages()),
            context);
      }
    }
  }

  protected void fillUrnenwahlbezirkKopfzahlSummenWaehlerPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    fillUrnenwahlbezirkKopfzahlSummenWaehlerPlausis(model, context, StimmeingabePlausiFehlerart.ERROR);
  }

  protected void fillUrnenwahlbezirkKopfzahlSummenWaehlerPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context,
      StimmeingabePlausiFehlerart stimmeingabePlausiFehlerart) {
    if (isWaehlerGesamtZuPlausibilisieren(model)) {
      Stimmeingabe stimmeingabeWaehlerOhneWahlschein = model.getWaehlerOhneWahlscheinStimmeingabe().orElse(null);
      Stimmeingabe stimmeingabeWaehlerMitWahlschein = model.getWaehlerMitWahlscheinStimmeingabe().orElse(null);

      if (stimmeingabeWaehlerOhneWahlschein != null && stimmeingabeWaehlerMitWahlschein != null) {
        Stimmeingabe stimmeingabeWaehlerMitVollmachtStimmeingabe = model.getWaehlerMitVollmachtStimmeingabe().orElse(null);

        addPlausiKopfzahl(model.getWaehlerGesamt(), // B = B1 + B2 + ...
            new WaehlerGesamtAnzahlGleichWaehlerAnzahlenSummePlausi(stimmeingabeWaehlerOhneWahlschein, stimmeingabeWaehlerMitWahlschein, stimmeingabeWaehlerMitVollmachtStimmeingabe,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GLEICH_WAEHLER_ANZAHLEN_SUMME, stimmeingabePlausiFehlerart), getMessages()),
            context);
      }
    }
  }

  protected void fillUrnenwahlbezirkWahlberechtigteAnzahlGleichWahlberechtigteAnzahlAmGebietPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    if (isWahlberechtigteOhneWahlscheinZuPlausibilisierenGegenWahlberechtigteOhneWahlscheinAmGebiet(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteOhneWahlschein(),
          new WahlberechtigteAnzahlGleichWahlberechtigteAnzahlAmGebietPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN,
              extractRequiredAnzahlWahlberechtigteOhneWahlscheinAmGebiet(model),
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, StimmeingabePlausiFehlerart.ERROR),
              getMessages()),
          context);
    }

    if (isWahlberechtigteMitWahlscheinZuPlausibilisierenGegenWahlberechtigteMitWahlscheinAmGebiet(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteMitWahlschein(),
          new WahlberechtigteAnzahlGleichWahlberechtigteAnzahlAmGebietPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_MIT_WAHLSCHEIN,
              extractRequiredAnzahlWahlberechtigteMitWahlscheinAmGebiet(model),
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, StimmeingabePlausiFehlerart.ERROR),
              getMessages()),
          context);
    }

    if (isWahlberechtigteNichtImWaehlerverzeichnisZuPlausibilisierenGegenWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteNichtImWaehlerverzeichnis(), new WahlberechtigteAnzahlGleichWahlberechtigteAnzahlAmGebietPlausi(
          StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS, extractRequiredAnzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(model),
          findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, StimmeingabePlausiFehlerart.ERROR),
          getMessages()), context);
    }

    if (isWahlberechtigteGesamtZuPlausibilisierenGegenWahlberechtigteGesamtAmGebiet(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteGesamt(),
          new WahlberechtigteAnzahlGleichWahlberechtigteAnzahlAmGebietPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_GESAMT,
              extractRequiredAnzahlWahlberechtigteGesamtAmGebiet(model),
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, StimmeingabePlausiFehlerart.ERROR), getMessages()),
          context);
    }
  }

  /**
   * A.k.a. "Eingenommene Wahlscheine"
   */
  protected void addPlausiWaehlerMitWahlscheinKleinerWahlberechtigteMitWahlscheinAnteil(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context,
      boolean stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisRelevant, double anteilWahlberechtigteMitWahlschein) {
    if (isWaehlerMitWahlscheinZuPlausibilisieren(model)) {
      model.getWahlberechtigteMitWahlscheinStimmeingabe().ifPresent(stimmeingabeWahlberechtigteMitWahlschein -> {
        StimmeingabeKopfzahl stimmeingabeWaehlerMitWahlschein = model.getWaehlerMitWahlschein();

        StimmeingabePlausiFehlerart stimmeingabePlausiFehlerart = StimmeingabePlausiFehlerart.WARNING;
        StimmeingabePlausiKonfigurationCustomData stimmeingabePlausiKonfigurationCustomData = new StimmeingabeAnzahlComparedToStimmeingabeAnzahlAnteilPlausiCustomData(
            anteilWahlberechtigteMitWahlschein);

        Optional<Stimmeingabe> stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisIfPresent = model.getWahlberechtigteNichtImWaehlerverzeichnisStimmeingabe();

        if (stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisRelevant && stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisIfPresent.isPresent()) {
          Stimmeingabe stimmeingabeWahlberechtigteNichtImWaehlerverzeichnis = stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisIfPresent.get();

          addPlausiKopfzahl(stimmeingabeWaehlerMitWahlschein,
              new WaehlerMitWahlscheinAnzahlKleinerWahlberechtigteMitWahlscheinAnzahlPlusWahlberechtigteNichtImWaehlerverzeichnisAnzahlAnteilPlausi(stimmeingabeWahlberechtigteMitWahlschein,
                  stimmeingabeWahlberechtigteNichtImWaehlerverzeichnis,
                  findStimmeingabePlausiKonfigurationOrDefaults(context,
                      StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_PLUS_WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_ANTEIL,
                      stimmeingabePlausiFehlerart, stimmeingabePlausiKonfigurationCustomData),
                  getMessages()),
              context);
        } else {
          addPlausiKopfzahl(stimmeingabeWaehlerMitWahlschein,
              new WaehlerMitWahlscheinAnzahlKleinerWahlberechtigteMitWahlscheinAnzahlAnteilPlausi(
                  stimmeingabeWahlberechtigteMitWahlschein, findStimmeingabePlausiKonfigurationOrDefaults(context,
                      StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_ANTEIL, stimmeingabePlausiFehlerart, stimmeingabePlausiKonfigurationCustomData),
                  getMessages()),
              context);
        }
      });
    }
  }

  /**
   * A.k.a. "Eingenommene Wahlscheine"
   */
  protected void addPlausiWaehlerMitWahlscheinKleinerWahlberechtigteMitWahlschein(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context,
      boolean stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisRelevant) {
    if (isWaehlerMitWahlscheinZuPlausibilisieren(model)) {
      Optional<Stimmeingabe> stimmeingabeWahlberechtigteMitWahlscheinIfPresent = model.getWahlberechtigteMitWahlscheinStimmeingabe();

      stimmeingabeWahlberechtigteMitWahlscheinIfPresent.ifPresent(stimmeingabeWahlberechtigteMitWahlschein -> {
        StimmeingabeKopfzahl stimmeingabeWaehlerMitWahlschein = model.getWaehlerMitWahlschein();
        StimmeingabePlausiFehlerart stimmeingabePlausiFehlerart = StimmeingabePlausiFehlerart.WARNING;

        Optional<Stimmeingabe> stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisIfPresent = model.getWahlberechtigteNichtImWaehlerverzeichnisStimmeingabe();

        if (stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisRelevant && stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisIfPresent.isPresent()) {
          Stimmeingabe stimmeingabeWahlberechtigteNichtImWaehlerverzeichnis = stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisIfPresent.get();

          addPlausiKopfzahl(stimmeingabeWaehlerMitWahlschein,
              new WaehlerMitWahlscheinAnzahlKleinerWahlberechtigteMitWahlscheinAnzahlPlusWahlberechtigteNichtImWaehlerverzeichnisAnzahlPlausi(stimmeingabeWahlberechtigteMitWahlschein,
                  stimmeingabeWahlberechtigteNichtImWaehlerverzeichnis,
                  findStimmeingabePlausiKonfigurationOrDefaults(context,
                      StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_PLUS_WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL,
                      stimmeingabePlausiFehlerart),
                  getMessages()),
              context);
        } else {
          addPlausiKopfzahl(stimmeingabeWaehlerMitWahlschein, new WaehlerMitWahlscheinAnzahlKleinerWahlberechtigteMitWahlscheinAnzahlPlausi(stimmeingabeWahlberechtigteMitWahlschein,
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL, stimmeingabePlausiFehlerart),
              getMessages()), context);
        }
      });
    }
  }

  protected void addPlausiWaehlerMitWahlscheinKleinerGleichWaehlerGesamt(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    if (isWaehlerMitWahlscheinZuPlausibilisieren(model)) {
      model.getWaehlerGesamtStimmeingabe().ifPresent(stimmeingabeWaehlerGesamt -> model.getWaehlerMitWahlscheinStimmeingabe().ifPresent(stimmeingabeWaehlerMitWahlschein -> {
        addPlausi(stimmeingabeWaehlerMitWahlschein,
            new WaehlerMitWahlscheinAnzahlKleinerGleichWaehlerGesamtAnzahlPlausi(stimmeingabeWaehlerGesamt,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_GLEICH_WAEHLER_GESAMT_ANZAHL, StimmeingabePlausiFehlerart.ERROR),
                getMessages()),
            context);

        // ELECT-9171, kann NIE ohne die vorherige Plausi auftreten!!!
        addPlausi(stimmeingabeWaehlerMitWahlschein,
            new WaehlerMitWahlscheinAnzahlKleinerWaehlerGesamtAnzahlPlausi(stimmeingabeWaehlerGesamt,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAEHLER_GESAMT_ANZAHL, StimmeingabePlausiFehlerart.WARNING),
                getMessages()),
            context);
      }));
    }
  }

  protected void addPlausiWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    model.getWaehlerMitWahlscheinStimmeingabe().ifPresent(stimmeingabeWaehlerMitWahlschein -> {
      model.getWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlStimmeingabe().ifPresent(stimmeingabeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl -> {
        if (isWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlZuPlausibilisieren(model)) {
          addPlausi(stimmeingabeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, // ELECTDD-349, Wähler mit Wahlschein >= Wähler mit Wahlschein aus Briefwahl
              new WaehlerMitWahlscheinAnzahlAusBriefwahlAnzahlKleinerGleichWaehlerMitWahlscheinAnzahlPlausi(stimmeingabeWaehlerMitWahlschein,
                  findStimmeingabePlausiKonfigurationOrDefaults(context,
                      StimmeingabePlausiTyp.WAEHLER_AUS_BRIEFWAHLBEZIRK_ERFASST_IN_URNENWAHLBEZIRK_ANZAHL_KLEINER_GLEICH_WAEHLER_MIT_WAHLSCHEIN_ANZAHL, StimmeingabePlausiFehlerart.ERROR),
                  getMessages()),
              context);
        }

        model.getWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlStimmeingabe().ifPresent(stimmeingabeWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl -> {
          if (isWaehlerMitWahlscheinZuPlausibilisieren(model)) {
            addPlausi(stimmeingabeWaehlerMitWahlschein, // ELECT-13269, Wähler mit Wahlschein =  Wähler mit Wahlschein aus Urnenwahl +  Wähler mit Wahlschein aus Briefwahl
                new WaehlerMitWahlscheinAnzahlGleichWaehlerMitWahlscheinAnzahlenSummePlausi(stimmeingabeWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl,
                    stimmeingabeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, findStimmeingabePlausiKonfigurationOrDefaults(context,
                        StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_GLEICH_WAEHLER_MIT_WAHLSCHEIN_ANZAHLEN_SUMME, StimmeingabePlausiFehlerart.ERROR),
                    getMessages()),
                context);
          }
        });
      });
    });
  }

  /**
   * A.k.a. "Wahlbeteiligung am Ort"
   */
  protected void addPlausiWaehlerOhneWahlscheinBerechnetKleinerGleichWahlberechtigteOhneWahlschein(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    if (isWaehlerGesamtZuPlausibilisieren(model)) {
      Optional<Stimmeingabe> stimmeingabeWahlberechtigteOhneWahlschein = model.getWahlberechtigteOhneWahlscheinStimmeingabe();
      Optional<Stimmeingabe> stimmeingabeWaehlerMitWahlschein = model.getWaehlerMitWahlscheinStimmeingabe();

      if (stimmeingabeWahlberechtigteOhneWahlschein.isPresent() && stimmeingabeWaehlerMitWahlschein.isPresent()) {
        StimmeingabeKopfzahl kopfzahlWaehlerGesamt = model.getWaehlerGesamt();
        Stimmeingabe stimmeingabeWaehlerGesamt = kopfzahlWaehlerGesamt.getStimmeingabe();

        addPlausiKopfzahl(kopfzahlWaehlerGesamt,
            new WaehlerOhneWahlscheinBerechnetAnzahlKleinerGleichWahlberechtigteOhneWahlscheinAnzahlPlausi(stimmeingabeWaehlerGesamt, stimmeingabeWaehlerMitWahlschein.get(),
                stimmeingabeWahlberechtigteOhneWahlschein.get(), findStimmeingabePlausiKonfigurationOrDefaults(context,
                    StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL, StimmeingabePlausiFehlerart.ERROR),
                getMessages()),
            context);
      }
    }
  }

  /**
   * A.k.a. "Wahlbeteiligung am Ort"
   */
  protected void addPlausiWaehlerOhneWahlscheinBerechnetKleinerWahlberechtigteOhneWahlscheinAnteil(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context,
      double anteilWahlberechtigtenOhneWahlschein) {
    if (isWaehlerGesamtZuPlausibilisieren(model)) {
      Optional<Stimmeingabe> stimmeingabeWahlberechtigteOhneWahlschein = model.getWahlberechtigteOhneWahlscheinStimmeingabe();
      Optional<Stimmeingabe> stimmeingabeWaehlerMitWahlschein = model.getWaehlerMitWahlscheinStimmeingabe();

      if (stimmeingabeWahlberechtigteOhneWahlschein.isPresent() && stimmeingabeWaehlerMitWahlschein.isPresent()) {
        StimmeingabeKopfzahl kopfzahlWaehlerGesamt = model.getWaehlerGesamt();
        Stimmeingabe stimmeingabeWaehlerGesamt = kopfzahlWaehlerGesamt.getStimmeingabe();

        addPlausiKopfzahl(kopfzahlWaehlerGesamt, new WaehlerOhneWahlscheinBerechnetAnzahlKleinerWahlberechtigteOhneWahlscheinAnzahlAnteilPlausi(stimmeingabeWaehlerGesamt,
            stimmeingabeWaehlerMitWahlschein.get(), stimmeingabeWahlberechtigteOhneWahlschein.get(),
            findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_ANTEIL,
                StimmeingabePlausiFehlerart.WARNING, new WaehlerOhneWahlscheinAnzahlBerechnetComparedToStimmeingabeAnzahlAnteilPlausiCustomData(anteilWahlberechtigtenOhneWahlschein)),
            getMessages()), context);
      }
    }
  }

  protected void addUrnenwahlbezirkWahlbeteiligungGesamtPlausis(double anteilWahlberechtigteGesamtMin, double anteilWahlberechtigteGesamtMax, StimmeingabeKopfzahlErfassenModel model,
      StimmeingabePlausiBuilderContext context) {
    if (isWaehlerGesamtZuPlausibilisieren(model)) {
      model.getWahlberechtigteGesamtStimmeingabe().ifPresent(stimmeingabeWahlberechtigteGesamt -> {
        StimmeingabeKopfzahl waehlerGesamt = model.getWaehlerGesamt();

        // WASDE-1329
        addPlausiKopfzahl(waehlerGesamt,
            new WaehlerGesamtAnzahlGroesserWahlberechtigteGesamtAnzahlAnteilPlausi(stimmeingabeWahlberechtigteGesamt,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GROESSER_WAHLBERECHTIGTE_GESAMT_ANZAHL_ANTEIL, StimmeingabePlausiFehlerart.WARNING,
                    new StimmeingabeAnzahlComparedToStimmeingabeAnzahlAnteilPlausiCustomData(anteilWahlberechtigteGesamtMin)),
                getMessages()),
            context);
        addPlausiKopfzahl(waehlerGesamt,
            new WaehlerGesamtAnzahlKleinerWahlberechtigteGesamtAnzahlAnteilPlausi(stimmeingabeWahlberechtigteGesamt,
                findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_KLEINER_WAHLBERECHTIGTE_GESAMT_ANZAHL_ANTEIL, StimmeingabePlausiFehlerart.WARNING,
                    new StimmeingabeAnzahlComparedToStimmeingabeAnzahlAnteilPlausiCustomData(anteilWahlberechtigteGesamtMax)),
                getMessages()),
            context);
      });
    }
  }

  protected void addUrnenwahlbezirkWahlberechtigteGesamtGroesserNullPlausi(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    addUrnenwahlbezirkWahlberechtigteGesamtGroesserNullPlausi(model, context, StimmeingabePlausiFehlerart.ERROR);
  }

  protected void addUrnenwahlbezirkWahlberechtigteGesamtGroesserNullPlausi(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context,
      StimmeingabePlausiFehlerart stimmeingabePlausiFehlerart) {
    if (isWahlberechtigteGesamtZuPlausibilisieren(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteGesamt(), new StimmeingabeAnzahlGroesserAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_GESAMT,
          findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GROESSER_NULL, stimmeingabePlausiFehlerart), getMessages()), context);
    }
  }

  protected void addPlausiWaehlerGesamtGroesserNull(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    addPlausiWaehlerGesamtGroesserNull(model, context, StimmeingabePlausiFehlerart.WARNING);
  }

  protected void addPlausiWaehlerGesamtGroesserNull(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context, StimmeingabePlausiFehlerart stimmeingabePlausiFehlerart) {
    if (isWaehlerGesamtZuPlausibilisieren(model)) {
      StimmeingabeKopfzahl waehlerGesamt = model.getWaehlerGesamt();

      addPlausiKopfzahl(waehlerGesamt, new StimmeingabeAnzahlGroesserAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAEHLER_GESAMT,
          findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GROESSER_NULL, stimmeingabePlausiFehlerart), getMessages()), context);

      // ELECTMUC-936, ELECTKOM-271
      addPlausiKopfzahl(waehlerGesamt, new WaehlerGesamtAnzahlGroesserGleichAnzahlPlausi(findStimmeingabePlausiKonfigurationOrDefaults(context,
          StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GROESSER_GLEICH_MINDESTANZAHL, StimmeingabePlausiFehlerart.WARNING, new StimmeingabeAnzahlComparedToAnzahlPlausiCustomData(50L)), getMessages()),
          context);
    }
  }

  // ELECTMUC-853, cf. AbstractIterierenderStimmeingabePlausiBuilder#fillSonderstimmbezirkWahlberechtigtePlausis!
  // Cf. https://confluence.ivu.de/display/ELECTMUC/Wahl-Glossar#Wahl-Glossar-SonderwahlbezirkSonderwahlbezirk!
  protected void fillSonderstimmbezirkWahlberechtigtePlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    if (isWahlberechtigteOhneWahlscheinZuPlausibilisieren(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteOhneWahlschein(),
          new StimmeingabeAnzahlGleichAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN,
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_NULL, StimmeingabePlausiFehlerart.ERROR), getMessages()),
          context);
    }

    if (isWahlberechtigteMitWahlscheinZuPlausibilisieren(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteMitWahlschein(),
          new StimmeingabeAnzahlGleichAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_MIT_WAHLSCHEIN,
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_GLEICH_NULL, StimmeingabePlausiFehlerart.ERROR), getMessages()),
          context);
    }

    if (isWahlberechtigteNichtImWaehlerverzeichnisZuPlausibilisieren(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteNichtImWaehlerverzeichnis(),
          new StimmeingabeAnzahlGleichAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS,
              findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_GLEICH_NULL, StimmeingabePlausiFehlerart.ERROR),
              getMessages()),
          context);
    }

    if (isWahlberechtigteGesamtZuPlausibilisieren(model)) {
      addPlausiKopfzahl(model.getWahlberechtigteGesamt(), new StimmeingabeAnzahlGleichAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAHLBERECHTIGTE_GESAMT,
          findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_NULL, StimmeingabePlausiFehlerart.ERROR), getMessages()), context);
    }
  }

  // ELECTMUC-853, cf. AbstractIterierenderStimmeingabePlausiBuilder#fillSonderstimmbezirkWaehlerPlausis!
  // Cf. https://confluence.ivu.de/display/ELECTMUC/Wahl-Glossar#Wahl-Glossar-SonderwahlbezirkSonderwahlbezirk!
  protected void fillSonderstimmbezirkWaehlerPlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    if (isWaehlerOhneWahlscheinZuPlausibilisieren(model)) {
      addPlausiKopfzahl(model.getWaehlerOhneWahlschein(), new StimmeingabeAnzahlGleichAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_WAEHLER_OHNE_WAHLSCHEIN,
          findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_NULL, StimmeingabePlausiFehlerart.ERROR), getMessages()), context);
    }

    if (isWaehlerMitWahlscheinZuPlausibilisieren(model)) {
      model.getWaehlerGesamtStimmeingabe()
          .ifPresent(stimmeingabeWaehlerGesamt -> addPlausiKopfzahl(model.getWaehlerMitWahlschein(),
              new SonderstimmbezirkWaehlerMitWahlscheinAnzahlGleichWaehlerGesamtAnzahlPlausi(stimmeingabeWaehlerGesamt,
                  findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_GLEICH_WAEHLER_GESAMT_ANZAHL, StimmeingabePlausiFehlerart.ERROR),
                  getMessages()),
              context));
    }
  }

  @Override
  public void fillStimmeingabePlausis(Ergebniseingang ergebniseingang) {
    // Nichts zu tun, hier nur Kopfzahlen...
  }

  // NOTE, dass bei Anpassungen auch AbstractIterierenderStimmeingabePlausiBuilder#fillPruefkennzeichenWahlberechtigtePlausis angepasst werden muss!!!
  protected void fillPruefkennzeichenWahlberechtigtePlausis(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    model.getPruefkennzeichenWahlberechtigteStimmeingabe().ifPresent(stimmeingabe -> {
      Ergebniseingang ergebniseingang = model.getErgebniseingang();
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = model.getBezugGebiet();

      if (isPruefkennzeichenWahlberechtigteZuPlausibilisierenGegenAnzahlPruefkennzeichenWahlberechtigteAmGebiet(getStimmeingabeKopfzahlKonfiguration(), wahldurchfuehrungsgebiet,
          ergebniseingang.getErgebniseingangsart(), ergebniseingang.getErgebniseingangsherkunft())) {
        String pruefkennzeichen = extractRequiredPruefkennzeichenWahlberechtigteAmGebiet(wahldurchfuehrungsgebiet);

        stimmeingabe.addStimmeingabePlausi(new PruefkennzeichenWahlberechtigteErfasstPlausi(
            findStimmeingabePlausiKonfigurationOrDefaults(context, StimmeingabePlausiTyp.PRUEFKENNZEICHEN_WAHLBERECHTIGTE_ERFASST, StimmeingabePlausiFehlerart.ERROR), getMessages()));
        stimmeingabe
            .addStimmeingabePlausi(new PruefkennzeichenWahlberechtigteGleichPruefkennzeichenWahlberechtigteAmGebietPlausi(pruefkennzeichen, findStimmeingabePlausiKonfigurationOrDefaults(context,
                StimmeingabePlausiTyp.PRUEFKENNZEICHEN_WAHLBERECHTIGTE_GLEICH_PRUEFKENNZEICHEN_WAHLBERECHTIGTE_AM_GEBIET, StimmeingabePlausiFehlerart.ERROR), getMessages()));
      }
    });
  }

  protected static boolean isPruefkennzeichenWahlberechtigteZuPlausibilisierenGegenAnzahlPruefkennzeichenWahlberechtigteAmGebiet(StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart, Ergebniseingangsherkunft ergebniseingangsherkunft) {
    if (ergebniseingangsherkunft != null && ergebniseingangsherkunft.isBenutzerEingabe()) { // ELECT-11740, Prüfkennzeichen soll "manuelle" Erfassung auf falschem Gebiet verhindern...
      return isPruefkennzeichenWahlberechtigteAmGebietHinterlegt(wahldurchfuehrungsgebiet, ergebniseingangsart) && stimmeingabeKopfzahlKonfiguration
          .isPruefkennzeichenWahlberechtigteBeiStimmeingabeZuPlausibilisierenGegenPruefkennzeichenWahlberechtigteAmGebiet(wahldurchfuehrungsgebiet, ergebniseingangsart);
    }

    return false;
  }

  private static boolean isPruefkennzeichenWahlberechtigteAmGebietHinterlegt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    return wahldurchfuehrungsgebiet != null && wahldurchfuehrungsgebiet.hasPruefkennzeichen();
  }

  protected static String extractRequiredPruefkennzeichenWahlberechtigteAmGebiet(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return extractRequiredWertWahlberechtigteAmGebiet(wahldurchfuehrungsgebiet, Wahldurchfuehrungsgebiet::getPruefkennzeichen);
  }

  private boolean isWahlberechtigteOhneWahlscheinZuPlausibilisierenGegenWahlberechtigteOhneWahlscheinAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return isWahlberechtigteOhneWahlscheinZuPlausibilisierenGegenWahlberechtigteOhneWahlscheinAmGebiet(getStimmeingabeKopfzahlKonfiguration(), model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  private boolean isWahlberechtigteOhneWahlscheinZuPlausibilisierenGegenWahlberechtigteOhneWahlscheinAmGebiet(StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    return stimmeingabeKopfzahlKonfiguration.isWahlberechtigteOhneWahlscheinBeiStimmeingabeZuPlausibilisierenGegenWahlberechtigteOhneWahlscheinAmGebiet(wahldurchfuehrungsgebiet, ergebniseingangsart, getStimmeingabeKopfzahlKonfigurationContext());
  }

  private Long extractRequiredAnzahlWahlberechtigteOhneWahlscheinAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return extractRequiredAnzahlWahlberechtigteAmGebiet(model,
        getStimmeingabeKopfzahlKonfigurationContext(), getStimmeingabeKopfzahlKonfiguration()::extractWahlberechtigteOhneWahlscheinAmGebietHinterlegt);
  }

  private boolean isWahlberechtigteMitWahlscheinZuPlausibilisierenGegenWahlberechtigteMitWahlscheinAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return isWahlberechtigteMitWahlscheinZuPlausibilisierenGegenWahlberechtigteMitWahlscheinAmGebiet(getStimmeingabeKopfzahlKonfiguration(), model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  private boolean isWahlberechtigteMitWahlscheinZuPlausibilisierenGegenWahlberechtigteMitWahlscheinAmGebiet(StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    return stimmeingabeKopfzahlKonfiguration.isWahlberechtigteMitWahlscheinBeiStimmeingabeZuPlausibilisierenGegenWahlberechtigteMitWahlscheinAmGebiet(wahldurchfuehrungsgebiet, ergebniseingangsart, getStimmeingabeKopfzahlKonfigurationContext());
  }

  private Long extractRequiredAnzahlWahlberechtigteMitWahlscheinAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return extractRequiredAnzahlWahlberechtigteAmGebiet(model,
        getStimmeingabeKopfzahlKonfigurationContext(), getStimmeingabeKopfzahlKonfiguration()::extractWahlberechtigteMitWahlscheinAmGebietHinterlegt);
  }

  private boolean isWahlberechtigteNichtImWaehlerverzeichnisZuPlausibilisierenGegenWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return isWahlberechtigteNichtImWaehlerverzeichnisZuPlausibilisierenGegenWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(getStimmeingabeKopfzahlKonfiguration(), model.getBezugGebiet(),
        model.getErgebniseingangsart());
  }

  private boolean isWahlberechtigteNichtImWaehlerverzeichnisZuPlausibilisierenGegenWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    return stimmeingabeKopfzahlKonfiguration.isWahlberechtigteNichtImWaehlerverzeichnisBeiStimmeingabeZuPlausibilisierenGegenWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(wahldurchfuehrungsgebiet,
        ergebniseingangsart, getStimmeingabeKopfzahlKonfigurationContext());
  }

  private Long extractRequiredAnzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return extractRequiredAnzahlWahlberechtigteAmGebiet(model,
        getStimmeingabeKopfzahlKonfigurationContext(),
        getStimmeingabeKopfzahlKonfiguration()::extractWahlberechtigteNichtImWaehlerverzeichnisAmGebietHinterlegt);
  }

  private boolean isWahlberechtigteGesamtZuPlausibilisierenGegenWahlberechtigteGesamtAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return isWahlberechtigteGesamtZuPlausibilisierenGegenWahlberechtigteGesamtAmGebiet(getStimmeingabeKopfzahlKonfiguration(), model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  private boolean isWahlberechtigteGesamtZuPlausibilisierenGegenWahlberechtigteGesamtAmGebiet(StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    return stimmeingabeKopfzahlKonfiguration.isWahlberechtigteGesamtBeiStimmeingabeZuPlausibilisierenGegenWahlberechtigteGesamtAmGebiet(wahldurchfuehrungsgebiet, ergebniseingangsart, getStimmeingabeKopfzahlKonfigurationContext());
  }

  private Long extractRequiredAnzahlWahlberechtigteGesamtAmGebiet(StimmeingabeKopfzahlErfassenModel model) {
    return extractRequiredAnzahlWahlberechtigteAmGebiet(model, getStimmeingabeKopfzahlKonfigurationContext(),
        getStimmeingabeKopfzahlKonfiguration()::extractWahlberechtigteGesamtAmGebietHinterlegt);
  }

  private static <T> T extractRequiredAnzahlWahlberechtigteAmGebiet(StimmeingabeKopfzahlErfassenModel model, BiFunction<Wahldurchfuehrungsgebiet, Ergebniseingangsart, T> getter) {
    return extractRequiredAnzahlWahlberechtigteAmGebiet(model, wahldurchfuehrungsgebiet -> getter.apply(wahldurchfuehrungsgebiet, model.getErgebniseingangsart()));
  }

  private static <T> T extractRequiredAnzahlWahlberechtigteAmGebiet(StimmeingabeKopfzahlErfassenModel model,
                                                                    StimmeingabeKopfzahlKonfigurationContext context, TriFunction<Wahldurchfuehrungsgebiet,
        Ergebniseingangsart, StimmeingabeKopfzahlKonfigurationContext, T> getter) {
    return extractRequiredAnzahlWahlberechtigteAmGebiet(model, wahldurchfuehrungsgebiet -> getter.apply(wahldurchfuehrungsgebiet,
        model.getErgebniseingangsart(), context));
  }

  private static <T> T extractRequiredAnzahlWahlberechtigteAmGebiet(StimmeingabeKopfzahlErfassenModel model, Function<Wahldurchfuehrungsgebiet, T> getter) {
    return extractRequiredWertWahlberechtigteAmGebiet(model.getBezugGebiet(), getter);
  }

  private static <T> T extractRequiredWertWahlberechtigteAmGebiet(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Function<Wahldurchfuehrungsgebiet, T> getter) {
    if (wahldurchfuehrungsgebiet != null) {
      T result = getter.apply(wahldurchfuehrungsgebiet);

      if (result != null) {
        return result;
      }
    }

    throw new IllegalStateException("Am Wahldurchfuehrungsgebiet fehlt ein hinterlegter Wert [wahldurchfuehrungsgebiet = " + wahldurchfuehrungsgebiet + ", getter = " + getter + "]!");
  }
}
