package de.ivu.elect.business.ergebnis.control;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import de.ivu.elect.business.aufgabe.entity.Aufgabe;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.fremdkandidatur.entity.Fremdkandidatur;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.AbstractGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietReihenfolgeVorsortierer;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.internet.control.HashablePredicate;
import de.ivu.elect.business.internet.entity.InternetSeite;
import de.ivu.elect.business.kandidatenportal.entity.WvpWarnung;
import de.ivu.elect.business.kandidatur.control.StimmzettelFormatter;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.niederschrift.entity.Niederschrift;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfiguration;
import de.ivu.elect.business.pruefung.boundary.ZulassungPruefer;
import de.ivu.elect.business.staat.entity.StaatenGruppe;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescription;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescriptionResolver;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangByZeitstempelComparator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeDocumentErgebnisDaten;
import de.ivu.elect.business.stimmeingabe.entity.AbstractErgebnisGueltigerEingang;
import de.ivu.elect.business.stimmeingabe.entity.AbstractErgebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebnisGueltigerEingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmen.control.ErgebnisStimmenFilter;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenStimmzettelPositionBerechnung;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.veranstaltung.entity.Veranstaltung;
import de.ivu.elect.business.wahl.control.GewaehlteLoader;
import de.ivu.elect.business.wahl.control.Loader;
import de.ivu.elect.business.wahl.control.ParameterLoader;
import de.ivu.elect.business.wahl.control.SitzeLoader;
import de.ivu.elect.business.wahl.control.StimmenLoader;
import de.ivu.elect.business.wahl.control.Wahldaten;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlausschuss.entity.WahlausschussEntschaedigung;
import de.ivu.elect.business.wahlausschuss.entity.Wahlausschussmitglied;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungAuswertung;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungVerlauf;
import de.ivu.elect.business.wahlrechtspruefung.entity.Pruefkriterium;
import de.ivu.elect.business.wahlrechtspruefung.entity.Wahlrechtspruefung;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;


import static java.util.stream.Collectors.toMap;

public class StimmergebnisDaten extends Wahldaten<Wahldurchfuehrung, Wahldurchfuehrungsgebiet> implements StimmergebnisDatenInterface, StimmeingabeDocumentErgebnisDaten<Wahldurchfuehrungsgebiet> {

  private static final long serialVersionUID = 1L;

  private static final int MAX_CACHE_SIZE_GEBIETSGRUPPIERUNG = 100_000; //ELECT-12242 willkürlich

  private final Ergebnis ergebnis;

  private final Ergebnis ergebnisVorperiode;

  private final StimmenPool stimmen;

  private final StimmenKumulierer stimmenKumulierer;

  private final Loader<Gebiet> erfassteGebieteLoader;

  private final ErgebnisStimmenFilter ergebnisStimmenFilter;

  private final Loader<Niederschrift> niederschriftLoader;

  private List<Niederschrift> nierderschriften;

  private final BestimmeStimmenPolitischeGruppen listenstimmenAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenVorperiodeBestimmeStimmenPolitischeGruppen = new ListenstimmenVorperiodeBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenVergleichBestimmeStimmenPolitischeGruppen = new ListenstimmenVergleichBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen direktstimmenAktuellBestimmeStimmenPolitischeGruppen = new DirektstimmenAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen direktstimmenVorperiodeBestimmeStimmenPolitischeGruppen = new DirektstimmenVorperiodeBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen direktstimmenVergleichBestimmeStimmenPolitischeGruppen = new DirektstimmenVergleichBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenAbzuziehenAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenAbzuziehenAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenEinzelnerWahlvorschlagUnveraendertAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenEinzelnerWahlvorschlagUnveraendertAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenKeinEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenKeinEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenPolitischeGruppen listenstimmenKeinEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen = new ListenstimmenKeinEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen();

