
package de.ivu.elect.dokumente.stimmeingabe;

import static de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte.berechneStimmzettelGueltig;
import static de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte.berechneStimmzettelUnbestimmt;
import static de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte.berechneStimmzettelUngueltig;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.STIMMEINGABE_WAHLART_ERGEBNISART_VORLAEUFIG_ERFASSUNGSART;
import static java.util.stream.Collectors.toMap;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenGueltige;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenStimmzettel;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenWaehler;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolverContext;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeGruppeStimmenErfassenModelBuilder;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeKopfzahlErfassenModelBuilder;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeListenkandidaturStimmenErfassenModelBuilder;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeReferendumOptionStimmenErfassenModelBuilder;
import de.ivu.elect.business.stimmeingabe.control.AbstractStimmeingabeErfassenContext;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeDocumentErgebnisDaten;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeErfassenContext;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeErfassenModelBuilderResolver;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeGruppeStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeGruppeStimmenErfassenLabelGeneratorFinder;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlErfassenLabelGeneratorFinder;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeListenkandidaturStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFinder;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeReferendumOptionStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder;
import de.ivu.elect.business.stimmeingabe.control.plausi.WahlvorschlaegeSupplier;
import de.ivu.elect.business.stimmeingabe.entity.ErgebnisGueltigerEingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeGruppeStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeGruppeStimmenErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeKopfzahlErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmenErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeReferendumOptionStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeReferendumVorlageStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeReferendumVorlageStimmenErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeStimmzettel;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelDAO;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisartVorlaeufigErfassungsart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;

public abstract class AbstractWasStimmeingabeDocumentFactory extends AbstractStimmeingabeDocumentFactory<Wahldurchfuehrungsgebiet> {

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  private transient StimmzettelDAO stimmzettelDAO;

  @Inject
  private transient StimmeingabeErfassenModelBuilderResolver stimmeingabeErfassenModelBuilderResolver;

  @Inject
  private transient StimmeingabeGruppeStimmenErfassenLabelGeneratorFinder stimmeingabeGruppeStimmenErfassenLabelGeneratorFinder;

  @Inject
  private transient StimmeingabeKopfzahlErfassenLabelGeneratorFinder stimmeingabeKopfzahlErfassenLabelGeneratorFinder;

  @Inject
  private transient StimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFinder stimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFinder;

  @Inject
  private transient StimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder stimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder;

  protected AbstractWasStimmeingabeDocumentFactory() {
    super();
  }

  /**
   * Erzeugung des Dokuments aus Ergebniseingang
   */
  public StimmeingabeDocument createDocumentFuerErfassungsgebiet(StimmeingabeDocumentContext stimmeingabeDocumentContext, Ergebniseingang ergebniseingang) {
    StimmeingabeDocument stimmeingabeDocument = createStimmeingabeDocument(stimmeingabeDocumentContext);
    stimmeingabeDocument.documentPerWahlbezirk = true;

    WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext = new WahlverwaltungGebietInformationResolverContext(ergebniseingang.getWahldurchfuehrung(),
        stimmeingabeDocumentContext.getWahlParameterLoader(), gebietDAO, gebietsgruppierungDAO);

    fillInGebietDatenFuerWahlbezirk(stimmeingabeDocument, stimmeingabeDocumentContext, wahlverwaltungGebietInformationResolverContext, ergebniseingang.getWahldurchfuehrungsgebiet());

    StimmeingabeErfassenContext stimmeingabeErfassenContext = createStimmeingabeDocumentByErgebniseingangExportContext(ergebniseingang);

    fillInErgebniseingang(stimmeingabeDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext, ergebniseingang);

    return stimmeingabeDocument;
  }

  /**
   * @see AbstractWasStimmeingabeDocumentFactory#fillInErgebniseingang
   */
  @Override
  protected void fillInErgebnisDaten(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahldurchfuehrungsgebiet gebiet,
      StimmeingabeDocumentErgebnisDaten<Wahldurchfuehrungsgebiet> ergebnisDaten) {
    super.fillInErgebnisDaten(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten);

    fillInErgebnisDatenSystemGruppeStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten);

    ErgebnisDaten wasErgebnisDaten = (ErgebnisDaten) ergebnisDaten;
    WahlartErgebnisart wahlartErgebnisart = wasErgebnisDaten.getErgebnis().getErgebnisart();

    // ELECT-13015
    stimmeingabeDocument.wahlartUmgangWahlberechtigte = wahlartErgebnisart.isEndgueltig() ? stimmeingabeDocument.wahlartUmgangWahlberechtigteEndgueltig : stimmeingabeDocument.wahlartUmgangWahlberechtigteVorlaeufig;
    stimmeingabeDocument.wahlartUmgangWaehler = wahlartErgebnisart.isEndgueltig() ? stimmeingabeDocument.wahlartUmgangWaehlerEndgueltig : stimmeingabeDocument.wahlartUmgangWaehlerVorlaeufig;

    // ELECT-14019
    Optional<WahlartErgebnisartVorlaeufigErfassungsart> ergebniseingangArtVorlaeufigErfassungsartIfConfigured = Optional.ofNullable(stimmeingabeDocumentContext.getWahlParameterLoader()
        .resolveEnumValue(stimmeingabeDocumentContext.getWahl(), STIMMEINGABE_WAHLART_ERGEBNISART_VORLAEUFIG_ERFASSUNGSART, WahlartErgebnisartVorlaeufigErfassungsart.class));

    stimmeingabeDocument.ergebniseingangArtVorlaeufig = wahlartErgebnisart.isVorlaeufig();
    stimmeingabeDocument.ergebniseingangArtEndgueltig = wahlartErgebnisart.isEndgueltig();
    stimmeingabeDocument.ergebniseingangsartEndgueltig = stimmeingabeDocument.ergebniseingangArtEndgueltig;
    stimmeingabeDocument.ergebniseingangArtNurTrendmeldung = ergebniseingangArtVorlaeufigErfassungsartIfConfigured.map(WahlartErgebnisartVorlaeufigErfassungsart::isTrendmeldung).orElse(false);