  private final BestimmeStimmenSystemgruppen direktstimmenAktuellBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.DIREKTSTIMME,
      this::getStimmen);

  private final BestimmeStimmenSystemgruppen listenstimmenAktuellBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.LISTENSTIMME,
      this::getStimmen);

  private final BestimmeStimmenSystemgruppen gesamtstimmenAktuellBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.GESAMT,
      this::getStimmen);

  private final Map<GebietUndStimmabgabeart, List<Wahldurchfuehrungsgebiet>> erfassungsgebieteUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, List<Wahldurchfuehrungsgebiet>> erfassungsgebieteLautGebietsartUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> wahlberechtigteGesamtVorperiodeFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> wahlberechtigteOhneWahlscheinVorperiodeFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> wahlberechtigteMitWahlscheinVorperiodeFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> wahlberechtigteNichtImWaehlerverzeichnisVorperiodeFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteGesamtFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteGesamtAusgezaehltFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteOhneWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteMitWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> wahlberechtigteNichtImWaehlerverzeichnisAusgezaehltFuerErfassungsgebietUnterhalbVon = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerOhneWahlschein = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerMitWahlschein = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerMitVollmacht = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerGesamt = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> wahlberechtigteGesamtVorperiode = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerOhneWahlscheinVorperiode = new HashMap<>();

  private final Map<Wahldurchfuehrungsgebiet, Long> waehlerMitWahlscheinVorperiode = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> waehlerAktuellGesamt = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> waehlerAktuellOhneWahlschein = new HashMap<>();

  private final Map<GebietUndStimmabgabeart, Long> waehlerGesamtVorperiode = new HashMap<>();

  /**
   * Der Schlüssel dieser Map muss ein GebietUndStimmabgabeart ohne Gebiet sein (also nur dessen Filter-Eigenschaft), weil ein StimmenPool jeweils die Stimmen-Daten für alle Gebiete enthält, die auf
   * gleiche Weise gefiltert wurden.
   */
  private final Map<GebietUndStimmabgabeart, StimmenPool> stimmenVorperiodeAlsPoolNachStimmabgabeart = new HashMap<>();

  /**
   * Der Schlüssel dieser Map muss ein GebietUndStimmabgabeart ohne Gebiet sein (also nur dessen Filter-Eigenschaft), weil ein StimmenPool jeweils die Stimmen-Daten für alle Gebiete enthält, die auf
   * gleiche Weise gefiltert wurden.
   */
  private final Map<GebietUndStimmabgabeart, StimmenPool> stimmenVergleichAlsPoolNachStimmabgabeart = new HashMap<>();

  private Map<Gebiet, Map<Stimmenart, Long>> anzahlGebieteProStimmenartAusgezaehlt;

  private StimmenPool stimmenVorperiodeAlsPool;

  private final BestimmeStimmenSystemgruppen listenstimmenVorperiodeBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.LISTENSTIMME,
      this::getStimmenVorperiodeAlsPool);

  private final BestimmeStimmenSystemgruppen direktstimmenVorperiodeBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.DIREKTSTIMME,
      this::getStimmenVorperiodeAlsPool);

  private final BestimmeStimmenSystemgruppen gesamtstimmenVorperiodeBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.GESAMT,
      this::getStimmenVorperiodeAlsPool);

  private StimmenPool stimmenVergleichAlsPool;

  private final BestimmeStimmenSystemgruppen listenstimmenVergleichBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.LISTENSTIMME,
      this::getStimmenVergleichAlsPool);

  private final BestimmeStimmenSystemgruppen direktstimmenVergleichBestimmeStimmenSystemgruppen = new BestimmeStimmenSystemgruppen(Stimmenart.DIREKTSTIMME,
      this::getStimmenVergleichAlsPool);

  private Set<Gebiet> erfassteGebiete = null;

  private Set<Wahldurchfuehrungsgebiet> ausbleibendeGebiete = null;

  private transient ErgebnisDatenQuorum<Wahldurchfuehrungsgebiet> ergebnisDatenQuorumWahlberechtigte;

  private transient AbstractErgebnisDatenWahlberechtigte ergebnisDatenWahlberechtigte;

  private transient AbstractErgebnisDatenWahlberechtigte ergebnisDatenWahlberechtigteVorperiode;

  private transient ErgebnisDatenWaehler ergebnisDatenWaehler;

  private transient ErgebnisDatenGueltige ergebnisDatenGueltige;

  private transient ErgebnisDatenStimmzettel ergebnisDatenStimmzettel;

  private transient ErgebnisDatenListenkandidaturen ergebnisDatenListenkandidaturen;

  private transient ErgebnisDatenDirektkandidaturen ergebnisDatenDirektkandidaturen;

  private transient ErgebnisDatenGesamtstimmen ergebnisDatenGesamtstimmen;

  private transient ErgebnisDatenReferendumOptionen ergebnisDatenReferendumOptionen;

  private transient StimmeingabeErfassenDescription stimmeingabeErfassenDescription;

  private final ParameterLoader<Ergebniseingang, Ergebniseingangsart> letzterErgebniseingangLoader;

  private final ParameterLoader<ErgebnisGueltigerEingang, Ergebniseingangsart> ergebnisGueltigerEingangLoader;

  private final Loader<WahlbeteiligungAuswertung> wahlbeteiligungAuswertungenLoader;

  private final StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver;

  public StimmergebnisDaten(StimmergebnisDaten stimmergebnisDaten) {
    this(stimmergebnisDaten.getWahl(), stimmergebnisDaten.getGebieteLoader(), stimmergebnisDaten.getGruppenLoader(),
        stimmergebnisDaten.getGruppenImGebietLoader(), stimmergebnisDaten.getWahlvorschlaegeLoader(), stimmergebnisDaten.getKandidaturenLoader(),
        stimmergebnisDaten.getPersonenVerknuepfungenLoader(), stimmergebnisDaten.getStimmenLoader(), stimmergebnisDaten.getGewaehlteLoader(),
        stimmergebnisDaten.getSitzeLoader(), stimmergebnisDaten.getStaatenGruppenLoader(), stimmergebnisDaten.getGebietsgruppierungenLoader(),
        stimmergebnisDaten.getWahlvorschlagsPersonenLoader(), stimmergebnisDaten.getWahlausschussmitgliederLoader(),
        stimmergebnisDaten.getWahlausschussEntschaedigungenLoader(), stimmergebnisDaten.getErgebnis(), stimmergebnisDaten.getErgebnisVorperiode(),
        stimmergebnisDaten.getStimmen(), stimmergebnisDaten.getSystemGruppeEigenschaften(), stimmergebnisDaten.getStimmenKumulierer(),
        stimmergebnisDaten.getWahlParameterLoader(), stimmergebnisDaten.getZulassungPruefer(), stimmergebnisDaten.getStimmzettelFormatter(),
        stimmergebnisDaten.getEntityAufStimmzettelDatenStimmzettelPositionBerechnung(), stimmergebnisDaten.getGruppeImGebietReihenfolgeVorsortierer(),
        stimmergebnisDaten.getGebietPolygonPointsLoader(), stimmergebnisDaten.getFremdkandidaturenLoader(), stimmergebnisDaten.getInternetSeitenLoader(),
        stimmergebnisDaten.getErfassteGebieteLoader(), stimmergebnisDaten.getErgebnisStimmenFilter(), stimmergebnisDaten.getLetzterErgebniseingangLoader(),
        stimmergebnisDaten.getErgebnisGueltigerEingangLoader(), stimmergebnisDaten.getWahlbeteiligungVerlaeufeLoader(),
        stimmergebnisDaten.getWahlbeteiligungAuswertungenLoader(), stimmergebnisDaten.getStimmeingabeErfassenDescriptionResolver(),
        stimmergebnisDaten.getStimmeingabePlausiKonfigurationenLoader(), stimmergebnisDaten.getVeranstaltungenLoader(),
        stimmergebnisDaten.getStimmzettelLoader(), stimmergebnisDaten.getStimmzettelKopfdatenLoader(), stimmergebnisDaten.getPruefkriterienLoader(),
        stimmergebnisDaten.getWahlrechtspruefungenLoader(), stimmergebnisDaten.getWvpWarnungenLoader(),
        stimmergebnisDaten.getPersonPruefungKonfigurationLoader(), stimmergebnisDaten.getNiederschriftLoader(), stimmergebnisDaten.getAufgabenLoader());
  }

  public StimmergebnisDaten(Wahldaten<Wahldurchfuehrung, Wahldurchfuehrungsgebiet> wahldaten, Ergebnis ergebnis, Ergebnis ergebnisVorperiode, StimmenPool stimmen, StimmenKumulierer stimmenKumulierer, Loader<Gebiet> erfassteGebieteLoader, ErgebnisStimmenFilter ergebnisStimmenFilter, ParameterLoader<Ergebniseingang, Ergebniseingangsart> letzterErgebniseingangLoader, ParameterLoader<ErgebnisGueltigerEingang, Ergebniseingangsart> ergebnisGueltigerEingangLoader, Loader<WahlbeteiligungAuswertung> wahlbeteiligungAuswertungenLoader, StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver, Loader<Niederschrift> niederschriftLoader) {
    super(wahldaten);

    this.ergebnis = ergebnis;
    this.ergebnisVorperiode = ergebnisVorperiode;
    this.stimmen = stimmen;
    this.stimmenKumulierer = stimmenKumulierer;
    this.erfassteGebieteLoader = erfassteGebieteLoader;
    this.ergebnisStimmenFilter = ergebnisStimmenFilter;
    this.letzterErgebniseingangLoader = letzterErgebniseingangLoader;
    this.ergebnisGueltigerEingangLoader = ergebnisGueltigerEingangLoader;
    this.wahlbeteiligungAuswertungenLoader = wahlbeteiligungAuswertungenLoader;
    this.stimmeingabeErfassenDescriptionResolver = stimmeingabeErfassenDescriptionResolver;
    this.niederschriftLoader = niederschriftLoader;

  }

  public StimmergebnisDaten(Wahldurchfuehrung wahl, Loader<Wahldurchfuehrungsgebiet> gebieteLoader, Loader<Gruppe> gruppenLoader, Loader<GruppeImGebiet> gruppenImGebietLoader, Loader<Wahlvorschlag> wahlvorschlaegeLoader, Loader<Kandidatur> kandidaturenLoader, Loader<PersonenVerknuepfung> personenVerknuepfungenLoader, StimmenLoader stimmenLoader, GewaehlteLoader gewaehlteLoader, SitzeLoader sitzeLoader, Loader<StaatenGruppe> staatenGruppenLoader, Loader<Gebietsgruppierung> gebietsgruppierungenLoader, Loader<WahlvorschlagsPerson> wahlvorschlagsPersonenLoader, Loader<Wahlausschussmitglied> wahlausschussmitgliederLoader, Loader<WahlausschussEntschaedigung> wahlausschussEntschaedigungenLoader, Ergebnis ergebnis, Ergebnis ergebnisVorperiode, StimmenPool stimmen, SystemGruppeEigenschaften systemGruppeEigenschaften, StimmenKumulierer stimmenKumulierer, AbstractWahlParameterLoader wahlParameterLoader, ZulassungPruefer zulassungPruefer, StimmzettelFormatter stimmzettelFormatter, EntityAufStimmzettelDatenStimmzettelPositionBerechnung entityAufStimmzettelDatenStimmzettelPositionBerechnung, GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer, Loader<GebietPolygonPoint> gebietPolygonPointsLoader, Loader<Fremdkandidatur> fremdkandidaturenLoader, Loader<InternetSeite> internetSeitenLoader, Loader<Gebiet> erfassteGebieteLoader, ErgebnisStimmenFilter ergebnisStimmenFilter, ParameterLoader<Ergebniseingang, Ergebniseingangsart> letzterErgebniseingangLoader, ParameterLoader<ErgebnisGueltigerEingang, Ergebniseingangsart> ergebnisGueltigerEingangLoader, Loader<WahlbeteiligungVerlauf> wahlbeteiligungVerlaeufeLoader, Loader<WahlbeteiligungAuswertung> wahlbeteiligungAuswertungenLoader, StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver, Loader<StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationenLoader, Loader<Veranstaltung> veranstaltungenLoader, Loader<Stimmzettel> stimmzettelLoader, Loader<StimmzettelKopfdatum> stimmzettelKopfdatenLoader, Loader<Pruefkriterium> pruefkriterienLoader, Loader<Wahlrechtspruefung> wahlrechtspruefungenLoader, Loader<WvpWarnung> wvpWarnungenLoader, Loader<PersonPruefungKonfiguration> personPruefungKonfigurationLoader, Loader<Niederschrift> niederschriftLoader, Loader<Aufgabe> aufgabeLoader) {
    super(wahl, gebieteLoader, gruppenLoader, gruppenImGebietLoader, wahlvorschlaegeLoader, kandidaturenLoader, personenVerknuepfungenLoader, stimmenLoader,
        gewaehlteLoader, sitzeLoader, wahlvorschlagsPersonenLoader, wahlausschussmitgliederLoader, wahlausschussEntschaedigungenLoader, staatenGruppenLoader,
        gebietsgruppierungenLoader, systemGruppeEigenschaften, wahlParameterLoader, zulassungPruefer, stimmzettelFormatter,
        entityAufStimmzettelDatenStimmzettelPositionBerechnung, gruppeImGebietReihenfolgeVorsortierer, gebietPolygonPointsLoader, fremdkandidaturenLoader,
        internetSeitenLoader, wahlbeteiligungVerlaeufeLoader, stimmeingabePlausiKonfigurationenLoader, veranstaltungenLoader, stimmzettelLoader,
        stimmzettelKopfdatenLoader, pruefkriterienLoader, wahlrechtspruefungenLoader, wvpWarnungenLoader, personPruefungKonfigurationLoader, aufgabeLoader);

    this.ergebnis = ergebnis;
    this.ergebnisVorperiode = ergebnisVorperiode;
    this.stimmen = stimmen;
    this.stimmenKumulierer = stimmenKumulierer;
    this.erfassteGebieteLoader = erfassteGebieteLoader;
    this.ergebnisStimmenFilter = ergebnisStimmenFilter;
    this.letzterErgebniseingangLoader = letzterErgebniseingangLoader;
    this.ergebnisGueltigerEingangLoader = ergebnisGueltigerEingangLoader;
    this.wahlbeteiligungAuswertungenLoader = wahlbeteiligungAuswertungenLoader;
    this.stimmeingabeErfassenDescriptionResolver = stimmeingabeErfassenDescriptionResolver;
    this.niederschriftLoader = niederschriftLoader;
  }

  List<WahlbeteiligungAuswertung> wahlbeteiligungAuswertungen;

  private final Map<Gruppe, Set<Gebiet>> zweitstimmenVorperiodProGruppeUndGebiet = new HashMap<>();

  public boolean isZweitstimmenVorperiodeVorhanden(Gruppe gruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return zweitstimmenVorperiodProGruppeUndGebiet.computeIfAbsent(gruppe,
        x -> bestimmeGebiete(getStimmenVorperiodeAlsPool().getListenstimmenDerGruppe(gruppe))).contains(wahldurchfuehrungsgebiet);
  }

  private final Map<Gruppe, Set<Gebiet>> erststimmenVorperiodProGruppeUndGebiet = new HashMap<>();

  public boolean isErststimmenVorperiodeVorhanden(Gruppe gruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return erststimmenVorperiodProGruppeUndGebiet.computeIfAbsent(gruppe, x -> bestimmeGebiete(getStimmenVorperiodeAlsPool().getDirektstimmen(gruppe)))
        .contains(wahldurchfuehrungsgebiet);
  }

  @Nonnull
  private Set<Gebiet> bestimmeGebiete(Collection<Stimmen> stimmenDerGruppe) {
    Set<Gebiet> stimmenProGebietVorhanden = new HashSet<>();
    for (var s : stimmenDerGruppe) {
      fuelleSet(stimmenProGebietVorhanden, s.getGebiet());
    }
    return stimmenProGebietVorhanden;
  }

  private void fuelleSet(Set<Gebiet> stimmenProGebietVorhanden, Gebiet gebiet) {
    if (gebiet != null) {
      if (stimmenProGebietVorhanden.add(gebiet)) {
        fuelleSet(stimmenProGebietVorhanden, gebiet.getUebergeordnetesGebiet());
      }
    }
  }

  public StimmenPool getStimmenVorperiodeAlsPool() {
    if (stimmenVorperiodeAlsPool == null) {
      stimmenVorperiodeAlsPool = StimmenPool.createStimmenPoolFuerAlleStimmen(getStimmenVorperiode(), LasseAllesDurchStimmenFilter.INSTANCE);
    }

    return stimmenVorperiodeAlsPool;
  }

  public StimmenPool getStimmenVorperiodeAlsPool(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var schluesselOhneGebiet = gebietUndStimmabgabeart.alsSchluesselOhneGebiet();
    var stimmenPool = stimmenVorperiodeAlsPoolNachStimmabgabeart.get(schluesselOhneGebiet);

    if (stimmenPool == null) {
      var filterGebiete = schluesselOhneGebiet.filterGebiete(this::getErfassteGebiete);
      stimmenPool = StimmenPool.createStimmenPoolFuerAlleStimmen(getStimmenVorperiode(),
          StimmenKumulierer.createStimmenFilter(filterGebiete, ergebnisStimmenFilter));
      stimmenVorperiodeAlsPoolNachStimmabgabeart.put(schluesselOhneGebiet, stimmenPool);
    }

    return stimmenPool;
  }

  public StimmenPool getStimmenVergleichAlsPool() {
    if (stimmenVergleichAlsPool == null) {
      stimmenVergleichAlsPool = StimmenPool.createStimmenPoolFuerAlleStimmen(getStimmenVergleich(), LasseAllesDurchStimmenFilter.INSTANCE);
    }

    return stimmenVergleichAlsPool;
  }

  public StimmenPool getStimmenVergleichAlsPool(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var schluesselOhneGebiet = gebietUndStimmabgabeart.alsSchluesselOhneGebiet();
    var stimmenPool = stimmenVergleichAlsPoolNachStimmabgabeart.get(schluesselOhneGebiet);

    if (stimmenPool == null) {
      var filterGebiete = schluesselOhneGebiet.filterGebiete(this::getErfassteGebiete);
      stimmenPool = StimmenPool.createStimmenPoolFuerAlleStimmen(getStimmenVergleich(),
          StimmenKumulierer.createStimmenFilter(filterGebiete, ergebnisStimmenFilter));
      stimmenVergleichAlsPoolNachStimmabgabeart.put(schluesselOhneGebiet, stimmenPool);
    }

    return stimmenPool;
  }

  public Ergebnis getErgebnis() {
    return ergebnis;
  }

  public Ergebnis getErgebnisVorperiode() {
    return ergebnisVorperiode;
  }

  public StimmenPool getStimmen() {
    return stimmen;
  }

  public StimmenKumulierer getStimmenKumulierer() {
    return stimmenKumulierer;
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenAktuellGesamt(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenAktuell(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuell(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getZweitstimmenAktuell(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuell(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart));
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenAktuell(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getZweitstimmenAktuell(gebietUndStimmabgabeart).get(politischeGruppe);
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenAktuell(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuell(liste.getGruppe(), wahldurchfuehrungsgebiet, stimmabgabeart);
  }

  public Long getZweitstimmenAktuellGesamt(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenAktuellGesamt(liste.getGruppe(), wahldurchfuehrungsgebiet);
  }

  @Override
  public Map<PolitischeGruppe, Long> getZweitstimmenAktuellGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenAktuell(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Override
  public Map<PolitischeGruppe, Long> getZweitstimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuell(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * @param wahldurchfuehrungsgebiet schränkt die Summation auf Erfassungsgebiete ein, die zu wahldurchfuehrungsgebiet gehören (also wahldurchfuehrungsgebiet selbst und die direkten und indirekten Untergebiete)
   * @param gebietsgruppierung       schränkt die Summation auf Erfassungsgebiete ein, die zu dieser Gebietsgruppierung gehören (Zugehörigkeit wird auf Untergebiete vererbt)
   * @param stimmabgabeart           schränkt die Summation auf Erfassungsgebiete ein, die zu dieser Stimmabgabeart passen.
   * @return Summen der Stimmen pro Partei. Summiert wird über alle Erfassungsgebiete unterhalb von wahldurchfuehrungsgebiet, wobei diese ggf. durch stimmabgabeart und gebietsgruppierung weiter
   * eingeschränkt werden.
   */
  public Map<PolitischeGruppe, Long> getZweitstimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuell(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart));
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  @Override
  public Map<Gruppe, Long> getZweitstimmenAktuellSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenAktuellBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
  }

  @Override
  public Map<PolitischeGruppe, Long> getGesamtStimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErgebnisDatenGesamtstimmen().getGesamtStimmenAktuell(wahldurchfuehrungsgebiet, stimmabgabeart);
  }

  @Override
  public Map<Gruppe, Long> getGesamtstimmenAktuellSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var gesamtStimmen = gesamtstimmenAktuellBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
    return bestimmeSummeErstZweit(gesamtStimmen, getErststimmenAktuellSystemgruppen(gebietUndStimmabgabeart),
        getZweitstimmenAktuellSystemgruppen(gebietUndStimmabgabeart));
  }

  @Override
  public Map<PolitischeGruppe, Long> getGesamtStimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErgebnisDatenGesamtstimmen().getGesamtStimmenVorperiode(wahldurchfuehrungsgebiet, stimmabgabeart);
  }

  @Override
  public Long getGesamtStimmenGueltigeVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErgebnisDatenGesamtstimmen().getGesamtStimmenGueltigeVorperiode(wahldurchfuehrungsgebiet, stimmabgabeart);
  }

  @Override
  public Map<Gruppe, Long> getGesamtstimmenVorperiodeSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var gesamtStimmen = gesamtstimmenVorperiodeBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
    return bestimmeSummeErstZweit(gesamtStimmen, getErststimmenVorperiodeSystemgruppen(gebietUndStimmabgabeart),
        getZweitstimmenVorperiodeSystemgruppen(gebietUndStimmabgabeart));
  }

  private Map<Gruppe, Long> bestimmeSummeErstZweit(Map<Gruppe, Long> gesamtstimmen, Map<Gruppe, Long> direktstimmen, Map<Gruppe, Long> listenstimmen) {
    Set<Gruppe> gruppen = new HashSet<>();
    gruppen.addAll(gesamtstimmen.keySet());
    gruppen.addAll(direktstimmen.keySet());
    gruppen.addAll(listenstimmen.keySet());

    Map<Gruppe, Long> result = new HashMap<>(gruppen.size() * 2 + 1);
    for (var gruppe : gruppen) {
      result.put(gruppe, add(gesamtstimmen.get(gruppe), direktstimmen.get(gruppe), listenstimmen.get(gruppe)));
    }
    return result;
  }

  private Long add(Long gesamtstimmen, Long erststimme, Long zweitstimme) {
    if (gesamtstimmen != null && gesamtstimmen > 0L) {
      return gesamtstimmen;
    }
    if (erststimme == null && zweitstimme == null) {
      return gesamtstimmen;
    }
    if (erststimme == null) {
      return zweitstimme;
    }
    if (zweitstimme == null) {
      return erststimme;
    }
    return erststimme + zweitstimme;
  }

  @Nullable
  public Long getZweitstimmenVorperiodeGesamt(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenVorperiode(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenVorperiode(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenVorperiode(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenVorperiode(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getZweitstimmenVorperiode(gebietUndStimmabgabeart).get(politischeGruppe);
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenVorperiode(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getZweitstimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten)).get(politischeGruppe);
  }

  @Nullable //ELECT-8913 null wenn kein Wahlvorschlag
  public Long getZweitstimmenVorperiode(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart)).get(politischeGruppe);
  }

  @Override
  public Map<Gruppe, Long> getZweitstimmenVorperiodeSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenVorperiodeBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenVorperiodeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenVorperiode(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Override
  public Map<PolitischeGruppe, Long> getZweitstimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenVorperiode(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenVorperiodeBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Map<Gruppe, Long> getZweitstimmenVergleichSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenVergleichBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenVergleich(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenVergleichBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeGueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenVorperiodeGueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Override
  @Nullable
  public Long getErststimmenVorperiodeGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenVorperiodeGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getErststimmenVorperiodeGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeGueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErststimmenVorperiodeSystemgruppen(gebietUndStimmabgabeart).get(getGruppeGueltige());
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeUngueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenVorperiodeUngueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenVorperiodeUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getErststimmenVorperiodeUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenVorperiodeUngueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErststimmenVorperiodeSystemgruppen(gebietUndStimmabgabeart).get(getGruppeUnGueltige());
  }

  public Map<Gruppe, Long> getErststimmenVergleichSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return direktstimmenVergleichBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getErststimmenVergleich(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return direktstimmenVergleichBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeGueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenVorperiodeGueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Override
  @Nullable
  public Long getZweitstimmenVorperiodeGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenVorperiodeGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getZweitstimmenVorperiodeGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeGueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getZweitstimmenVorperiodeSystemgruppen(gebietUndStimmabgabeart).get(getGruppeGueltige());
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeUngueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenVorperiodeUngueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenVorperiodeUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getZweitstimmenVorperiodeUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenVorperiodeUngueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getZweitstimmenVorperiodeSystemgruppen(gebietUndStimmabgabeart).get(getGruppeUnGueltige());
  }

  @Nullable
  public Long getErststimmenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenAktuell(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  @Nullable
  public Long getErststimmenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getErststimmenAktuell(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  @Nullable
  public Long getErststimmenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getErststimmenAktuell(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart));
  }

  @Nullable
  public Long getErststimmenAktuell(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErststimmenAktuell(gebietUndStimmabgabeart).get(politischeGruppe);
  }

  public Long getErststimmenAktuellGesamt(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenAktuell(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Override
  public Map<PolitischeGruppe, Long> getErststimmenAktuellGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenAktuell(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Override
  public Map<PolitischeGruppe, Long> getErststimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenAktuell(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<PolitischeGruppe, Long> getErststimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getErststimmenAktuell(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart));
  }

  public Map<PolitischeGruppe, Long> getErststimmenAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return direktstimmenAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  @Override
  public Map<Gruppe, Long> getErststimmenAktuellSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return direktstimmenAktuellBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
  }

  @Nullable
  public Long getErststimmenVorperiodeGesamt(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenVorperiode(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Nullable // ELECT-8913 null wenn kein Wahlvorschlag
  public Long getErststimmenVorperiode(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenVorperiode(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  @Nullable // ELECT-8913 null wenn kein Wahlvorschlag
  public Long getErststimmenVorperiode(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErststimmenVorperiode(gebietUndStimmabgabeart).get(politischeGruppe);
  }

  @Nullable // ELECT-8913 null wenn kein Wahlvorschlag
  public Long getErststimmenVorperiode(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getErststimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten)).get(politischeGruppe);
  }

  @Nullable // ELECT-8913 null wenn kein Wahlvorschlag
  public Long getErststimmenVorperiode(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getErststimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart)).get(politischeGruppe);
  }

  public Map<PolitischeGruppe, Long> getErststimmenVorperiodeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenVorperiode(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  @Override
  public Map<PolitischeGruppe, Long> getErststimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<PolitischeGruppe, Long> getErststimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return getErststimmenVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart));
  }

  public Map<PolitischeGruppe, Long> getErststimmenVorperiode(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return direktstimmenVorperiodeBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  @Override
  public Map<Gruppe, Long> getErststimmenVorperiodeSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return direktstimmenVorperiodeBestimmeStimmenSystemgruppen.getStimmenSystemgruppen(gebietUndStimmabgabeart);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellGueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenAktuellGueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenAktuellGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getErststimmenAktuellGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellGueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErststimmenAktuellSystemgruppen(gebietUndStimmabgabeart).get(getGruppeGueltige());
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellUngueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErststimmenAktuellUngueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErststimmenAktuellUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getErststimmenAktuellUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getErststimmenAktuellUngueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErststimmenAktuellSystemgruppen(gebietUndStimmabgabeart).get(getGruppeUnGueltige());
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellGueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenAktuellGueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuellGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getZweitstimmenAktuellGueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfGueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellGueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getZweitstimmenAktuellSystemgruppen(gebietUndStimmabgabeart).get(getGruppeGueltige());
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellUngueltigeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenAktuellUngueltige(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getZweitstimmenAktuellUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellUngueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getZweitstimmenAktuellUngueltige(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * Liefert die Anzahl ohne Kontext (Stimmen oder Stimmzettel)!!!
   *
   * @see de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte#valueOfUngueltige
   * @see StimmergebnisDaten#getErgebnisDatenGueltige
   */
  @Nullable
  public Long getZweitstimmenAktuellUngueltige(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getZweitstimmenAktuellSystemgruppen(gebietUndStimmabgabeart).get(getGruppeUnGueltige());
  }

  public ErgebnisDatenQuorum<Wahldurchfuehrungsgebiet> getErgebnisDatenQuorumWahlberechtigte() {
    if (ergebnisDatenQuorumWahlberechtigte == null) {
      ergebnisDatenQuorumWahlberechtigte = new ErgebnisDatenQuorum<>(getWahl(),
          wahldurchfuehrungsgebiet -> getErgebnisDatenWahlberechtigte().getSummeWahlberechtigteGesamt(
              new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT)));
    }
    return ergebnisDatenQuorumWahlberechtigte;
  }

  public AbstractErgebnisDatenWahlberechtigte getErgebnisDatenWahlberechtigte() {
    if (ergebnisDatenWahlberechtigte == null) {
      ergebnisDatenWahlberechtigte = new ErgebnisDatenWahlberechtigteAktuelleWahlAusGebieten(this, this::getErfassteGebiete, stimmenKumulierer, getStimmen());
    }
    return ergebnisDatenWahlberechtigte;
  }

  public AbstractErgebnisDatenWahlberechtigte getErgebnisDatenWahlberechtigteVorperiode() {
    if (ergebnisDatenWahlberechtigteVorperiode == null) {
      ergebnisDatenWahlberechtigteVorperiode = new ErgebnisDatenWahlberechtigteVorperiode(this::getUntergeordneteGebiete, this::getErfassteGebiete,
          stimmenKumulierer, getStimmenVorperiodeAlsPool());
    }
    return ergebnisDatenWahlberechtigteVorperiode;
  }

  public ErgebnisDatenWaehler getErgebnisDatenWaehler() {
    if (ergebnisDatenWaehler == null) {
      ergebnisDatenWaehler = new ErgebnisDatenWaehler(this::getUntergeordneteGebiete, getStimmen(), stimmenKumulierer, this::getErfassteGebiete);
    }
    return ergebnisDatenWaehler;
  }

  public ErgebnisDatenGueltige getErgebnisDatenGueltige() {
    if (ergebnisDatenGueltige == null) {
      ergebnisDatenGueltige = new ErgebnisDatenGueltige(this);
    }
    return ergebnisDatenGueltige;
  }

  public ErgebnisDatenStimmzettel getErgebnisDatenStimmzettel() {
    if (ergebnisDatenStimmzettel == null) {
      ergebnisDatenStimmzettel = new ErgebnisDatenStimmzettel(this);
    }
    return ergebnisDatenStimmzettel;
  }

  public ErgebnisDatenListenkandidaturen getErgebnisDatenListenkandidaturen() {
    if (ergebnisDatenListenkandidaturen == null) {
      ergebnisDatenListenkandidaturen = new ErgebnisDatenListenkandidaturen(this, getStimmen(), stimmenKumulierer, getListenkandidaturenNachGruppe(),
          this::getErfassteGebiete);
    }
    return ergebnisDatenListenkandidaturen;
  }

  public ErgebnisDatenDirektkandidaturen getErgebnisDatenDirektkandidaturen() {
    if (ergebnisDatenDirektkandidaturen == null) {
      ergebnisDatenDirektkandidaturen = new ErgebnisDatenDirektkandidaturen(this::getUntergeordneteGebiete, getStimmen(), stimmenKumulierer,
          getDirektkandidaturenNachGruppe(), getDirektkandidaturen(), this::getErfassteGebiete);
    }
    return ergebnisDatenDirektkandidaturen;
  }

  public ErgebnisDatenGesamtstimmen getErgebnisDatenGesamtstimmen() {
    if (ergebnisDatenGesamtstimmen == null) {
      ergebnisDatenGesamtstimmen = new ErgebnisDatenGesamtstimmen(this);
    }
    return ergebnisDatenGesamtstimmen;
  }

  private static <K> void merge(Map<K, Long> result, Map<K, Long> ergebnisUntergeordnetesGebiet) {
    for (var entry : ergebnisUntergeordnetesGebiet.entrySet()) {
      if (entry.getValue() != null) {
        result.compute(entry.getKey(), (k, v) -> sum(v, entry.getValue()));
      }
    }
  }

  @Nullable
  private static Long sum(Long x, Long y) {
    if (x == null && y == null) {
      return null;
    }
    if (x == null) {
      return y;
    }
    if (y == null) {
      return y;
    }
    return x + y;
  }

  private static <Target, G extends GebietUndStimmabgabeart> void legeErgebnisFuerAlleRelevantenStimmabgabenAb(Map<G, Map<Target, Long>> ziel, Map<Target, Long> result, G gebietUndStimmabgabeart, Supplier<Set<Gebiet>> erfassteGebiete) {
    var wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();
    // Speicher gleich für alle potentiellen Stimmabgabearten
    for (var zuSpeicherndeStimmabgabeart : Stimmabgabeart.values()) {
      var copy = gebietUndStimmabgabeart.copy(zuSpeicherndeStimmabgabeart);
      if (copy.filterGebiete(erfassteGebiete).test(wahldurchfuehrungsgebiet)) {
        ziel.put((G) copy, result);
      }
    }
  }

  /**
   * @param gebietUndStimmabgabeart definiert das Obergebiet, für das die Stimmen bestimmt werden und Filter, die die Stimmen auf bestimmte Untergebiete einschränken (z.B. nur Briefwahlbezirke)
   * @param cache                   darin wird das Ergebnis dieser Methode gecachet mit dem gebietUndStimmabgabeart als Key
   * @param bestimmeStimmen
   * @param <T>                     Target, z.B. Gruppe oder PolitischeGruppe
   * @param <G>                     GebietUndStimmabgabeart oder eine Subklasse, die ggf. weitere Filtermöglichkeiten bietet
   * @return eine Map, die für jedes Target die Anzahl von Stimmen auf dem Obergebiet enthält, ggf. per Filter auf eine Teilmenge der Untergebiete eingeschränkt
   */
  private <T, G extends GebietUndStimmabgabeart> Map<T, Long> bestimmeStimmenOderSummiereAufUntergebieten(G gebietUndStimmabgabeart, Map<G, Map<T, Long>> cache, BestimmeStimmen<T, G> bestimmeStimmen) {
    return bestimmeStimmenOderSummiereAufUntergebieten(gebietUndStimmabgabeart, cache, bestimmeStimmen, this::getUntergeordneteGebiete,
        this::getErfassteGebiete);
  }

  static <T, G extends GebietUndStimmabgabeart> Map<T, Long> bestimmeStimmenOderSummiereAufUntergebieten(G gebietUndStimmabgabeart, Map<G, Map<T, Long>> cache, BestimmeStimmen<T, G> bestimmeStimmen, Function<Wahldurchfuehrungsgebiet, List<Wahldurchfuehrungsgebiet>> untergeordneteGebiete, Supplier<Set<Gebiet>> erfassteGebiete) {
    var result = cache.get(gebietUndStimmabgabeart);
    if (result == null) {
      result = new HashMap<>();
      var putInCache = putInCache(gebietUndStimmabgabeart, cache.size());
      var wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();
      if (wahldurchfuehrungsgebiet != null && (wahldurchfuehrungsgebiet.isErfassungsgebiet() || !wahldurchfuehrungsgebiet.isErgebnisRelevant())) {
        var filterGebiete = gebietUndStimmabgabeart.filterGebiete(erfassteGebiete);
        if (filterGebiete.test(wahldurchfuehrungsgebiet)) {
          for (var target : bestimmeStimmen.getTargetAufGebiet(gebietUndStimmabgabeart)) {
            var stimmen = bestimmeStimmen.ermittleStimmen(target, wahldurchfuehrungsgebiet);
            if (stimmen != null) {
              result.put(target, stimmen);
            }
          }
          if (putInCache) {
            legeErgebnisFuerAlleRelevantenStimmabgabenAb(cache, result, gebietUndStimmabgabeart, erfassteGebiete);
          }
        }
      } else {
        for (var untergeordnetesGebiet : untergeordneteGebiete.apply(wahldurchfuehrungsgebiet)) {
          // ELECT-3919, WASDE-1741
          if (gebietUndStimmabgabeart.getFilterNachErgebnisRelevanz().test(untergeordnetesGebiet)) {
            final var gebietUndStimmabgabeartUntergebiet = (G) gebietUndStimmabgabeart.copy(untergeordnetesGebiet);
            merge(result, bestimmeStimmen.getErgebnisUntergebiet(gebietUndStimmabgabeartUntergebiet));
          }
        }
      }

      if (putInCache) {
        cache.put(gebietUndStimmabgabeart, result);
      }

    }
    return result;
  }

  private static <G extends GebietUndStimmabgabeart> boolean putInCache(G gebietUndStimmabgabeart, int cacheSize) {
    // ELECT-12242
    return cacheSize < MAX_CACHE_SIZE_GEBIETSGRUPPIERUNG || gebietUndStimmabgabeart.getGebietsgruppierung() == null || gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet() == null || !gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()
        .isErfassungsgebiet();
  }

  private Long notNull(Long aLong) {
    if (aLong == null) {
      return 0L;
    }
    return aLong;
  }

  /**
   * Liefert für ein GebietUndStimmabgabeart die Gebiete, aus denen sich das Ergebnis kumulieren lässt. Dabei werden ggf. auch Stimmabgabeart, Gebietsgruppierung und Ergebnis-relevanz berücksichtigt.
   */
  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = erfassungsgebieteUnterhalbVon.get(gebietUndStimmabgabeart);
    if (result == null) {
      var filterGebiete = gebietUndStimmabgabeart.filterGebiete(this::getErfassteGebiete);
      result = getErfassungsgebieteErgebnisRelevantUnterhalbVon(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream().filter(filterGebiete)
          .collect(Collectors.toList());
      erfassungsgebieteUnterhalbVon.put(gebietUndStimmabgabeart, result);
    }
    return result;
  }

  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteLautGebietsartUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = erfassungsgebieteLautGebietsartUnterhalbVon.get(gebietUndStimmabgabeart);
    if (result == null) {
      var filterGebiete = gebietUndStimmabgabeart.filterGebiete(this::getErfassteGebiete);
      result = getErfassungsgebieteLautGebietsarUnterhalbVon(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream().filter(filterGebiete)
          .collect(Collectors.toList());
      erfassungsgebieteLautGebietsartUnterhalbVon.put(gebietUndStimmabgabeart, result);
    }
    return result;
  }

  public Long getWahlberechtigteGesamtAusgezaehltFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return zaehleZusammen(getWahlberechtigteGesamtAusgezaehltFuerErfassungsgebietUnterhalbVon(gebietUndStimmabgabeart));
  }

  public Long getWahlberechtigteOhneWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return zaehleZusammen(getWahlberechtigteOhneWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon(gebietUndStimmabgabeart));
  }

  public Long getWahlberechtigteMitWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return zaehleZusammen(getWahlberechtigteMitWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon(gebietUndStimmabgabeart));
  }

  public Long getWahlberechtigteNichtImWaehlerverzeichnisAusgezaehltFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return zaehleZusammen(getWahlberechtigteNichtImWaehlerverzeichnisAusgezaehltFuerErfassungsgebietUnterhalbVon(gebietUndStimmabgabeart));
  }

  public Long getWahlberechtigteVorperiodeGesamtFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getWahlberechtigteGesamtVorperiodeFuerErfassungsgebietUnterhalbVon(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Long getWahlberechtigteVorperiodeGesamtFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getWahlberechtigteGesamtVorperiodeFuerErfassungsgebietUnterhalbVon(
        new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  public Long getWahlberechtigteGesamtVorperiodeFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteGesamtVorperiodeFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> getStimmenKumulierer().getSummeWahlberechtigteGesamtAusStimmen(getStimmenVorperiodeAlsPool(gebietUndStimmabgabeart),
            gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()));
  }

  public Long getWahlberechtigteOhneWahlscheinVorperiodeFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteOhneWahlscheinVorperiodeFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> getStimmenKumulierer().getSummeWahlberechtigteOhneWahlscheinAusStimmen(getStimmenVorperiodeAlsPool(gebietUndStimmabgabeart),
            gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()));
  }

  public Long getWahlberechtigteMitWahlscheinVorperiodeFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteMitWahlscheinVorperiodeFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> getStimmenKumulierer().getSummeWahlberechtigteMitWahlscheinAusStimmen(getStimmenVorperiodeAlsPool(gebietUndStimmabgabeart),
            gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()));
  }

  public Long getWahlberechtigteNichtImWaehlerverzeichnisVorperiodeFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteNichtImWaehlerverzeichnisVorperiodeFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> getStimmenKumulierer().getSummeWahlberechtigteNichtImWaehlerverzeichnisAusStimmen(getStimmenVorperiodeAlsPool(gebietUndStimmabgabeart),
            gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()));
  }

  // NOTE ELECTKOM-121, dass erst die A-Werte am Gebiet und dann die erfassten A-Werte ausgewertet werden sollen, cf. KopfzahlKumulierteStimmenSearch#calculateSummeAnzahlWahlberechtigte!!!
  @Nullable
  private Long bestimmeWahlberechtigteFuerErfassungsgebiet(Wahldurchfuehrungsgebiet erfassungsgebiet, Function<Wahldurchfuehrungsgebiet, Long> anzahlWahlberechtigteAmGebietGetter, Stimmenart stimmenart) {

    var result = bestimmeWahlberechtigte(erfassungsgebiet, anzahlWahlberechtigteAmGebietGetter, stimmenart);

    // ELECT-5287 Fallback auf Stimmen
    // nur wenn nicht fallback auf stimmen

    if (anzahlWahlberechtigteAmGebietGetter.apply(erfassungsgebiet) != null) {
      // ELECT-12040
      for (var zusaetzlichErfassetesGebiet : getErfasstFuerGebiete(erfassungsgebiet)) {
        var resultZusaetzlichErfassetesGebiet = bestimmeWahlberechtigte(zusaetzlichErfassetesGebiet, anzahlWahlberechtigteAmGebietGetter, stimmenart);
        if (resultZusaetzlichErfassetesGebiet != null) {
          if (result == null) {
            result = resultZusaetzlichErfassetesGebiet;
          } else {
            result += resultZusaetzlichErfassetesGebiet;
          }
        }
      }
    }

    return result;
  }

  @Nullable
  private Long bestimmeWahlberechtigte(Wahldurchfuehrungsgebiet erfassungsgebiet, Function<Wahldurchfuehrungsgebiet, Long> anzahlWahlberechtigteAmGebietGetter, Stimmenart stimmenart) {
    var result = anzahlWahlberechtigteAmGebietGetter.apply(erfassungsgebiet);

    if (result == null) { // ELECT-5287 Fallback auf Stimmen
      result = getStimmenKumulierer().getSummeWahlberechtigteStimmenAufGebiet(getStimmen(), erfassungsgebiet, stimmenart);
    }
    return result;
  }

  // NOTE ELECT-9522, dass die A-Werte ggf. erst an einem übergeordneten Gebiet erfasst werden!
  private Long bestimmeWahlberechtigteFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart, Function<GebietUndStimmabgabeart, Map<Wahldurchfuehrungsgebiet, Long>> anzahlWahlberechtigtePerErfassungsgebietFct, Function<Wahldurchfuehrungsgebiet, Long> anzahlWahlberechtigteAmGebietFct) {
    var result = zaehleZusammen(anzahlWahlberechtigtePerErfassungsgebietFct.apply(gebietUndStimmabgabeart));

    if (result == null || result < 1) {
      result = 0L;

      if (!getErgebnisDatenWahlberechtigte().isErmittleStimmenNurAnhandErfassungsgebiete()) {
        var wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

        if (wahldurchfuehrungsgebiet != null) {
          result = anzahlWahlberechtigteAmGebietFct.apply(wahldurchfuehrungsgebiet); // ELECT-9546
        }
      }
    }

    return notNull(result);
  }

  private Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteGesamtAusgezaehltFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteGesamtAusgezaehltFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> bestimmeWahlberechtigteAusgezaehltFuerErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart,
            this::bestimmeWahlberechtigteGesamtFuerErfassungsgebiet));
  }

  private Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteOhneWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteOhneWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> bestimmeWahlberechtigteAusgezaehltFuerErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart,
            this::bestimmeWahlberechtigteOhneWahlscheinFuerErfassungsgebiet));
  }

  private Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteMitWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteMitWahlscheinAusgezaehltFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> bestimmeWahlberechtigteAusgezaehltFuerErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart,
            this::bestimmeWahlberechtigteMitWahlscheinFuerErfassungsgebiet));
  }

  private Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteNichtImWaehlerverzeichnisAusgezaehltFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return wahlberechtigteNichtImWaehlerverzeichnisAusgezaehltFuerErfassungsgebietUnterhalbVon.computeIfAbsent(gebietUndStimmabgabeart,
        k -> bestimmeWahlberechtigteAusgezaehltFuerErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart,
            this::bestimmeWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebiet));
  }

  private Map<Wahldurchfuehrungsgebiet, Long> bestimmeWahlberechtigteAusgezaehltFuerErfassungsgebieteUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart, Function<Wahldurchfuehrungsgebiet, Long> bestimmeWahlberechtigteFuerErfassungsgebiet) {
    Function<Wahldurchfuehrungsgebiet, Long> bestimmeWahlberechtigteFuerErfassungsgebietNotNull = g -> notNull(
        bestimmeWahlberechtigteFuerErfassungsgebiet.apply(g)); // ELECTKOM-1704

    return getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart).stream() //
        .filter(erfassungsgebiet -> getErfassteGebiete().contains(erfassungsgebiet)) //
        .collect(toMap(erfassungsgebiet -> erfassungsgebiet, bestimmeWahlberechtigteFuerErfassungsgebietNotNull, (a, b) -> b));
  }

  private Long bestimmeWahlberechtigteGesamtFuerErfassungsgebiet(Wahldurchfuehrungsgebiet erfassungsgebiet) {
    Long result = 0L;

    if (erfassungsgebiet.hasAnyAnzahlWahlberechtigte()) { // ELECTSL-74
      result += notNull(bestimmeWahlberechtigteOhneWahlscheinFuerErfassungsgebiet(erfassungsgebiet));
      result += notNull(bestimmeWahlberechtigteMitWahlscheinFuerErfassungsgebiet(erfassungsgebiet));
      result += notNull(bestimmeWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebiet(erfassungsgebiet));
    } else {
      result += notNull(bestimmeWahlberechtigteFuerErfassungsgebiet(erfassungsgebiet, ignored -> null, Stimmenart.GESAMT));
    }

    return result;
  }

  @Nullable
  private Long bestimmeWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebiet(Wahldurchfuehrungsgebiet erfassungsgebiet) {
    return bestimmeWahlberechtigteFuerErfassungsgebiet(erfassungsgebiet, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteNichtImWaehlerverzeichnis,
        Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
  }

  @Nullable
  private Long bestimmeWahlberechtigteMitWahlscheinFuerErfassungsgebiet(Wahldurchfuehrungsgebiet erfassungsgebiet) {
    return bestimmeWahlberechtigteFuerErfassungsgebiet(erfassungsgebiet, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteMitWahlschein,
        Stimmenart.MIT_WAHLSCHEIN);
  }

  @Nullable
  private Long bestimmeWahlberechtigteOhneWahlscheinFuerErfassungsgebiet(Wahldurchfuehrungsgebiet erfassungsgebiet) {
    return bestimmeWahlberechtigteFuerErfassungsgebiet(erfassungsgebiet, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteOhneWahlschein,
        Stimmenart.OHNE_WAHLSCHEIN);
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteOhneWahlschein
   */
  @Override
  public Long getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(mitStimmabgabeartGesamt(wahldurchfuehrungsgebiet));
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteOhneWahlschein
   */
  public Long getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return bestimmeWahlberechtigteFuerErfassungsgebietUnterhalbVonAnzahl(gebietUndStimmabgabeart,
        this::getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVon, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteOhneWahlscheinNotNull);
  }

  /**
   * @see StimmergebnisDaten#getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl
   * @deprecated ELECT-9522, je nach Konfiguration sind A-Werte auch auf übergeordneten Gebieten erfassbar!
   */
  @Deprecated
  public Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = wahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVon.get(gebietUndStimmabgabeart);

    if (result == null) {
      result = new HashMap<>();

      wahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVon.put(gebietUndStimmabgabeart, result);

      for (var erfassungsgebiet : getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        result.put(erfassungsgebiet, bestimmeWahlberechtigteOhneWahlscheinFuerErfassungsgebiet(erfassungsgebiet));
      }
    }

    return result;
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteMitWahlschein
   */
  @Override
  public Long getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(mitStimmabgabeartGesamt(wahldurchfuehrungsgebiet));
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteMitWahlschein
   */
  public Long getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return bestimmeWahlberechtigteFuerErfassungsgebietUnterhalbVonAnzahl(gebietUndStimmabgabeart,
        this::getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVon, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteMitWahlscheinNotNull);
  }

  /**
   * @see StimmergebnisDaten#getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl
   * @deprecated ELECT-9522, je nach Konfiguration sind A-Werte auch auf übergeordneten Gebieten erfassbar!
   */
  @Deprecated
  public Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = wahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVon.get(gebietUndStimmabgabeart);

    if (result == null) {
      result = new HashMap<>();

      wahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVon.put(gebietUndStimmabgabeart, result);

      for (var erfassungsgebiet : getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        result.put(erfassungsgebiet, bestimmeWahlberechtigteMitWahlscheinFuerErfassungsgebiet(erfassungsgebiet));
      }
    }

    return result;
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteNichtImWaehlerverzeichnis
   */
  @Override
  public Long getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVonAnzahl(mitStimmabgabeartGesamt(wahldurchfuehrungsgebiet));
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteNichtImWaehlerverzeichnis
   */
  public Long getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return bestimmeWahlberechtigteFuerErfassungsgebietUnterhalbVonAnzahl(gebietUndStimmabgabeart,
        this::getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVon,
        Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteNichtImWaehlerverzeichnisNotNull);
  }

  /**
   * @see StimmergebnisDaten#getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVonAnzahl
   * @deprecated ELECT-9522, je nach Konfiguration sind A-Werte auch auf übergeordneten Gebieten erfassbar!
   */
  @Deprecated
  public Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = wahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVon.get(gebietUndStimmabgabeart);

    if (result == null) {
      result = new HashMap<>();

      wahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVon.put(gebietUndStimmabgabeart, result);

      for (var erfassungsgebiet : getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        result.put(erfassungsgebiet, bestimmeWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebiet(erfassungsgebiet));
      }
    }

    return result;
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteGesamt
   */
  @Override
  public Long getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahlGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteGesamt
   */
  public Long getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteGesamt
   */
  public Long getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return bestimmeWahlberechtigteFuerErfassungsgebietUnterhalbVonAnzahl(gebietUndStimmabgabeart,
        this::getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVon, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigterGesamtA1A2A3NotNull);
  }

  /**
   * @see StimmergebnisDaten#getErgebnisDatenWahlberechtigte
   * @see AbstractErgebnisDatenWahlberechtigte#getSummeWahlberechtigteGesamt
   */
  public Long getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl(
        new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  /**
   * @see StimmergebnisDaten#getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahl
   * @deprecated ELECT-9522, je nach Konfiguration sind A-Werte auch auf übergeordneten Gebieten erfassbar!
   */
  @Deprecated
  public Map<Wahldurchfuehrungsgebiet, Long> getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = wahlberechtigteGesamtFuerErfassungsgebietUnterhalbVon.get(gebietUndStimmabgabeart);

    if (result == null) {
      result = new HashMap<>();

      wahlberechtigteGesamtFuerErfassungsgebietUnterhalbVon.put(gebietUndStimmabgabeart, result);

      for (var erfassungsgebiet : getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        result.put(erfassungsgebiet, bestimmeWahlberechtigteGesamtFuerErfassungsgebiet(erfassungsgebiet));
      }
    }

    return result;
  }

  protected GebietUndStimmabgabeart mitStimmabgabeartGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  protected Long zaehleZusammen(Map<Wahldurchfuehrungsgebiet, Long> map) {
    return map.values().stream().filter(Objects::nonNull).mapToLong(aLong -> aLong).sum();
  }

  @Override
  public Long getWaehlerOhneWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerOhneWahlschein.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerOhneWahlscheinStimmenAufGebiet(getStimmen(), wahldurchfuehrungsgebiet));
  }

  @Override
  public Long getWaehlerMitWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerMitWahlschein.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerMitWahlscheinStimmenAufGebiet(getStimmen(), wahldurchfuehrungsgebiet));
  }

  @Override
  public Long getWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlStimmenAufGebiet(getStimmen(), wahldurchfuehrungsgebiet));
  }

  @Override
  public Long getWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlStimmenAufGebiet(getStimmen(), wahldurchfuehrungsgebiet));
  }

  @Override
  public Long getWaehlerMitVollmacht(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerMitVollmacht.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerMitVollmachtStimmenAufGebiet(getStimmen(), wahldurchfuehrungsgebiet));
  }

  @Override
  public Long getWaehlerGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerGesamt.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerGesamtStimmenAufGebiet(getStimmen(), wahldurchfuehrungsgebiet));
  }

  public boolean hatGebietWaehler(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return notNull(getWaehlerGesamt(wahldurchfuehrungsgebiet)) > 0L;
  }

  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteMitStimmen() {
    List<Wahldurchfuehrungsgebiet> erfassungsgebieteMitStimmen = new ArrayList<>();

    for (var erfassungsgebiet : getErfassungsgebiete()) {
      if (hatGebietWaehler(erfassungsgebiet)) {
        erfassungsgebieteMitStimmen.add(erfassungsgebiet);
      }
    }

    return erfassungsgebieteMitStimmen;
  }

  public Long getWahlberechtigteGesamtVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return wahlberechtigteGesamtVorperiode.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWahlberechtigteGesamtAusStimmen(getStimmenVorperiodeAlsPool(), wahldurchfuehrungsgebiet));
  }

  public Long getWaehlerOhneWahlscheinVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerOhneWahlscheinVorperiode.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerOhneWahlscheinStimmenAufGebiet(getStimmenVorperiodeAlsPool(), wahldurchfuehrungsgebiet));
  }

  public Long getWaehlerMitWahlscheinVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return waehlerMitWahlscheinVorperiode.computeIfAbsent(wahldurchfuehrungsgebiet,
        k -> getStimmenKumulierer().getSummeWaehlerMitWahlscheinStimmenAufGebiet(getStimmenVorperiodeAlsPool(), wahldurchfuehrungsgebiet));
  }

  public Long getWaehlerGesamtVorperiodeGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getWaehlerGesamtVorperiode(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public Long getWaehlerGesamtVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getWaehlerGesamtVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Long getWaehlerGesamtVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, HashablePredicate<Gebiet> gebietImmerAlsErfasstWerten) {
    return getWaehlerGesamtVorperiode(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart, gebietImmerAlsErfasstWerten));
  }

  public Long getWaehlerAktuellGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = waehlerAktuellGesamt.get(gebietUndStimmabgabeart);
    if (result == null) {
      result = 0L;
      var erfassungsgebiete = getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart);
      for (var erfassungsgebiet : erfassungsgebiete) {
        result += notNull(getWaehlerGesamt(erfassungsgebiet));
      }
      waehlerAktuellGesamt.put(gebietUndStimmabgabeart, result);
    }
    return result;
  }

  public Long getWaehlerAktuellOhneWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    var result = waehlerAktuellOhneWahlschein.get(gebietUndStimmabgabeart);
    if (result == null) {
      result = 0L;
      var erfassungsgebiete = getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart);
      for (var erfassungsgebiet : erfassungsgebiete) {
        result += notNull(getWaehlerOhneWahlschein(erfassungsgebiet));
      }
      waehlerAktuellOhneWahlschein.put(gebietUndStimmabgabeart, result);
    }
    return result;
  }

  public Long getWaehlerGesamtVorperiode(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return waehlerGesamtVorperiode.computeIfAbsent(gebietUndStimmabgabeart,
        k -> getStimmenKumulierer().getSummeWaehlerGesamtStimmenAufGebiet(getStimmenVorperiodeAlsPool(gebietUndStimmabgabeart),
            gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()));
  }

  @Override
  public ErgebnisDatenReferendumOptionen getErgebnisDatenReferendumOptionen() {
    if (ergebnisDatenReferendumOptionen == null) {
      ergebnisDatenReferendumOptionen = new ErgebnisDatenReferendumOptionen(this, getStimmen(), stimmenKumulierer,
          getReferendumOptionenNachReferendumVorlage());
    }
    return ergebnisDatenReferendumOptionen;
  }

  /**
   * ELECT-3908
   */
  public Long getZweitstimmenAbzuziehenAktuellGesamt(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getZweitstimmenAbzuziehenAktuell(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public Long getZweitstimmenAbzuziehenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return notNull(getZweitstimmenAbzuziehenAktuell(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart)).get(politischeGruppe));
  }

  public Long getZweitstimmenAbzuziehenAktuell(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Gebietsgruppierung gebietsgruppierung, Stimmabgabeart stimmabgabeart) {
    return notNull(
        getZweitstimmenAbzuziehenAktuell(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, stimmabgabeart)).get(politischeGruppe));
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenAbzuziehenAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenAbzuziehenAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenEinzelnerWahlvorschlagUnveraendertAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenEinzelnerWahlvorschlagUnveraendertAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenEinzelnerWahlvorschlagVeraendertAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenEinzelnerWahlvorschlagPanaschiertAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenKeinEinzelnerWahlvorschlagVeraendertAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenKeinEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenKeinEinzelnerWahlvorschlagPanaschiertAktuell(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return listenstimmenKeinEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen.getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
  }

  public Loader<Gebiet> getErfassteGebieteLoader() {
    return erfassteGebieteLoader;
  }

  public Set<Gebiet> getErfassteGebiete() {
    if (erfassteGebiete == null) {
      erfassteGebiete = new HashSet<>(erfassteGebieteLoader.load());

      // ELECTKOM-2408
      if (erfassteGebiete.isEmpty() && getWahl() != null && getWahl().getStatus() != null && getWahl().getStatus().isMigriert()) {
        erfassteGebiete = new HashSet<>(getErfassungsgebiete()/*ELECT-15795*/);
      }
    }
    return erfassteGebiete;
  }

  public Set<Wahldurchfuehrungsgebiet> getAusbleibendeGebiete() {
    if (ausbleibendeGebiete == null) {
      ausbleibendeGebiete = getGebiete().stream().filter(AbstractGebiet::isErfassungsgebietLautGebietsart)
          .filter(g -> g.getErgebniseingabestatus(getErgebnis()).isAusbleibend()).collect(Collectors.toSet());
    }
    return ausbleibendeGebiete;
  }

  public boolean isGebietErfasst(Gebiet gebiet) {
    return getErfassteGebiete().contains(gebiet);
  }

  public ErgebnisStimmenFilter getErgebnisStimmenFilter() {
    return ergebnisStimmenFilter;
  }

  public Map<Stimmenart, Long> getAnzahlGebieteProStimmenartAusgezaehlt(Gebiet gebiet) {
    if (anzahlGebieteProStimmenartAusgezaehlt == null) {
      anzahlGebieteProStimmenartAusgezaehlt = initAnzahlGebieteProStimmenartAusgezaehlt();
    }
    return anzahlGebieteProStimmenartAusgezaehlt.getOrDefault(gebiet, Collections.emptyMap());
  }

  private Map<Gebiet, Map<Stimmenart, Long>> initAnzahlGebieteProStimmenartAusgezaehlt() {
    Map<Gebiet, Map<Stimmenart, Long>> result = new HashMap<>();
    for (var gebiet : getErfassteGebiete()) {

      addStimmenProStimmenartFuerGebietUndObergebiete(result, gebiet);

      var stimmenartAnzahlFuerGebietMap = result.get(gebiet);

      for (var stimmenAufGebiet : getStimmen().getDirektListenSystemStimmenAufGebiet(gebiet)) {
        if (stimmenAufGebiet.getAnzahl() > 0L && !stimmenartAnzahlFuerGebietMap.containsKey(stimmenAufGebiet.getStimmenart()) && stimmenAufGebiet.getGruppe()
            .isPolitischeGruppe()) {
          addAnzahlStimmenProStimmenartFuerGebietUndObergebiete(result, gebiet, stimmenAufGebiet.getStimmenart());
        }
      }
    }

    return result;
  }

  private void addAnzahlStimmenProStimmenartFuerGebietUndObergebiete(Map<Gebiet, Map<Stimmenart, Long>> result, Gebiet gebiet, Stimmenart stimmenart) {
    if (gebiet == null) {
      return;
    }

    var stimmenMap = result.get(gebiet);
    stimmenMap.put(stimmenart, stimmenMap.getOrDefault(stimmenart, 0L) + 1L);
    addAnzahlStimmenProStimmenartFuerGebietUndObergebiete(result, gebiet.getUebergeordnetesGebiet(), stimmenart);
  }

  private void addStimmenProStimmenartFuerGebietUndObergebiete(Map<Gebiet, Map<Stimmenart, Long>> result, @Nullable Gebiet gebiet) {
    if (gebiet == null || result.containsKey(gebiet)) {
      return;
    }
    result.put(gebiet, new EnumMap<>(Stimmenart.class));
    addStimmenProStimmenartFuerGebietUndObergebiete(result, gebiet.getUebergeordnetesGebiet());
  }

  // Innere Klassen zum Kumulieren und Cachen der Stimmen der politischen Gruppen
  private abstract class BestimmeStimmenPolitischeGruppen implements BestimmeStimmen<PolitischeGruppe, GebietUndStimmabgabeart> {

    private final Map<GebietUndStimmabgabeart, Map<PolitischeGruppe, Long>> stimmenPolitischeGruppen = new HashMap<>();

    public Map<PolitischeGruppe, Long> getStimmenPolitischeGruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return bestimmeStimmenOderSummiereAufUntergebieten(gebietUndStimmabgabeart, stimmenPolitischeGruppen, this);
    }

    @Override
    public List<PolitischeGruppe> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return getPolitischeGruppenSortiertNachPositionAufGebiet(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    }

    @Override
    public Map<PolitischeGruppe, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return getStimmenPolitischeGruppen(gebietUndStimmabgabeart);
    }
  }

  private class ListenstimmenAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    @Nullable
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      var result = getStimmenKumulierer().getSummeListenstimmenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
          getStimmen());
      if (result != null && result == 0L) {
        //ELECT-8913 null statt 0 wenn kein Wahlvorschlag
        if (isListenVorhanden() && !isGruppeHatZugelasseneListeInGebietshierarchie(politischeGruppe, wahldurchfuehrungsgebiet)) {
          return null;
        }
      }
      return result;
    }
  }

  private class ListenstimmenVorperiodeBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      if (isZweitstimmenVorperiodeVorhanden(politischeGruppe, wahldurchfuehrungsgebiet)) {
        return getStimmenKumulierer().getSummeListenstimmenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
            getStimmenVorperiodeAlsPool());
      }

      return null; // ELECT-12859
    }
  }

  private class ListenstimmenVergleichBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
          getStimmenVergleichAlsPool());
    }
  }

  private class DirektstimmenAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    @Nullable
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      var result = getStimmenKumulierer().getSummeDirektstimmenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
          getStimmen());
      if (result != null && result == 0) {
        // ELECT-8913 null statt 0 wenn kein Wahlvorschlag
        if (isDirektkandidatenVorhanden() && !isGruppeHatZugelassenenDirektkandidatenInGebietshierarchie(politischeGruppe, wahldurchfuehrungsgebiet)) {
          return null;
        }
      }
      return result;
    }
  }

  private class DirektstimmenVorperiodeBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    @Nullable
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      if (isErststimmenVorperiodeVorhanden(politischeGruppe, wahldurchfuehrungsgebiet)) {
        return getStimmenKumulierer().getSummeDirektstimmenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
            getStimmenVorperiodeAlsPool());
      }

      return null; // ELECT-12859
    }
  }

  private class DirektstimmenVergleichBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeDirektstimmenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
          getStimmenVergleichAlsPool());
    }
  }

  private class ListenstimmenAbzuziehenAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenAbzuziehenFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe, wahldurchfuehrungsgebiet,
          getStimmen());
    }
  }

  private class ListenstimmenEinzelnerWahlvorschlagUnveraendertAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenEinzelnerWahlvorschlagUnveraendertFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe,
          wahldurchfuehrungsgebiet, getStimmen());
    }
  }

  private class ListenstimmenEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenEinzelnerWahlvorschlagVeraendertFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe,
          wahldurchfuehrungsgebiet, getStimmen());
    }
  }

  private class ListenstimmenEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenEinzelnerWahlvorschlagPanaschiertFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe,
          wahldurchfuehrungsgebiet, getStimmen());
    }
  }

  private class ListenstimmenKeinEinzelnerWahlvorschlagVeraendertAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenKeinEinzelnerWahlvorschlagVeraendertFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe,
          wahldurchfuehrungsgebiet, getStimmen());
    }
  }

  private class ListenstimmenKeinEinzelnerWahlvorschlagPanaschiertAktuellBestimmeStimmenPolitischeGruppen extends BestimmeStimmenPolitischeGruppen {

    @Override
    public Long ermittleStimmen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().getSummeListenstimmenKeinEinzelnerWahlvorschlagPanaschiertFuerPolitischeGruppeAufGebietUndDarunter(politischeGruppe,
          wahldurchfuehrungsgebiet, getStimmen());
    }
  }

  // Innere Klassen zum Kumulieren und Cachen der Stimmen der Systemgruppen

  private class BestimmeStimmenSystemgruppen implements BestimmeStimmen<Gruppe, GebietUndStimmabgabeart>, Serializable {

    private static final long serialVersionUID = 1L;

    private final Stimmenart stimmenart;

    private final Supplier<StimmenPool> stimmenPoolSupplier;

    private final Map<GebietUndStimmabgabeart, Map<Gruppe, Long>> stimmenSystemgruppen = new HashMap<>();

    private StimmenPool stimmenPool;

    BestimmeStimmenSystemgruppen(Stimmenart stimmenart, Supplier<StimmenPool> stimmenPoolSupplier) {
      this.stimmenart = stimmenart;
      this.stimmenPoolSupplier = stimmenPoolSupplier;
    }

    private StimmenPool getStimmenPool() {
      if (stimmenPool == null) {
        // nur Systemstimmen betrachten
        stimmenPool = StimmenPool.createStimmenPoolFuerAlleStimmen(stimmenPoolSupplier.get().getSystemgruppenStimmen());
      }
      return stimmenPool;
    }

    @Override
    public Long ermittleStimmen(Gruppe systemgruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return getStimmenKumulierer().bestimmeSummeSystemGruppenStimmenAufGebiet(getStimmenPool(), wahldurchfuehrungsgebiet,
          systemgruppe.getSystemGruppe(getSystemGruppeEigenschaften()), stimmenart);
    }

    public Map<Gruppe, Long> getStimmenSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return bestimmeStimmenOderSummiereAufUntergebieten(gebietUndStimmabgabeart, stimmenSystemgruppen, this);
    }

    private boolean isErfassungsgebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet() != null && gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet().isErfassungsgebiet();

    }

    @Override
    public Map<Gruppe, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return getStimmenSystemgruppen(gebietUndStimmabgabeart);
    }

    @Override
    public List<Gruppe> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return getSystemgruppen();
    }
  }

  private final Map<Ergebniseingangsart, Map<Gebiet, Ergebniseingang>> letzterErgebniseingangNachEingansart = new EnumMap<>(Ergebniseingangsart.class);

  public Map<Gebiet, Ergebniseingang> getLetzteErgebniseingaenge(Ergebniseingangsart ergebniseingangsart) {
    return letzterErgebniseingangNachEingansart.computeIfAbsent(ergebniseingangsart, x -> letzterErgebniseingangLoader.load(ergebniseingangsart).stream()
        .collect(Collectors.toMap(AbstractErgebniseingang::getGebiet, ergebniseingang -> ergebniseingang, (a, b) -> b)));
  }

  private final Map<Ergebniseingangsart, Map<Gebiet, ErgebnisGueltigerEingang>> letzterErgebnisGueltigerEingangNachEingansart = new EnumMap<>(
      Ergebniseingangsart.class);

  public Map<Gebiet, ErgebnisGueltigerEingang> getLetzteErgebnisGueltigerEingang(Ergebniseingangsart ergebniseingangsart) {
    return letzterErgebnisGueltigerEingangNachEingansart.computeIfAbsent(ergebniseingangsart,
        x -> ergebnisGueltigerEingangLoader.load(ergebniseingangsart).stream()
            .collect(Collectors.toMap(AbstractErgebnisGueltigerEingang::getGebiet, ergebnisGueltigerEingang -> ergebnisGueltigerEingang, (a, b) -> b)));
  }

  public Optional<Ergebniseingang> findLetzterErgebniseingang(List<Wahldurchfuehrungsgebiet> gebiete) {
    return gebiete.stream().flatMap(gebiet -> getLetzteErgebnisGueltigerEingaengeUnterhalbVon(gebiet).values().stream()) //
        .filter(Objects::nonNull) //
        .map(ErgebnisGueltigerEingang::getErgebniseingang) //
        .sorted(ErgebniseingangByZeitstempelComparator.DESCENDING) //
        .findFirst();
  }

  public Map<Wahldurchfuehrungsgebiet, ErgebnisGueltigerEingang> getLetzteErgebnisGueltigerEingaengeUnterhalbVon(Wahldurchfuehrungsgebiet gebiet) {
    Set<Wahldurchfuehrungsgebiet> erfassungsgebiete = new HashSet<>(getErfassungsgebieteUnterhalbVon(gebiet));

    Map<Wahldurchfuehrungsgebiet, ErgebnisGueltigerEingang> result = new HashMap<>();

    for (var entry : getLetzteErgebnisGueltigerEingang(
        Ergebniseingangsart.getErgebniseingangsartFuerErgebnisArt(getErgebnis().getErgebnisart())).entrySet()) {
      var wahldurchfuehrungsgebiet = entry.getKey().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
      if (erfassungsgebiete.contains(wahldurchfuehrungsgebiet)) {
        result.put(wahldurchfuehrungsgebiet, entry.getValue());
      }
    }

    return result;
  }

  protected ParameterLoader<Ergebniseingang, Ergebniseingangsart> getLetzterErgebniseingangLoader() {
    return letzterErgebniseingangLoader;
  }

  protected ParameterLoader<ErgebnisGueltigerEingang, Ergebniseingangsart> getErgebnisGueltigerEingangLoader() {
    return ergebnisGueltigerEingangLoader;
  }

  protected Loader<WahlbeteiligungAuswertung> getWahlbeteiligungAuswertungenLoader() {
    return wahlbeteiligungAuswertungenLoader;
  }

  public List<WahlbeteiligungAuswertung> getWahlbeteiligungAuswertungen() {
    if (wahlbeteiligungAuswertungen == null) {
      wahlbeteiligungAuswertungen = wahlbeteiligungAuswertungenLoader.load();
    }
    return wahlbeteiligungAuswertungen;
  }

  @Override
  public Wahldaten<Wahldurchfuehrung, Wahldurchfuehrungsgebiet> getWahldaten() {
    return this;
  }

  protected StimmeingabeErfassenDescription getStimmeingabeErfassenDescription() {
    if (stimmeingabeErfassenDescription == null) {
      stimmeingabeErfassenDescription = getStimmeingabeErfassenDescriptionResolver().getStimmeingabeErfassenDescriptionByWahl(getWahl());
    }

    return stimmeingabeErfassenDescription;
  }

  public StimmeingabeErfassenDescriptionResolver getStimmeingabeErfassenDescriptionResolver() {
    return stimmeingabeErfassenDescriptionResolver;
  }

  @Override
  public Long getStimmenFuerListenkandidatur(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerListenkandidatur(listenkandidatur, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmenFuerListenkandidatur(Listenkandidatur listenkandidatur, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenListenkandidaturen().getStimmenFuerListenkandidatur(listenkandidatur, gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmenNurListenkandidaturen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenNurListenkandidaturen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmenNurListenkandidaturen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenListenkandidaturen().getStimmenFuerListenkandidaturen(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmenNurListenkandidaturen(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenNurListenkandidaturen(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmenNurListenkandidaturen(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenListenkandidaturen().getStimmenFuerListenkandidaturen(liste, gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmenNurListe(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenNurListe(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmenNurListe(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    Long result = getZweitstimmenAktuell(gebietUndStimmabgabeart).values().stream().filter(Objects::nonNull).mapToLong(Long::longValue).sum();

    if (result > 0) {
      var stimmenNurListenkandidaturen = getStimmenNurListenkandidaturen(gebietUndStimmabgabeart);

      if (stimmenNurListenkandidaturen != null) {
        result = Math.max(0, result - stimmenNurListenkandidaturen);
      }
    }

    return result;
  }

  @Override
  public Long getStimmenNurListe(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenNurListe(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmenNurListe(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    Long result = null;

    if (liste != null) {
      result = getZweitstimmenAktuell(liste.getGruppe(), gebietUndStimmabgabeart);

      if (result != null && result > 0) {
        var stimmenNurListenkandidaturen = getStimmenNurListenkandidaturen(liste, gebietUndStimmabgabeart);

        if (stimmenNurListenkandidaturen != null) {
          result = Math.max(0, result - stimmenNurListenkandidaturen);
        }
      }
    }

    return result;
  }

  @Override
  public Long getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(liste, gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelNurEinzelnerWahlvorschlagVeraendert(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelNurEinzelnerWahlvorschlagVeraendert(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelNurEinzelnerWahlvorschlagVeraendert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(liste, gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(liste, gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelNurEinzelnerWahlvorschlagVeraendert(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelNurEinzelnerWahlvorschlagVeraendert(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelNurEinzelnerWahlvorschlagVeraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelKeinEinzelnerWahlvorschlagVeraendert(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelKeinEinzelnerWahlvorschlagVeraendert(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelKeinEinzelnerWahlvorschlagVeraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelKeinEinzelnerWahlvorschlagPanaschiert(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelKeinEinzelnerWahlvorschlagPanaschiert(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelKeinEinzelnerWahlvorschlagPanaschiert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelUngueltig(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelUngueltig(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelUngueltig(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellUngueltig(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelUnbestimmt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelUnbestimmt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelUnbestimmt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellUnbestimmt(gebietUndStimmabgabeart);
  }

  @Override
  public Long getStimmzettelGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmzettelGesamt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT));
  }

  public Long getStimmzettelGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenStimmzettel().getStimmzettelAktuellGesamt(gebietUndStimmabgabeart);
  }

  private Map<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuerGebiete;

  public List<Wahldurchfuehrungsgebiet> getErfasstFuerGebiete(Gebiet gebiet) {
    if (erfasstFuerGebiete == null) {
      erfasstFuerGebiete = new HashMap<>();

      for (var wahldurchfuehrungsgebiet : getGebiete()) {
        if (wahldurchfuehrungsgebiet.getErfasstIn() != null) {
          erfasstFuerGebiete.computeIfAbsent(wahldurchfuehrungsgebiet.getErfasstIn(), x -> new ArrayList<>()).add(wahldurchfuehrungsgebiet);
        }
      }
      for (var value : erfasstFuerGebiete.values()) {
        value.sort(GebietByBreadthFirstComparator.INSTANCE);
      }
    }
    return erfasstFuerGebiete.getOrDefault(gebiet, Collections.emptyList());
  }

  private final transient Map<Gebiet, Set<Wahldurchfuehrungsgebiet>> erfassungsgebieteMitAbweichendenAWertenUnterhalbVon = new HashMap<>();

  public Set<Wahldurchfuehrungsgebiet> gerErfassungsgebieteMitAbweichendenAWertenUnterhalbVon(@Nullable Gebiet uebergeordnetesGebiet) {
    var uebergeornet = bestimmeUebergeornet(uebergeordnetesGebiet);

    return erfassungsgebieteMitAbweichendenAWertenUnterhalbVon.computeIfAbsent(uebergeornet, x -> {
      Map<Wahldurchfuehrungsgebiet, Set<Wahldurchfuehrungsgebiet>> erfassteGebieteNachGebiet = new HashMap<>();

      for (var erfassungsgebiet : getGebiete()) {
        if (erfassungsgebiet.isErfassungsgebietLautGebietsart()) {
          if (erfassungsgebiet.isInGebietshierarchieUntergeordnetesGebietVon(uebergeornet) || erfassungsgebiet.getErfasstIn() != null) {
            if (erfassungsgebiet.getErfasstIn() == null) {
              erfassteGebieteNachGebiet.computeIfAbsent(erfassungsgebiet, ignore -> new HashSet<>()).add(erfassungsgebiet);
            } else {
              if (erfassungsgebiet.getErfasstIn().isInGebietshierarchieUntergeordnetesGebietVon(uebergeornet)) {
                erfassteGebieteNachGebiet.computeIfAbsent(erfassungsgebiet.getErfasstIn(), ignore -> new HashSet<>()).add(erfassungsgebiet);
              }
            }
          }
        }
      }

      return erfassteGebieteNachGebiet.entrySet().stream().filter(entry -> isAwerteAbweichendErfasst(entry.getValue())).map(Map.Entry::getKey)
          .collect(Collectors.toSet());
    });

  }

  private Gebiet bestimmeUebergeornet(Gebiet uebergeordnetesGebiet) {
    if (uebergeordnetesGebiet == null) {
      return getWurzelgebiet();
    }

    return uebergeordnetesGebiet;
  }

  private boolean isAwerteAbweichendErfasst(Set<Wahldurchfuehrungsgebiet> erfassteGebiete) {
    return isAwerteAbweichend(erfassteGebiete, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteOhneWahlschein,
        Stimmenart.OHNE_WAHLSCHEIN) || isAwerteAbweichend(erfassteGebiete, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteMitWahlschein,
        Stimmenart.MIT_WAHLSCHEIN) || isAwerteAbweichend(erfassteGebiete, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteNichtImWaehlerverzeichnis,
        Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
  }

  private boolean isAwerteAbweichend(Set<Wahldurchfuehrungsgebiet> erfassteGebiete, Function<Wahldurchfuehrungsgebiet, Long> amGebietHinterlegt, Stimmenart stimmenart) {
    Long anzahlAwerteHinterlegt = null;
    for (var gebiet : erfassteGebiete) {
      var anzahlWahlberechtigte = amGebietHinterlegt.apply(gebiet);
      if (anzahlAwerteHinterlegt == null) {
        anzahlAwerteHinterlegt = anzahlWahlberechtigte;
      } else {
        if (anzahlWahlberechtigte != null) {
          anzahlAwerteHinterlegt += anzahlWahlberechtigte;
        }
      }
    }

    if (anzahlAwerteHinterlegt != null) {
      Long anzahlErfasst = null;
      for (var gebiet : erfassteGebiete) {
        var erfassteAnzahlImGebiet = bestimmeWahlberechtigte(gebiet, g -> null /* explizit nur die erfassten Werte betrachten */, stimmenart);
        if (anzahlErfasst == null) {
          anzahlErfasst = erfassteAnzahlImGebiet;
        } else {
          if (erfassteAnzahlImGebiet != null) {
            anzahlErfasst += erfassteAnzahlImGebiet;
          }
        }
      }
      if (anzahlErfasst != null) {
        return !anzahlAwerteHinterlegt.equals(anzahlErfasst);
      }
    }

    return false;
  }

  protected Loader<Niederschrift> getNiederschriftLoader() {
    return niederschriftLoader;
  }

  public List<Niederschrift> getNiederschriften() {
    if (nierderschriften == null) {
      nierderschriften = niederschriftLoader.load();
    }
    return nierderschriften;
  }

}