    for (ErgebnisGueltigerEingang ergebnisGueltigerEingang : wasErgebnisDaten.getGueltigerEingaenge()) {
      if (Objects.equals(ergebnisGueltigerEingang.getGebiet(), gebiet)) {
        Ergebniseingang ergebniseingang = ergebnisGueltigerEingang.getErgebniseingang();

        stimmeingabeDocument.ergebniseingangErfasser = ergebniseingang.getErfasser();
        stimmeingabeDocument.ergebniseingangZeitstempel = stimmeingabeDocumentContext.formatDateTimeMedium(ergebniseingang.getZeitstempel());
      }
    }
  }

  @Override
  protected void fillInErgebnisDatenKopfzahlen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahldurchfuehrungsgebiet gebiet,
      StimmeingabeDocumentErgebnisDaten<Wahldurchfuehrungsgebiet> ergebnisDaten) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebiet.getPk());

    Long anzahlWahlberechtigteGesamt = ergebnisDaten.getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahlGesamt(wahldurchfuehrungsgebiet); // Hier nur A1 und A2 gemeint
    Long anzahlWahlberechtigteOhneWahlschein = ergebnisDaten.getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(wahldurchfuehrungsgebiet); // A1
    Long anzahlWahlberechtigteMitWahlschein = ergebnisDaten.getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(wahldurchfuehrungsgebiet); // A2
    Long anzahlWahlberechtigteNichtImWaehlerverzeichnis = ergebnisDaten.getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVonAnzahl(wahldurchfuehrungsgebiet); // A3

    ErgebnisDatenWaehler ergebnisDatenWaehler = ((ErgebnisDaten) ergebnisDaten).getErgebnisDatenWaehler();

    Long anzahlWaehlerOhneWahlschein = ergebnisDatenWaehler.getSummeWaehlerOhneWahlschein(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
    Long anzahlWaehlerMitWahlschein = ergebnisDatenWaehler.getSummeWaehlerMitWahlschein(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
    Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = ergebnisDatenWaehler.getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
    Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = ergebnisDatenWaehler.getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
    Long anzahlWaehlerMitVollmacht = ergebnisDatenWaehler.getSummeWaehlerMitVollmacht(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
    Long anzahlWaehlerGesamt = ergebnisDatenWaehler.getSummeWaehlerGesamt(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);

    fillInKopfzahlen(stimmeingabeDocument, stimmeingabeDocumentContext, wahldurchfuehrungsgebiet, anzahlWahlberechtigteGesamt, anzahlWahlberechtigteOhneWahlschein, anzahlWahlberechtigteMitWahlschein,
        anzahlWahlberechtigteNichtImWaehlerverzeichnis, anzahlWaehlerGesamt, anzahlWaehlerOhneWahlschein, anzahlWaehlerMitWahlschein, anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl,
        anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, anzahlWaehlerMitVollmacht);
  }

  protected void fillInKopfzahlen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahldurchfuehrungsgebiet gebiet, Long anzahlWahlberechtigteGesamt,
      Long anzahlWahlberechtigteOhneWahlschein, Long anzahlWahlberechtigteMitWahlschein, Long anzahlWahlberechtigteNichtImWaehlerverzeichnis, Long anzahlWaehlerGesamt,
      Long anzahlWaehlerOhneWahlschein, Long anzahlWaehlerMitWahlschein, Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl, Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl,
      Long anzahlWaehlerMitVollmacht) {
    fillInKopfzahlen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, anzahlWahlberechtigteGesamt, anzahlWahlberechtigteOhneWahlschein, anzahlWahlberechtigteMitWahlschein,
        anzahlWahlberechtigteNichtImWaehlerverzeichnis, anzahlWaehlerGesamt, anzahlWaehlerOhneWahlschein, anzahlWaehlerMitWahlschein, anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl,
        anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, anzahlWaehlerMitVollmacht, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteOhneWahlschein,
        Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteMitWahlschein, Wahldurchfuehrungsgebiet::getAnzahlWahlberechtigteNichtImWaehlerverzeichnis);
  }

  /**
   * @see AbstractWasStimmeingabeDocumentFactory#fillInErgebnisDaten
   */
  private void fillInErgebniseingang(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, StimmeingabeErfassenContext stimmeingabeErfassenContext,
      Ergebniseingang ergebniseingang) {
    fillInErgebniseingangErfassteStimmzettel(stimmeingabeDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext, ergebniseingang);

    WahlartErgebnisart wahlartErgebnisart = ergebniseingang.getErgebniseingangsart();

    // ELECT-13015
    stimmeingabeDocument.wahlartUmgangWahlberechtigte = wahlartErgebnisart.isEndgueltig() ? stimmeingabeDocument.wahlartUmgangWahlberechtigteEndgueltig : stimmeingabeDocument.wahlartUmgangWahlberechtigteVorlaeufig;
    stimmeingabeDocument.wahlartUmgangWaehler = wahlartErgebnisart.isEndgueltig() ? stimmeingabeDocument.wahlartUmgangWaehlerEndgueltig : stimmeingabeDocument.wahlartUmgangWaehlerVorlaeufig;

    // ELECT-14019
    Optional<WahlartErgebnisartVorlaeufigErfassungsart> ergebniseingangArtVorlaeufigErfassungsartIfConfigured = Optional.ofNullable(stimmeingabeDocumentContext.getWahlParameterLoader()
        .resolveEnumValue(stimmeingabeDocumentContext.getWahl(), STIMMEINGABE_WAHLART_ERGEBNISART_VORLAEUFIG_ERFASSUNGSART, WahlartErgebnisartVorlaeufigErfassungsart.class));

    stimmeingabeDocument.ergebniseingangArtVorlaeufig = wahlartErgebnisart.isVorlaeufig();
    stimmeingabeDocument.ergebniseingangArtEndgueltig = wahlartErgebnisart.isEndgueltig();
    stimmeingabeDocument.ergebniseingangsartEndgueltig = stimmeingabeDocument.ergebniseingangArtEndgueltig;
    stimmeingabeDocument.ergebniseingangArtNurTrendmeldung = ergebniseingangArtVorlaeufigErfassungsartIfConfigured.map(WahlartErgebnisartVorlaeufigErfassungsart::isTrendmeldung).orElse(false);

    stimmeingabeDocument.ergebniseingangErfasser = ergebniseingang.getErfasser();
    stimmeingabeDocument.ergebniseingangZeitstempel = stimmeingabeDocumentContext.formatDateTimeMedium(ergebniseingang.getZeitstempel());

    fillInErgebniseingangKopfzahlen(stimmeingabeDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext);

    if (stimmeingabeDocumentContext.getWahl().isReferendum()) {
      fillInErgebniseingangReferendumStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext);
    } else {
      fillInErgebniseingangStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext);
    }
  }

  private void fillInErgebniseingangErfassteStimmzettel(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      StimmeingabeErfassenContext stimmeingabeErfassenContext, Ergebniseingang ergebniseingang) {
    fillInErfassteStimmzettel(stimmeingabeDocument, stimmeingabeDocumentContext, ergebniseingang.getStimmzettel(stimmzettelDAO));
  }

  private void fillInErgebniseingangKopfzahlen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    StimmeingabeKopfzahlErfassenModel model = createStimmeingabeKopfzahlErfassenModel(stimmeingabeErfassenContext);

    Long anzahlWahlberechtigteOhneWahlschein = null;
    Long anzahlWahlberechtigteMitWahlschein = null;
    Long anzahlWahlberechtigteNichtImWaehlerverzeichnis = null;
    Long anzahlWahlberechtigteGesamt = null;

    if (model.hasWahlberechtigteOhneWahlschein()) {
      anzahlWahlberechtigteOhneWahlschein = model.getWahlberechtigteOhneWahlschein().getAnzahl();
    }

    if (model.hasWahlberechtigteMitWahlschein()) {
      anzahlWahlberechtigteMitWahlschein = model.getWahlberechtigteMitWahlschein().getAnzahl();
    }

    if (model.hasWahlberechtigteNichtImWaehlerverzeichnis()) {
      anzahlWahlberechtigteNichtImWaehlerverzeichnis = model.getWahlberechtigteNichtImWaehlerverzeichnis().getAnzahl();
    }

    if (model.hasWahlberechtigteGesamt()) {
      anzahlWahlberechtigteGesamt = model.getWahlberechtigteGesamt().getAnzahl();
    }

    Long anzahlWaehlerOhneWahlschein = null;
    Long anzahlWaehlerMitWahlschein = null;
    Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = null;
    Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = null;
    Long anzahlWaehlerMitVollmacht = null;
    Long anzahlWaehlerGesamt = null;

    if (model.hasWaehlerOhneWahlschein()) {
      anzahlWaehlerOhneWahlschein = model.getWaehlerOhneWahlschein().getAnzahl();
    }

    if (model.hasWaehlerMitWahlschein()) {
      anzahlWaehlerMitWahlschein = model.getWaehlerMitWahlschein().getAnzahl();
    }

    if (model.hasWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl()) {
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = model.getWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl().getAnzahl();
    }

    if (model.hasWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl()) {
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = model.getWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl().getAnzahl();
    }

    if (model.hasWaehlerMitVollmacht()) {
      anzahlWaehlerMitVollmacht = model.getWaehlerMitVollmacht().getAnzahl();
    }

    if (model.hasWaehlerGesamt()) {
      anzahlWaehlerGesamt = model.getWaehlerGesamt().getAnzahl();
    }

    fillInKopfzahlen(stimmeingabeDocument, stimmeingabeDocumentContext, model.getBezugGebiet(), anzahlWahlberechtigteGesamt, anzahlWahlberechtigteOhneWahlschein, anzahlWahlberechtigteMitWahlschein,
        anzahlWahlberechtigteNichtImWaehlerverzeichnis, anzahlWaehlerGesamt, anzahlWaehlerOhneWahlschein, anzahlWaehlerMitWahlschein, anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl,
        anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, anzahlWaehlerMitVollmacht);
  }

  private void fillInErgebniseingangStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    EntityAufStimmzettelDaten entityAufStimmzettelDaten = stimmeingabeErfassenContext.getEntityAufStimmzettelDaten();

    StimmeingabeGruppeStimmenErfassenModel model = createStimmeingabeGruppeStimmenErfassenModel(stimmeingabeErfassenContext);

    for (StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen : extractStimmeingabenGruppeStimmen(model, stimmeingabeErfassenContext)) {
      GruppeImGebiet gruppeImGebiet = stimmeingabeGruppeStimmen.getGruppeImGebiet();

      if (gruppeImGebiet != null) {
        Gebiet gebiet = gruppeImGebiet.getGebiet();

        WahlvorschlagStimmzettelPosition wahlvorschlagStimmzettelPosition = null;

        Direktkandidat direktkandidat = null;
        Long anzahlDirektstimmen = stimmeingabeGruppeStimmen.hasAnzahlDirektstimme() ? stimmeingabeGruppeStimmen.getAnzahlDirektstimme() : null;

        Liste liste = null;
        Long anzahlListenstimmen = stimmeingabeGruppeStimmen.hasAnzahlListenstimme() ? stimmeingabeGruppeStimmen.getAnzahlListenstimme() : null;
        Long anzahlListenstimmenNurListe = anzahlListenstimmen;
        Long anzahlListenstimmenNurListenkandidaturen = null;

        Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = null;
        Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert = null;
        Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert = null;

        if (stimmeingabeGruppeStimmen instanceof StimmeingabeStimmzettel) {
          StimmeingabeStimmzettel stimmeingabeStimmzettel = (StimmeingabeStimmzettel) stimmeingabeGruppeStimmen;

          anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = stimmeingabeStimmzettel.hasAnzahlNurEinzelnerWahlvorschlagUnveraendert() ? stimmeingabeStimmzettel.getAnzahlNurEinzelnerWahlvorschlagUnveraendert() : null;
          anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert = stimmeingabeStimmzettel.hasAnzahlNurEinzelnerWahlvorschlagVeraendert() ? stimmeingabeStimmzettel.getAnzahlNurEinzelnerWahlvorschlagVeraendert() : null;
          anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert = stimmeingabeStimmzettel.hasAnzahlNurEinzelnerWahlvorschlagPanaschiert() ? stimmeingabeStimmzettel.getAnzahlNurEinzelnerWahlvorschlagPanaschiert() : null;
        }

        if (hasStimmeingabeDirektstimme(stimmeingabeGruppeStimmen)) {
          direktkandidat = gruppeImGebiet.getErstenDirektkandidatAbgeleitet(entityAufStimmzettelDaten);

          if (direktkandidat == null) {
            throw new IllegalStateException("Eine Gruppe hat keinen Direktkandidaten als Wahlvorschlag! [gruppeImGebiet = " + gruppeImGebiet + "]");
          }

          wahlvorschlagStimmzettelPosition = direktkandidat.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten);

          KandidatStimmenDocument direktkandidatStimmenDocument = createErgebniseingangDirektkandidatStimmenDocument(stimmeingabeDocumentContext, stimmeingabeErfassenContext,
              stimmeingabeGruppeStimmen, gruppeImGebiet, direktkandidat, wahlvorschlagStimmzettelPosition, anzahlDirektstimmen);

          stimmeingabeDocument.direktkandidaten.add(direktkandidatStimmenDocument);
        }

        if (hasStimmeingabeListenstimme(stimmeingabeGruppeStimmen)) {
          liste = gruppeImGebiet.getErsteListeAbgeleitet(entityAufStimmzettelDaten);

          if (liste == null) {
            throw new IllegalStateException("Eine Gruppe hat keinen Liste als Wahlvorschlag! [gruppeImGebiet = " + gruppeImGebiet + "]");
          }

          wahlvorschlagStimmzettelPosition = liste.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten);

          StimmeingabeListenkandidaturStimmenErfassenModel listenkandidaturStimmenErfassenModel = null;

          if (liste.getWahl().isWahlMitListenkandidatenStimmen()) {
            listenkandidaturStimmenErfassenModel = createStimmeingabeListenkandidaturStimmenErfassenModel(stimmeingabeErfassenContext, gruppeImGebiet);

            if (listenkandidaturStimmenErfassenModel != null) {
              anzahlListenstimmenNurListenkandidaturen = listenkandidaturStimmenErfassenModel.getListenkandidaturStimmen().stream().filter(StimmeingabeListenkandidaturStimmen::hasStimmeingaben)
                  .mapToLong(StimmeingabeListenkandidaturStimmen::getAnzahl).sum();

              if (anzahlListenstimmen != null) {
                anzahlListenstimmenNurListe = Math.max(0, anzahlListenstimmen - anzahlListenstimmenNurListenkandidaturen);
              }
            }
          }

          ListeStimmenDocument listeStimmenDocument = createErgebniseingangListeStimmenDocument(stimmeingabeDocumentContext, stimmeingabeErfassenContext, stimmeingabeGruppeStimmen, gruppeImGebiet,
              liste, wahlvorschlagStimmzettelPosition, anzahlListenstimmen, anzahlListenstimmenNurListe, anzahlListenstimmenNurListenkandidaturen,
              anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);

          stimmeingabeDocument.listen.add(listeStimmenDocument);

          if (listenkandidaturStimmenErfassenModel != null) {
            List<KandidatStimmenDocument> listenkandidatStimmenDocuments = createErgebniseingangListenkandidatStimmenDocuments(listeStimmenDocument, stimmeingabeDocumentContext,
                stimmeingabeErfassenContext, stimmeingabeGruppeStimmen, gruppeImGebiet, liste, wahlvorschlagStimmzettelPosition, listenkandidaturStimmenErfassenModel);

            listeStimmenDocument.listenkandidaturen.addAll(listenkandidatStimmenDocuments);
          }
        }

        if (direktkandidat != null || liste != null) {
          // PolitischeGruppeStimmenDocument als letztes füllen, da die Information direktstimmenErfassbar/listenstimmenErfassbar benötigt wird!
          GruppeStimmenDocument politischeGruppeStimmenDocument = createErgebniseingangPolitischeGruppeStimmenDocument(stimmeingabeDocumentContext, stimmeingabeErfassenContext,
              stimmeingabeGruppeStimmen, gruppeImGebiet, wahlvorschlagStimmzettelPosition, direktkandidat, anzahlDirektstimmen, liste, anzahlListenstimmen, anzahlListenstimmenNurListe,
              anzahlListenstimmenNurListenkandidaturen, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
              anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);

          stimmeingabeDocument.politischeGruppen.add(politischeGruppeStimmenDocument);
        }
      }
    }

    fillInErgebniseingangSystemGruppeStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext, model);

    sortByPosition(stimmeingabeDocument.politischeGruppen);
    sortByPosition(stimmeingabeDocument.direktkandidaten);
    sortByPosition(stimmeingabeDocument.listen);
  }

  protected ListeStimmenDocument createErgebniseingangListeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, StimmeingabeErfassenContext stimmeingabeErfassenContext,
      StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen, GruppeImGebiet gruppeImGebiet, Liste liste, WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Long anzahlStimmen,
      Long anzahlStimmenNurListe, Long anzahlStimmenNurListenkandidaturen, Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
      Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert) {
    return createListeStimmenDocument(stimmeingabeDocumentContext, stimmeingabeErfassenContext.getEntityAufStimmzettelDaten(), stimmeingabeErfassenContext.getGruppenImGebiet(), gruppeImGebiet, liste,
        listeStimmzettelPosition, anzahlStimmen, anzahlStimmenNurListe, anzahlStimmenNurListenkandidaturen, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert,
        anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);
  }

  protected List<KandidatStimmenDocument> createErgebniseingangListenkandidatStimmenDocuments(ListeStimmenDocument listeStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      StimmeingabeErfassenContext stimmeingabeErfassenContext, StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen, GruppeImGebiet gruppeImGebiet, Liste liste,
      WahlvorschlagStimmzettelPosition listeStimmzettelPosition, StimmeingabeListenkandidaturStimmenErfassenModel model) {
    Map<Listenkandidatur, Long> anzahlStimmenPerListenkandidatur = model.getListenkandidaturStimmen().stream() //
        .filter(StimmeingabeListenkandidaturStimmen::hasStimmeingaben) //
        .collect(toMap(StimmeingabeListenkandidaturStimmen::getListenkandidatur, StimmeingabeListenkandidaturStimmen::getAnzahl));

    return createListenkandidatStimmenDocuments(stimmeingabeDocumentContext, stimmeingabeErfassenContext.getEntityAufStimmzettelDaten(), stimmeingabeErfassenContext.getGruppenImGebiet(),
        gruppeImGebiet, liste, listeStimmzettelPosition, anzahlStimmenPerListenkandidatur, listeStimmenDocument.stimmzettelWahlvorschlagUnveraendert /*TODO? ELECT-14352*/);
  }

  protected KandidatStimmenDocument createErgebniseingangDirektkandidatStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, StimmeingabeErfassenContext stimmeingabeErfassenContext,
      StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen, GruppeImGebiet gruppeImGebiet, Direktkandidat direktkandidat, WahlvorschlagStimmzettelPosition direktkandidatStimmzettelPosition,
      Long anzahlStimmenGesamt) {
    return createDirektkandidatStimmenDocument(stimmeingabeDocumentContext, stimmeingabeErfassenContext.getEntityAufStimmzettelDaten(), stimmeingabeErfassenContext.getGruppenImGebiet(),
        gruppeImGebiet, direktkandidat, direktkandidatStimmzettelPosition, anzahlStimmenGesamt);
  }

  protected GruppeStimmenDocument createErgebniseingangPolitischeGruppeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, StimmeingabeErfassenContext stimmeingabeErfassenContext,
      StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen, GruppeImGebiet gruppeImGebiet, WahlvorschlagStimmzettelPosition wahlvorschlagStimmzettelPosition, @Nullable Direktkandidat direktkandidat,
      @Nullable Long anzahlDirektstimmen, @Nullable Liste liste, @Nullable Long anzahlListenstimmen, @Nullable Long anzahlListenstimmenNurListe,
      @Nullable Long anzahlListenstimmenNurListenkandidaturen, @Nullable Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert,
      @Nullable Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, @Nullable Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert) {
    GruppeStimmenDocument gruppeStimmenDocument = createGruppeStimmenDocument(stimmeingabeDocumentContext);

    fillInPolitischeGruppeStimmen(gruppeStimmenDocument, stimmeingabeDocumentContext, stimmeingabeErfassenContext.getEntityAufStimmzettelDaten(), stimmeingabeErfassenContext.getGruppenImGebiet(),
        gruppeImGebiet, wahlvorschlagStimmzettelPosition, direktkandidat, anzahlDirektstimmen, liste, anzahlListenstimmen, anzahlListenstimmenNurListe, anzahlListenstimmenNurListenkandidaturen,
        anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);

    return gruppeStimmenDocument;
  }

  private void fillInErgebniseingangReferendumStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    EntityAufStimmzettelDaten entityAufStimmzettelDaten = stimmeingabeErfassenContext.getEntityAufStimmzettelDaten();

    List<StimmeingabeReferendumVorlageStimmen> stimmeingabenReferendumVorlageStimmen = createStimmeingabeReferendumVorlageStimmenErfassenModel(stimmeingabeErfassenContext)
        .getStimmenReferendumVorlage();

    List<GruppeImGebiet> referendumVorlagenImGebiet = stimmeingabenReferendumVorlageStimmen.stream() //
        .map(StimmeingabeReferendumVorlageStimmen::getGruppeImGebiet) //
        .collect(Collectors.toList());

    for (int i = 0; i < stimmeingabenReferendumVorlageStimmen.size(); i++) {
      StimmeingabeReferendumVorlageStimmen referendumVorlageStimmen = stimmeingabenReferendumVorlageStimmen.get(i);

      Long anzahlStimmenGueltig = null;
      Long anzahlStimmenUngueltig = null;
      Long anzahlStimmenAbgegeben = null;

      List<StimmeingabeReferendumOptionStimmen> stimmeingabenReferendumOptionStimmen = referendumVorlageStimmen.getStimmeingabeReferendumOptionStimmen();
      Map<ReferendumOption, Long> anzahlStimmenPerReferendumOption = new LinkedHashMap<>();

      for (StimmeingabeReferendumOptionStimmen stimmeingabeReferendumOptionStimmen : stimmeingabenReferendumOptionStimmen) {
        ReferendumOption referendumOption = stimmeingabeReferendumOptionStimmen.getReferendumOption();

        if (stimmeingabeReferendumOptionStimmen.isStimmeingabeZwischensummeAnzahlGueltigeGesamt()) {
          anzahlStimmenGueltig = stimmeingabeReferendumOptionStimmen.getAnzahl();
        } else if (stimmeingabeReferendumOptionStimmen.isStimmeingabeZwischensummeAnzahlUngueltigeGesamt()) {
          anzahlStimmenUngueltig = stimmeingabeReferendumOptionStimmen.getAnzahl();
        } else if (stimmeingabeReferendumOptionStimmen.isStimmeingabeZwischensummeAnzahlAbgegebeneGesamt()) {
          anzahlStimmenAbgegeben = stimmeingabeReferendumOptionStimmen.getAnzahl();
        } else {
          if (referendumOption != null) {
            Long anzahlStimmen = stimmeingabeReferendumOptionStimmen.getAnzahl();

            anzahlStimmenPerReferendumOption.put(referendumOption, anzahlStimmen);
          }
        }
      }

      if (anzahlStimmenAbgegeben == null) { // ELECT-6385
        if (stimmeingabeDocument.waehlerGesamt != null && stimmeingabeDocument.waehlerGesamt > 0) {
          anzahlStimmenAbgegeben = stimmeingabeDocument.waehlerGesamt;
        } else if (stimmeingabeDocument.stimmzettelGesamt != null && stimmeingabeDocument.stimmzettelGesamt > 0) {
          anzahlStimmenAbgegeben = stimmeingabeDocument.stimmzettelGesamt;
        } else if (anzahlStimmenGueltig != null && anzahlStimmenUngueltig != null) {
          anzahlStimmenAbgegeben = anzahlStimmenGueltig + anzahlStimmenUngueltig;
        }
      }

      fillInReferendumStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageStimmen.getGruppeImGebiet(), i + 1L,
          anzahlStimmenGueltig, anzahlStimmenUngueltig, anzahlStimmenAbgegeben, anzahlStimmenPerReferendumOption);
    }

    sortByPosition(stimmeingabeDocument.referendumVorlagen);
  }

  protected List<StimmeingabeGruppeStimmen> extractStimmeingabenGruppeStimmen(StimmeingabeGruppeStimmenErfassenModel model, StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    List<StimmeingabeGruppeStimmen> result = model.getStimmenPolitischeGruppen();

    if (result.isEmpty() && model.hasStimmenPolitischeGruppenReadonly()) { // ELECT-12656
      result = model.getStimmenPolitischeGruppenReadonly();
    }

    return result;
  }

  protected boolean hasStimmeingabeDirektstimme(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    return stimmeingabeGruppeStimmen.hasStimmeingabeDirektstimme();
  }

  protected boolean hasStimmeingabeListenstimme(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    return stimmeingabeGruppeStimmen.hasStimmeingabeListenstimme();
  }

  private void fillInErgebniseingangSystemGruppeStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      StimmeingabeErfassenContext stimmeingabeErfassenContext, StimmeingabeGruppeStimmenErfassenModel model) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = model.getBezugGebiet();
    Wahl wahl = wahldurchfuehrungsgebiet.getWahldurchfuehrung();
    WahlartErgebnisart ergebnisart = stimmeingabeErfassenContext.getErgebniseingangsart();

    Long anzahlDirektstimmenGueltig = null;
    Long anzahlDirektstimmenUngueltig = null;
    Long anzahlDirektstimmenUnbestimmt = null;
    Long anzahlDirektstimmenGesamt = null;

    Long anzahlListenstimmenGueltig = null;
    Long anzahlListenstimmenUngueltig = null;
    Long anzahlListenstimmenUnbestimmt = null;
    Long anzahlListenstimmenGesamt = null;

    if (model.hasStimmenGruppeGueltige()) {
      StimmeingabeGruppeStimmen stimmenGruppeGueltige = model.getStimmenGruppeGueltige();

      if (stimmenGruppeGueltige.hasAnzahlDirektstimme()) {
        anzahlDirektstimmenGueltig = stimmenGruppeGueltige.getAnzahlDirektstimme();
      }

      if (stimmenGruppeGueltige.hasAnzahlListenstimme()) {
        anzahlListenstimmenGueltig = stimmenGruppeGueltige.getAnzahlListenstimme();
      }
    }

    if (model.hasStimmenGruppeUngueltige()) {
      StimmeingabeGruppeStimmen stimmenGruppeUngueltige = model.getStimmenGruppeUngueltige();

      if (stimmenGruppeUngueltige.hasAnzahlDirektstimme()) {
        anzahlDirektstimmenUngueltig = stimmenGruppeUngueltige.getAnzahlDirektstimme();
      }

      if (stimmenGruppeUngueltige.hasAnzahlListenstimme()) {
        anzahlListenstimmenUngueltig = stimmenGruppeUngueltige.getAnzahlListenstimme();
      }
    }

    if (model.hasStimmenGruppeUnbestimmte()) {
      StimmeingabeGruppeStimmen stimmenGruppeUnbestimmte = model.getStimmenGruppeUnbestimmte();

      if (stimmenGruppeUnbestimmte.hasAnzahlDirektstimme()) {
        anzahlDirektstimmenUnbestimmt = stimmenGruppeUnbestimmte.getAnzahlDirektstimme();
      }

      if (stimmenGruppeUnbestimmte.hasAnzahlListenstimme()) {
        anzahlListenstimmenUnbestimmt = stimmenGruppeUnbestimmte.getAnzahlListenstimme();
      }
    }

    if (model.hasStimmenGruppeCustom()) {
      StimmeingabeGruppeStimmen stimmenGruppeCustom = model.getStimmenGruppeCustom();

      if (stimmenGruppeCustom.hasAnzahlDirektstimme()) {
        anzahlDirektstimmenGesamt = stimmenGruppeCustom.getAnzahlDirektstimme();
      }

      if (stimmenGruppeCustom.hasAnzahlListenstimme()) {
        anzahlListenstimmenGesamt = stimmenGruppeCustom.getAnzahlListenstimme();
      }
    }

    Long anzahlWaehlerGesamt = stimmeingabeDocument.waehlerGesamt;
    Long anzahlGueltige = anzahlDirektstimmenGueltig != null && anzahlDirektstimmenGueltig > 0 ? anzahlDirektstimmenGueltig : anzahlListenstimmenGueltig;
    Long anzahlUngueltige = anzahlDirektstimmenUngueltig != null && anzahlDirektstimmenUngueltig > 0 ? anzahlDirektstimmenUngueltig : anzahlListenstimmenUngueltig;
    Long anzahlUnbestimmte = anzahlDirektstimmenUnbestimmt != null && anzahlDirektstimmenUnbestimmt > 0 ? anzahlDirektstimmenUnbestimmt : anzahlListenstimmenUnbestimmt;

    Function<Gebiet, Long> anzahlWaehlerGesamtFct = ignored -> anzahlWaehlerGesamt;
    Function<Gebiet, Long> anzahlGueltigeFct = ignored -> anzahlGueltige;
    Function<Gebiet, Long> anzahlUngueltigeFct = ignored -> anzahlUngueltige;
    Function<Gebiet, Long> anzahlUnbestimmteFct = ignored -> anzahlUnbestimmte;

    Long anzahlStimmzettelGueltig = berechneStimmzettelGueltig(wahl, ergebnisart, wahldurchfuehrungsgebiet, anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct, anzahlUnbestimmteFct);
    Long anzahlStimmzettelUngueltig = berechneStimmzettelUngueltig(wahl, ergebnisart, wahldurchfuehrungsgebiet, anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct, anzahlUnbestimmteFct);
    Long anzahlStimmzettelUnbestimmt = berechneStimmzettelUnbestimmt(wahl, ergebnisart, wahldurchfuehrungsgebiet, anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct, anzahlUnbestimmteFct);
    Long anzahlStimmzettelGesamt = anzahlWaehlerGesamt; // ELECT-6385

    Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = model.getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagUnveraendert().map(Stimmeingabe::getAnzahl)
        .orElse(model.getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagUnveraendert().stream().mapToLong(Stimmeingabe::getAnzahl).sum());
    Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert = model.getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagVeraendert().map(Stimmeingabe::getAnzahl)
        .orElse(model.getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagVeraendert().stream().mapToLong(Stimmeingabe::getAnzahl).sum());
    Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert = model.getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagPanaschiert().map(Stimmeingabe::getAnzahl)
        .orElse(model.getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagPanaschiert().stream().mapToLong(Stimmeingabe::getAnzahl).sum());

    Long anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert = model.getStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagVeraendert().map(Stimmeingabe::getAnzahl).orElse(null);
    Long anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert = model.getStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagPanaschiert().map(Stimmeingabe::getAnzahl).orElse(null);

    fillInErgebniseingangSystemGruppeStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, wahldurchfuehrungsgebiet, model, anzahlDirektstimmenGueltig, anzahlDirektstimmenUngueltig,
        anzahlDirektstimmenGesamt, anzahlListenstimmenGueltig, anzahlListenstimmenUngueltig, anzahlListenstimmenGesamt, anzahlStimmzettelGueltig, anzahlStimmzettelUngueltig,
        anzahlStimmzettelUnbestimmt, anzahlStimmzettelGesamt, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
        anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert, anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert);
  }

  protected void fillInErgebniseingangSystemGruppeStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahldurchfuehrungsgebiet gebiet,
      StimmeingabeGruppeStimmenErfassenModel model, Long anzahlDirektstimmenGueltig, Long anzahlDirektstimmenUngueltig, Long anzahlDirektstimmenGesamt, Long anzahlListenstimmenGueltig,
      Long anzahlListenstimmenUngueltig, Long anzahlListenstimmenGesamt, Long anzahlStimmzettelGueltig, Long anzahlStimmzettelUngueltig, Long anzahlStimmzettelUnbestimmt, Long anzahlStimmzettelGesamt,
      Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert,
      Long anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert, Long anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert) {
    Long anzahlStimmzettelManuellErfasstOhneBeschluss = null;
    Long anzahlStimmzettelManuellErfasstGueltigMitBeschluss = null;
    Long anzahlStimmzettelManuellErfasstUngueltigMitBeschluss = null;
    Long anzahlStimmzettelOhneErfassungOffensichtlichUngueltig = null;

    fillInSystemGruppeStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, anzahlDirektstimmenGueltig, anzahlListenstimmenGueltig, anzahlStimmzettelGueltig, anzahlDirektstimmenUngueltig,
        anzahlListenstimmenUngueltig, anzahlStimmzettelUngueltig, anzahlStimmzettelUnbestimmt, anzahlDirektstimmenGesamt, anzahlListenstimmenGesamt, anzahlStimmzettelGesamt,
        anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert,
        anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert, anzahlStimmzettelManuellErfasstOhneBeschluss,
        anzahlStimmzettelManuellErfasstGueltigMitBeschluss, anzahlStimmzettelManuellErfasstUngueltigMitBeschluss, anzahlStimmzettelOhneErfassungOffensichtlichUngueltig

    );
  }

  private void fillInErgebnisDatenSystemGruppeStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahldurchfuehrungsgebiet gebiet,
      StimmeingabeDocumentErgebnisDaten<Wahldurchfuehrungsgebiet> ergebnisDaten) {
    GebietUndStimmabgabeart gebietUndStimmabgabeart = new GebietUndStimmabgabeart(wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebiet.getPk()), Stimmabgabeart.GESAMT);

    ErgebnisDatenGueltige ergebnisDatenGueltige = ((ErgebnisDaten) ergebnisDaten).getErgebnisDatenGueltige();

    Long anzahlDirektstimmenGueltig = ergebnisDatenGueltige.getErststimmenAktuellGueltig(gebietUndStimmabgabeart);
    Long anzahlDirektstimmenUngueltig = ergebnisDatenGueltige.getErststimmenAktuellUngueltig(gebietUndStimmabgabeart);
    Long anzahlDirektstimmenGesamt = ergebnisDatenGueltige.getErststimmenAktuellGesamt(gebietUndStimmabgabeart);

    Long anzahlListenstimmenGueltig = ergebnisDatenGueltige.getZweitstimmenAktuellGueltig(gebietUndStimmabgabeart);
    Long anzahlListenstimmenUngueltig = ergebnisDatenGueltige.getZweitstimmenAktuellUngueltig(gebietUndStimmabgabeart);
    Long anzahlListenstimmenGesamt = ergebnisDatenGueltige.getZweitstimmenAktuellGesamt(gebietUndStimmabgabeart);

    ErgebnisDatenStimmzettel ergebnisDatenStimmzettel = ((ErgebnisDaten) ergebnisDaten).getErgebnisDatenStimmzettel();

    Long anzahlStimmzettelGueltig = ergebnisDatenStimmzettel.getStimmzettelAktuellGueltig(gebietUndStimmabgabeart);
    Long anzahlStimmzettelUngueltig = ergebnisDatenStimmzettel.getStimmzettelAktuellUngueltig(gebietUndStimmabgabeart);
    Long anzahlStimmzettelUnbestimmt = ergebnisDatenStimmzettel.getStimmzettelAktuellUnbestimmt(gebietUndStimmabgabeart);
    Long anzahlStimmzettelGesamt = ergebnisDatenStimmzettel.getStimmzettelAktuellGesamt(gebietUndStimmabgabeart);

    Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(gebietUndStimmabgabeart);
    Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert = ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(gebietUndStimmabgabeart);
    Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert = ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(gebietUndStimmabgabeart);

    Long anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert = ergebnisDatenStimmzettel.getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert(gebietUndStimmabgabeart);
    Long anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert = ergebnisDatenStimmzettel.getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert(gebietUndStimmabgabeart);

    fillInErgebnisDatenSystemGruppeStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten, anzahlDirektstimmenGueltig, anzahlDirektstimmenUngueltig,
        anzahlDirektstimmenGesamt, anzahlListenstimmenGueltig, anzahlListenstimmenUngueltig, anzahlListenstimmenGesamt, anzahlStimmzettelGueltig, anzahlStimmzettelUngueltig,
        anzahlStimmzettelUnbestimmt, anzahlStimmzettelGesamt, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
        anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert, anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert);
  }

  protected void fillInErgebnisDatenSystemGruppeStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahldurchfuehrungsgebiet gebiet,
      StimmeingabeDocumentErgebnisDaten<Wahldurchfuehrungsgebiet> ergebnisDaten, Long anzahlDirektstimmenGueltig, Long anzahlDirektstimmenUngueltig, Long anzahlDirektstimmenGesamt,
      Long anzahlListenstimmenGueltig, Long anzahlListenstimmenUngueltig, Long anzahlListenstimmenGesamt, Long anzahlStimmzettelGueltig, Long anzahlStimmzettelUngueltig,
      Long anzahlStimmzettelUnbestimmt, Long anzahlStimmzettelGesamt, Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
      Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert, Long anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert, Long anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert) {
    Long anzahlStimmzettelManuellErfasstOhneBeschluss = null;
    Long anzahlStimmzettelManuellErfasstGueltigMitBeschluss = null;
    Long anzahlStimmzettelManuellErfasstUngueltigMitBeschluss = null;
    Long anzahlStimmzettelOhneErfassungOffensichtlichUngueltig = null;

    fillInSystemGruppeStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, anzahlDirektstimmenGueltig, anzahlListenstimmenGueltig, anzahlStimmzettelGueltig, anzahlDirektstimmenUngueltig,
        anzahlListenstimmenUngueltig, anzahlStimmzettelUngueltig, anzahlStimmzettelUnbestimmt, anzahlDirektstimmenGesamt, anzahlListenstimmenGesamt, anzahlStimmzettelGesamt,
        anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert,
        anzahlStimmzettelKeinEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelKeinEinzelnerWahlvorschlagPanaschiert, anzahlStimmzettelManuellErfasstOhneBeschluss,
        anzahlStimmzettelManuellErfasstGueltigMitBeschluss, anzahlStimmzettelManuellErfasstUngueltigMitBeschluss, anzahlStimmzettelOhneErfassungOffensichtlichUngueltig);
  }

  public StimmeingabeKopfzahlErfassenModel createStimmeingabeKopfzahlErfassenModel(StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    Wahl wahl = stimmeingabeErfassenContext.getWahldurchfuehrung();

    StimmeingabeKopfzahlErfassenModelBuilder modelBuilder = stimmeingabeErfassenModelBuilderResolver.getStimmeingabeKopfzahlErfassenModelBuilder(wahl);

    if (modelBuilder != null) {
      return modelBuilder.createKopfzahlErfassenModel(stimmeingabeErfassenContext);
    }

    throw new IllegalStateException("Kein StimmeingabeKopfzahlErfassenModelBuilder! [wahl = " + wahl + "]");
  }

  public StimmeingabeGruppeStimmenErfassenModel createStimmeingabeGruppeStimmenErfassenModel(StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    Wahl wahl = stimmeingabeErfassenContext.getWahldurchfuehrung();

    StimmeingabeGruppeStimmenErfassenModelBuilder modelBuilder = stimmeingabeErfassenModelBuilderResolver.getStimmeingabeGruppeStimmenErfassenModelBuilder(wahl);

    if (modelBuilder != null) {
      return modelBuilder.createGruppeStimmenErfassenModel(stimmeingabeErfassenContext);
    }

    throw new IllegalStateException("Kein StimmeingabeGruppeStimmenErfassenModelBuilder! [wahl = " + wahl + "]");
  }

  public StimmeingabeListenkandidaturStimmenErfassenModel createStimmeingabeListenkandidaturStimmenErfassenModel(StimmeingabeErfassenContext stimmeingabeErfassenContext,
      GruppeImGebiet gruppeImGebiet) {
    Wahl wahl = stimmeingabeErfassenContext.getWahldurchfuehrung();

    StimmeingabeListenkandidaturStimmenErfassenModelBuilder modelBuilder = stimmeingabeErfassenModelBuilderResolver.getStimmeingabeListenkandidaturStimmenErfassenModelBuilder(wahl);

    if (modelBuilder != null) {
      return modelBuilder.createListenkandidaturStimmenErfassenModel(gruppeImGebiet, stimmeingabeErfassenContext);
    }

    throw new IllegalStateException("Kein StimmeingabeListenkandidaturStimmenErfassenModelBuilder! [wahl = " + wahl + "]");
  }

  public StimmeingabeReferendumVorlageStimmenErfassenModel createStimmeingabeReferendumVorlageStimmenErfassenModel(StimmeingabeErfassenContext stimmeingabeErfassenContext) {
    Wahl wahl = stimmeingabeErfassenContext.getWahldurchfuehrung();

    StimmeingabeReferendumOptionStimmenErfassenModelBuilder modelBuilder = stimmeingabeErfassenModelBuilderResolver.getStimmeingabeReferendumOptionStimmenErfassenModelBuilder(wahl);

    if (modelBuilder != null) {
      return modelBuilder.createReferendumVorlageStimmenErfassenModel(stimmeingabeErfassenContext);
    }

    throw new IllegalStateException("Kein StimmeingabeReferendumVorlageStimmenErfassenModel! [wahl = " + wahl + "]");
  }

  private WahlvorschlaegeSupplier getWahlvorschlaegeSupplier(Ergebniseingang ergebniseingang) {
    return new WahlvorschlaegeSupplier(ergebniseingang.getWahldurchfuehrung(), wahlvorschlagDAO);
  }

  private EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
    return entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten();
  }

  protected StimmeingabeKopfzahlErfassenLabelGenerator getStimmeingabeKopfzahlErfassenLabelGenerator(Wahl wahl) {
    if (wahl != null) {
      if (stimmeingabeKopfzahlErfassenLabelGeneratorFinder.hasStimmeingabeKopfzahlErfassenLabelGeneratorFuerWahl(wahl)) {
        return stimmeingabeKopfzahlErfassenLabelGeneratorFinder.getStimmeingabeKopfzahlErfassenLabelGeneratorFuerWahl(wahl);
      }
    }

    return null;
  }

  protected StimmeingabeGruppeStimmenErfassenLabelGenerator getGruppeStimmenErfassenLabelGenerator(Wahl wahl) {
    if (wahl != null) {
      if (stimmeingabeGruppeStimmenErfassenLabelGeneratorFinder.hasStimmeingabeGruppeStimmenErfassenLabelGeneratorFuerWahl(wahl)) {
        return stimmeingabeGruppeStimmenErfassenLabelGeneratorFinder.getStimmeingabeGruppeStimmenErfassenLabelGeneratorFuerWahl(wahl);
      }
    }

    return null;
  }

  protected StimmeingabeListenkandidaturStimmenErfassenLabelGenerator getListenkandidaturStimmenErfassenLabelGenerator(Wahl wahl) {
    if (wahl != null) {
      if (stimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFinder.hasStimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFuerWahl(wahl)) {
        return stimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFinder.getStimmeingabeListenkandidaturStimmenErfassenLabelGeneratorFuerWahl(wahl);
      }
    }

    return null;
  }

  protected StimmeingabeReferendumOptionStimmenErfassenLabelGenerator getReferendumOptionStimmenErfassenLabelGenerator(Wahl wahl) {
    if (wahl != null) {
      if (stimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder.hasStimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFuerWahl(wahl)) {
        return stimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder.getStimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFuerWahl(wahl);
      }
    }

    return null;
  }

  private AbstractStimmeingabeErfassenContext createStimmeingabeDocumentByErgebniseingangExportContext(Ergebniseingang ergebniseingang) {
    Wahldurchfuehrung wahldurchfuehrung = ergebniseingang.getWahldurchfuehrung();

    return new WasStimmeingabeDocumentByErgebniseingangExportContext(ergebniseingang, getStimmeingabeKopfzahlErfassenLabelGenerator(wahldurchfuehrung),
        getGruppeStimmenErfassenLabelGenerator(wahldurchfuehrung), getListenkandidaturStimmenErfassenLabelGenerator(wahldurchfuehrung),
        getReferendumOptionStimmenErfassenLabelGenerator(wahldurchfuehrung), getWahlvorschlaegeSupplier(ergebniseingang), getEntityAufStimmzettelDaten(),
        wahldurchfuehrungsgebietDAO::findByErfasstFuerGebiete);
  }
}
