
package de.ivu.elect.dokumente.stimmeingabe;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.ergebnis.boundary.StimmenWaehler;
import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsgruppierungDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietDescriptor;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolver;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolverContext;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeDocumentErgebnisDaten;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeDocumentReferendumErgebnisDaten;
import de.ivu.elect.business.stimmen.boundary.DirektstimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.GruppeStimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.ListenkandidaturstimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.ListenstimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.ReferendumStimmeLabelGenerator;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.wahl.control.Wahldaten;
import de.ivu.elect.business.wahl.entity.Wahl;
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.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;
import de.ivu.elect.dokumente.DokumentFormatter;
import de.ivu.elect.dokumente.wahlvorschlag.CoreWahlvorschlagsdatenDocument;
import de.ivu.elect.exchange.wahlhelfereinsatz.WasWahlhelfereinsatzCsvDTO;
import de.ivu.elect.presentation.person.PersonendatenFormatter;

public abstract class AbstractStimmeingabeDocumentFactory<G extends Gebiet> {

  protected static final Logger LOG = LoggerFactory.getLogger(AbstractStimmeingabeDocumentFactory.class);

  @Inject
  protected transient GebietDAO gebietDAO;

  @Inject
  protected transient GebietsgruppierungDAO gebietsgruppierungDAO;

  @Inject
  protected transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  protected transient GruppeStimmeLabelGenerator gruppeStimmeLabelGenerator;

  @Inject
  protected transient CoreStimmeingabeDocumentBuilder stimmeingabeDocumentBuilder;

  @Inject
  protected transient StimmeingabeDocumentFactoryHelper stimmeingabeDocumentFactoryHelper;

  @Inject
  protected transient WahlverwaltungGebietInformationResolver wahlverwaltungGebietInformationResolver;

  @Inject
  protected transient PersonendatenFormatter personendatenFormatter;

  protected AbstractStimmeingabeDocumentFactory() {
    super();
  }

  /**
   * Erzeugt ein StimmeingabeDocument für ein Obergebiet (@see StimmeingabeDocumentByErgebnisDatenExportContext#getStimmzettelgebiete), üblicherweise einen Wahlkreis, auf dem die Kandidatenaufstellung
   * in allen Wahlbezirken gleich ist. Das Dokument dient als Formular, z.B. die Wahlbezirksnummer muss manuell eingetragen werden.
   */
  public StimmeingabeDocument createDocumentFuerStimmzettelgebiet(StimmeingabeDocumentContext stimmeingabeDocumentContext, G stimmzettelgebiet, StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    StimmeingabeDocument stimmeingabeDocument = createStimmeingabeDocument(stimmeingabeDocumentContext);
    stimmeingabeDocument.documentPerStimmzettelgebiet = true;

    fillInGebietDatenFuerStimmzettelgebiet(stimmeingabeDocument, stimmeingabeDocumentContext, new WahlverwaltungGebietInformationResolverContext(ergebnisDaten.getWahldaten()), stimmzettelgebiet);
    fillInErgebnisDaten(stimmeingabeDocument, stimmeingabeDocumentContext, stimmzettelgebiet, ergebnisDaten);

    return stimmeingabeDocument;
  }

  public StimmeingabeDocument createDocumentFuerWahlgebiet(StimmeingabeDocumentContext stimmeingabeDocumentContext, G wahlgebiet, Collection<StimmeingabeDocument> documentsFuerErfassungsgebiet) {
    StimmeingabeDocument stimmeingabeDocument = createStimmeingabeDocument(stimmeingabeDocumentContext);
    stimmeingabeDocument.documentPerWahlgebiet = true;

    fillInGebietDatenFuerWahlgebiet(stimmeingabeDocument, stimmeingabeDocumentContext,
        new WahlverwaltungGebietInformationResolverContext(wahlgebiet.getWahl(), stimmeingabeDocumentContext.getWahlParameterLoader(), gebietDAO, gebietsgruppierungDAO), wahlgebiet);

    for (StimmeingabeDocument document : documentsFuerErfassungsgebiet) {
      document.addToDocumentKumuliert(stimmeingabeDocumentContext, stimmeingabeDocumentBuilder, stimmeingabeDocument);
    }

    fillInWahlhelfereinsaetze(stimmeingabeDocument, stimmeingabeDocumentContext, wahlgebiet);

    return stimmeingabeDocument;
  }

  /**
   * Erzeugung des Dokuments aus ErgebnisDaten
   */
  public StimmeingabeDocument createDocumentFuerErfassungsgebiet(StimmeingabeDocumentContext stimmeingabeDocumentContext, G erfassungsgebiet, StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    StimmeingabeDocument stimmeingabeDocument = createStimmeingabeDocument(stimmeingabeDocumentContext);
    stimmeingabeDocument.documentPerWahlbezirk = true;

    fillInGebietDatenFuerWahlbezirk(stimmeingabeDocument, stimmeingabeDocumentContext, new WahlverwaltungGebietInformationResolverContext(ergebnisDaten.getWahldaten()), erfassungsgebiet);
    fillInErgebnisDaten(stimmeingabeDocument, stimmeingabeDocumentContext, erfassungsgebiet, ergebnisDaten);

    fillInWahlhelfereinsaetze(stimmeingabeDocument, stimmeingabeDocumentContext, erfassungsgebiet);

    return stimmeingabeDocument;
  }

  private void fillInWahlhelfereinsaetze(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Gebiet gebiet) {
    List<WasWahlhelfereinsatzDocument> result = new ArrayList<>();

    for (WasWahlhelfereinsatzCsvDTO dto : stimmeingabeDocumentContext.getWahlhelferEinsaetzeFuerGebiet(gebiet)) {
      String titelNamensbestandteileUndNachname = personendatenFormatter.bestimmeTitelNamensbestandteileUndNachname(dto.getTitel(), dto.getNamensbestandteile(), dto.getFamilienname());

      result.add(new WasWahlhelfereinsatzDocument(titelNamensbestandteileUndNachname, dto.getVorname(), dto.getWahlhelferrolleFormatiert(), dto.getPosition()));
    }

    result.sort((o1, o2) -> (int) (o1.getListenPosition() - o2.getListenPosition()));

    stimmeingabeDocument.wasWahlhelfereinsaetze.addAll(result);
  }

  protected StimmeingabeDocument createStimmeingabeDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext) {
    return stimmeingabeDocumentBuilder.createStimmeingabeDocument(stimmeingabeDocumentContext);
  }

  protected void fillInGebietDatenFuerWahlgebiet(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, G wahlgebiet) {
    fillInGebietDaten(stimmeingabeDocument, stimmeingabeDocumentContext, wahlverwaltungGebietInformationResolverContext, wahlgebiet, null, null);
  }

  protected void fillInGebietDatenFuerStimmzettelgebiet(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, G stimmzettelgebiet) {
    Gebiet wahlgebiet = stimmzettelgebiet.getUebergeordnetesWahlgebiet();

    fillInGebietDaten(stimmeingabeDocument, stimmeingabeDocumentContext, wahlverwaltungGebietInformationResolverContext, wahlgebiet, stimmzettelgebiet, null);
  }

  protected void fillInGebietDatenFuerWahlbezirk(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, G erfassungsgebiet) {
    Gebiet wahlgebiet = erfassungsgebiet.getUebergeordnetesWahlgebiet();
    Gebiet stimmzettelgebiet = erfassungsgebiet.getUebergeordnetesWahlvorschlaggebiet();

    fillInGebietDaten(stimmeingabeDocument, stimmeingabeDocumentContext, wahlverwaltungGebietInformationResolverContext, wahlgebiet, stimmzettelgebiet, erfassungsgebiet);
  }

  private void fillInGebietDaten(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, Gebiet wahlgebiet, Gebiet stimmzettelgebiet, Gebiet erfassungsgebiet) {
    WahlverwaltungGebietDescriptor documentGebietDescriptor = createDocumentGebietDescriptor(wahlverwaltungGebietInformationResolverContext, wahlgebiet, stimmzettelgebiet, erfassungsgebiet);
    Gebiet documentGebiet = documentGebietDescriptor.getGebiet();

    stimmeingabeDocument.documentGebiet = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebiet); // ELECT-14076

    stimmeingabeDocument.wahlgebiet = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, wahlgebiet);

    // ELECT-7302
    WahlverwaltungGebietInformation wahlkreisInformation = documentGebietDescriptor.getWahlkreis();
    stimmeingabeDocument.wahlkreis = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, wahlkreisInformation);

    if (wahlkreisInformation.equalsGebiet(stimmzettelgebiet)) {
      stimmeingabeDocument.wahlkreis = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, stimmzettelgebiet);
    }

    // ELECT-7030
    WahlverwaltungGebietInformation stimmkreisInformation = documentGebietDescriptor.getStimmkreis();
    stimmeingabeDocument.stimmkreis = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, stimmkreisInformation);

    if (stimmkreisInformation.equalsGebiet(stimmzettelgebiet)) {
      stimmeingabeDocument.stimmkreis = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, stimmzettelgebiet);
    }

    WahlverwaltungGebietInformation wahlbereichInformation = documentGebietDescriptor.getWahlbereich();
    stimmeingabeDocument.wahlbereich = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, wahlbereichInformation);

    if (wahlbereichInformation.equalsGebiet(stimmzettelgebiet)) {
      stimmeingabeDocument.wahlbereich = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, stimmzettelgebiet);
    }

    stimmeingabeDocument.wahlbezirk = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, erfassungsgebiet);

    if (erfassungsgebiet != null) {
      stimmeingabeDocumentFactoryHelper.fillInWahlbezirkDaten(stimmeingabeDocument, stimmeingabeDocumentContext, erfassungsgebiet);
    }

    stimmeingabeDocument.bundesland = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebietDescriptor.getBundesland());
    stimmeingabeDocument.landkreis = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebietDescriptor.getKreis());
    stimmeingabeDocument.gemeindeverband = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebietDescriptor.getGemeindeverband());
    stimmeingabeDocument.gemeinde = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebietDescriptor.getGemeinde());
    stimmeingabeDocument.ortsbezirk = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebietDescriptor.getOrtsbezirk());
    stimmeingabeDocument.stadtbezirk = stimmeingabeDocumentBuilder.createGebietDocument(stimmeingabeDocumentContext, documentGebietDescriptor.getStadtbezirk());
  }

  private WahlverwaltungGebietDescriptor createDocumentGebietDescriptor(WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, Gebiet wahlgebiet,
      Gebiet stimmzettelgebiet, Gebiet erfassungsgebiet) {

    /*
     * Wähle existierendes Gebiet "von unten nach oben"...
     */

    if (erfassungsgebiet != null) {
      return wahlverwaltungGebietInformationResolver.createDescriptor(wahlverwaltungGebietInformationResolverContext, erfassungsgebiet);
    }

    if (stimmzettelgebiet != null) {
      return wahlverwaltungGebietInformationResolver.createDescriptor(wahlverwaltungGebietInformationResolverContext, stimmzettelgebiet);
    }

    if (wahlgebiet != null) {
      return wahlverwaltungGebietInformationResolver.createDescriptor(wahlverwaltungGebietInformationResolverContext, wahlgebiet);
    }

    throw new IllegalArgumentException("FAILED to find any Gebiet!");
  }

  protected CoreWahlvorschlagsdatenDocument createWahlvorschlagsdatenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Wahlvorschlag wahlvorschlag) {
    return stimmeingabeDocumentBuilder.createWahlvorschlagsdatenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, wahlvorschlag);
  }

  /**
   * @see de.ivu.elect.dokumente.stimmeingabe.AbstractWasStimmeingabeDocumentFactory#fillInErgebnisDaten
   * @see de.ivu.elect.dokumente.stimmeingabe.AbstractWasStimmeingabeDocumentFactory#fillInErgebniseingang
   */
  protected void fillInErgebnisDaten(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, G gebiet, StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    fillInErgebnisDatenErfassteStimmzettel(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten);

    fillInErgebnisDatenKopfzahlen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten);

    if (stimmeingabeDocumentContext.getWahl().isReferendum()) {
      fillInErgebnisDatenReferendumStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten);
    } else {
      fillInErgebnisDatenStimmen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, ergebnisDaten);
    }
  }

  protected void fillInErgebnisDatenErfassteStimmzettel(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, G gebiet,
      StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    fillInErfassteStimmzettel(stimmeingabeDocument, stimmeingabeDocumentContext, ergebnisDaten.getStimmzettel(gebiet));
  }

  protected void fillInErgebnisDatenKopfzahlen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, G gebiet,
      StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    Long anzahlWahlberechtigteGesamt = ergebnisDaten.getWahlberechtigteGesamtFuerErfassungsgebietUnterhalbVonAnzahlGesamt(gebiet);
    Long anzahlWahlberechtigteOhneWahlschein = ergebnisDaten.getWahlberechtigteOhneWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(gebiet);
    Long anzahlWahlberechtigteMitWahlschein = ergebnisDaten.getWahlberechtigteMitWahlscheinFuerErfassungsgebietUnterhalbVonAnzahl(gebiet);
    Long anzahlWahlberechtigteNichtImWaehlerverzeichnis = ergebnisDaten.getWahlberechtigteNichtImWaehlerverzeichnisFuerErfassungsgebietUnterhalbVonAnzahl(gebiet);

    Long anzahlWaehlerOhneWahlschein = ergebnisDaten.getWaehlerOhneWahlschein(gebiet);
    Long anzahlWaehlerMitWahlschein = ergebnisDaten.getWaehlerMitWahlschein(gebiet);
    Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = ergebnisDaten.getWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(gebiet);
    Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = ergebnisDaten.getWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(gebiet);
    Long anzahlWaehlerMitVollmacht = ergebnisDaten.getWaehlerMitVollmacht(gebiet);
    Long anzahlWaehlerGesamt = ergebnisDaten.getWaehlerGesamt(gebiet);

    Function<G, Long> anzahlWahlberechtigteOhneWahlscheinFct = x -> anzahlWahlberechtigteOhneWahlschein;
    Function<G, Long> anzahlWahlberechtigteMitWahlscheinFct = x -> anzahlWahlberechtigteMitWahlschein;
    Function<G, Long> anzahlWahlberechtigteNichtImWaehlerverzeichnisFct = x -> anzahlWahlberechtigteNichtImWaehlerverzeichnis;

    fillInKopfzahlen(stimmeingabeDocument, stimmeingabeDocumentContext, gebiet, anzahlWahlberechtigteGesamt, anzahlWahlberechtigteOhneWahlschein, anzahlWahlberechtigteMitWahlschein,
        anzahlWahlberechtigteNichtImWaehlerverzeichnis, anzahlWaehlerGesamt, anzahlWaehlerOhneWahlschein, anzahlWaehlerMitWahlschein, anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl,
        anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, anzahlWaehlerMitVollmacht, anzahlWahlberechtigteOhneWahlscheinFct, anzahlWahlberechtigteMitWahlscheinFct,
        anzahlWahlberechtigteNichtImWaehlerverzeichnisFct);
  }

  protected void fillInErgebnisDatenStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, G gebiet,
      StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    Wahldaten< ? extends Wahl, G> wahldaten = ergebnisDaten.getWahldaten();

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();

    Collection<PolitischeGruppe> politischeGruppen = wahldaten.getPolitischeGruppen();
    Collection<GruppeImGebiet> gruppenImGebiet = wahldaten.getGruppenImGebiet();
    Collection<Wahlvorschlag> wahlvorschlaege = ergebnisDaten.getWahlvorschlaege();

    // Im WUS sind die Values der Maps alle null
    Map<PolitischeGruppe, Long> anzahlDirektstimmePerGruppe = ergebnisDaten.getErststimmenAktuellGesamt(gebiet);
    Map<PolitischeGruppe, Long> anzahlListenstimmePerGruppe = ergebnisDaten.getZweitstimmenAktuellGesamt(gebiet);

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      boolean isReferendum = politischeGruppe.isReferendumVorlage();
      GruppeImGebiet gruppeImGebiet = ergebnisDaten.getGruppeImGebiet(politischeGruppe, gebiet);

      if (gruppeImGebiet != null) {
        Collection<Wahlvorschlag> wahlvorschlaegeAbgeleitet = gruppeImGebiet.getWahlvorschlaegeAbgeleitet(wahlvorschlaege);

        if (!wahlvorschlaegeAbgeleitet.isEmpty()) {
          WahlvorschlagStimmzettelPosition wahlvorschlagStimmzettelPosition = null;

          Direktkandidat direktkandidat = null;
          Long anzahlDirektstimmen = anzahlDirektstimmePerGruppe.get(politischeGruppe);

          Liste liste = null;
          Long anzahlListenstimmen = anzahlListenstimmePerGruppe.get(politischeGruppe);
          Long anzahlListenstimmenNurListe = anzahlListenstimmen;
          Long anzahlListenstimmenNurListenkandidaturen = null;

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

          for (Wahlvorschlag wahlvorschlag : wahlvorschlaegeAbgeleitet) {
            if (wahlvorschlagStimmzettelPosition == null && !isReferendum) {
              wahlvorschlagStimmzettelPosition = wahlvorschlag.getStimmzettelPosition(gruppeImGebiet.getGebiet(), entityAufStimmzettelDaten);
            }

            if (wahlvorschlagStimmzettelPosition != null && wahlvorschlagStimmzettelPosition.hasPosition()) {
              if (wahlvorschlag.isDirektkandidat()) {
                direktkandidat = castTo(wahlvorschlag, Direktkandidat.class);

                KandidatStimmenDocument direktkandidatStimmenDocument = createErgebnisDatenDirektkandidatStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet,
                    gruppeImGebiet, direktkandidat, wahlvorschlagStimmzettelPosition, anzahlDirektstimmen);

                stimmeingabeDocument.direktkandidaten.add(direktkandidatStimmenDocument);
              }

              if (wahlvorschlag.isListe()) {
                liste = castTo(wahlvorschlag, Liste.class);

                anzahlListenstimmenNurListe = ergebnisDaten.getStimmenNurListe(liste, gebiet);
                anzahlListenstimmenNurListenkandidaturen = ergebnisDaten.getStimmenNurListenkandidaturen(liste, gebiet);

                anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = ergebnisDaten.getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(liste, gebiet);
                anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert = ergebnisDaten.getStimmzettelNurEinzelnerWahlvorschlagVeraendert(liste, gebiet);
                anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert = ergebnisDaten.getStimmzettelNurEinzelnerWahlvorschlagPanaschiert(liste, gebiet);

                ListeStimmenDocument listeStimmenDocument = createErgebnisDatenListeStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, liste,
                    wahlvorschlagStimmzettelPosition, anzahlListenstimmen, anzahlListenstimmenNurListe, anzahlListenstimmenNurListenkandidaturen,
                    anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);

                stimmeingabeDocument.listen.add(listeStimmenDocument);

                if (liste.getWahl().isWahlMitListenkandidatenStimmen()) {
                  Map<Listenkandidatur, Long> anzahlStimmenPerListenkandidatur = new LinkedHashMap<>();

                  for (Listenkandidatur listenkandidatur : entityAufStimmzettelDaten.collectListenkandidaturenAufStimmzettelSortedByDefault(liste)) {
                    anzahlStimmenPerListenkandidatur.put(listenkandidatur, ergebnisDaten.getStimmenFuerListenkandidatur(listenkandidatur, gebiet));
                  }

                  listeStimmenDocument.listenkandidaturen.addAll(createErgebnisDatenListenkandidatenStimmenDocuments(listeStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten,
                      gruppenImGebiet, gruppeImGebiet, liste, wahlvorschlagStimmzettelPosition, anzahlStimmenPerListenkandidatur, ergebnisDaten));
                }
              }
            }
          }

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

            stimmeingabeDocument.politischeGruppen.add(politischeGruppeStimmenDocument);
          }
        }
      } else {
        LOG.debug("GruppeImGebiet NICHT gefunden: [politischeGruppe = {}, gebiet = {}]", politischeGruppe, gebiet);
      }
    }

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

  protected ListeStimmenDocument createErgebnisDatenListeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste, WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Long anzahlStimmen, Long anzahlStimmenNurListe,
      Long anzahlStimmenNurListenkandidaturen, Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
      Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert) {
    return createListeStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, liste, listeStimmzettelPosition, anzahlStimmen, anzahlStimmenNurListe,
        anzahlStimmenNurListenkandidaturen, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
        anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);
  }

  protected ListeStimmenDocument createListeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste, WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Long anzahlStimmen, Long anzahlStimmenNurListe,
      Long anzahlStimmenNurListenkandidaturen, Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
      Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert) {
    ListeStimmenDocument listeStimmenDocument = createListeStimmenDocument(stimmeingabeDocumentContext);

    fillInListeStimmen(listeStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, liste, listeStimmzettelPosition, anzahlStimmen,
        anzahlStimmenNurListe, anzahlStimmenNurListenkandidaturen, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
        anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);

    return listeStimmenDocument;
  }

  protected ListeStimmenDocument createListeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext) {
    return stimmeingabeDocumentBuilder.createListeStimmenDocument(stimmeingabeDocumentContext);
  }

  protected void fillInListeStimmen(ListeStimmenDocument listeStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste, WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Long anzahlStimmen, Long anzahlStimmenNurListe,
      Long anzahlStimmenNurListenkandidaturen, Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert,
      Long anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert) {
    String kennung = formatListenstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, listeStimmzettelPosition);
    String bezeichnung = formatListenstimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet);

    fillInStimmen(listeStimmenDocument, listeStimmzettelPosition, kennung, bezeichnung);

    if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
      listeStimmenDocument.anzahlStimmen = anzahlStimmen;
      listeStimmenDocument.anzahlStimmenNurListe = anzahlStimmenNurListe;
      listeStimmenDocument.anzahlStimmenNurListenkandidaturen = anzahlStimmenNurListenkandidaturen;

      listeStimmenDocument.stimmzettelWahlvorschlagUnveraendert = anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert;
      listeStimmenDocument.stimmzettelWahlvorschlagVeraendert = anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert;
      listeStimmenDocument.stimmzettelWahlvorschlagGesamt = sum(anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert);
      listeStimmenDocument.stimmzettelWahlvorschlagPanaschiert = anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert;
    }

    listeStimmenDocument.wahlvorschlagsdatenDocument = createWahlvorschlagsdatenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, liste);
  }

  protected List<KandidatStimmenDocument> createErgebnisDatenListenkandidatenStimmenDocuments(ListeStimmenDocument listeStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste,
      WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Map<Listenkandidatur, Long> anzahlStimmenPerListenkandidatur, StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    return createListenkandidatStimmenDocuments(stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, liste, listeStimmzettelPosition,
        anzahlStimmenPerListenkandidatur);
  }

  protected List<KandidatStimmenDocument> createListenkandidatStimmenDocuments(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste, WahlvorschlagStimmzettelPosition listeStimmzettelPosition,
      Map<Listenkandidatur, Long> anzahlStimmenPerListenkandidatur) {
    List<KandidatStimmenDocument> result = new ArrayList<>();

    Long laufendeNummer = 0L;

    for (Entry<Listenkandidatur, Long> entry : anzahlStimmenPerListenkandidatur.entrySet()) {
      Listenkandidatur listenkandidatur = entry.getKey();
      ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = listenkandidatur.getStimmzettelPosition(entityAufStimmzettelDaten);

      if (listenkandidaturStimmzettelPosition.hasPosition()) {
        laufendeNummer = laufendeNummer + 1;

        result.add(createListenkandidatStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, liste, listeStimmzettelPosition, listenkandidatur,
            listenkandidaturStimmzettelPosition, entry.getValue(), laufendeNummer));
      }
    }

    sortByPosition(result); // ELECT-9041

    return result;
  }

  private KandidatStimmenDocument createListenkandidatStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste, WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Listenkandidatur listenkandidatur,
      ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition, Long anzahlStimmenGesamt, Long laufendeNummer) {
    KandidatStimmenDocument listenkandidatStimmenDocument = createKandidatStimmenDocument(stimmeingabeDocumentContext);

    fillInListenkandidatStimmen(listenkandidatStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, liste, listeStimmzettelPosition,
        listenkandidatur, listenkandidaturStimmzettelPosition, anzahlStimmenGesamt, laufendeNummer);

    return listenkandidatStimmenDocument;
  }

  private void fillInListenkandidatStimmen(KandidatStimmenDocument listenkandidatStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Liste liste,
      WahlvorschlagStimmzettelPosition listeStimmzettelPosition, Listenkandidatur listenkandidatur, ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition, Long anzahlStimmenGesamt,
      Long laufendeNummer) {
    Long position = listenkandidaturStimmzettelPosition.getPosition();

    String positionFormatted = listenkandidaturStimmzettelPosition.formatPosition(listeStimmzettelPosition);
    String kennzahlFormatted = listenkandidaturStimmzettelPosition.formatKennzahl(listeStimmzettelPosition);

    String kennung = formatListenkandidatStimmenAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, listeStimmzettelPosition, listenkandidaturStimmzettelPosition);
    String bezeichnung = formatListenkandidatStimmenAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, listenkandidatur);

    Long anzahlNennungen = Math.max(fixNullToZero(listenkandidatur.getAnzahlNennungen()), 1L);

    fillInKandidatStimmen(listenkandidatStimmenDocument, stimmeingabeDocumentContext, liste, position, positionFormatted, kennzahlFormatted, listenkandidatur, laufendeNummer, kennung, bezeichnung,
        anzahlNennungen, anzahlStimmenGesamt, entityAufStimmzettelDaten);
  }

  protected KandidatStimmenDocument createErgebnisDatenDirektkandidatStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Direktkandidat direktkandidat, WahlvorschlagStimmzettelPosition direktkandidatStimmzettelPosition,
      Long anzahlStimmenGesamt) {
    return createDirektkandidatStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, direktkandidat, direktkandidatStimmzettelPosition,
        anzahlStimmenGesamt);
  }

  protected KandidatStimmenDocument createDirektkandidatStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Direktkandidat direktkandidat, WahlvorschlagStimmzettelPosition direktkandidatStimmzettelPosition,
      Long anzahlStimmenGesamt) {
    KandidatStimmenDocument direktkandidatStimmenDocument = createKandidatStimmenDocument(stimmeingabeDocumentContext);

    fillInDirektkandidatStimmen(direktkandidatStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, direktkandidat,
        direktkandidatStimmzettelPosition, anzahlStimmenGesamt);

    return direktkandidatStimmenDocument;
  }

  private void fillInDirektkandidatStimmen(KandidatStimmenDocument direktkandidatStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet, Direktkandidat direktkandidat,
      WahlvorschlagStimmzettelPosition direktkandidatStimmzettelPosition, Long anzahlStimmenGesamt) {
    Long direktkandidatPosition = direktkandidatStimmzettelPosition.getPosition();

    String direktkandidatPositionFormatted = direktkandidatStimmzettelPosition.formatPosition();
    String direktkandidatKennzahlFormatted = direktkandidatStimmzettelPosition.formatKennzahl();

    Long laufendeNummer = null;

    String kennung = formatDirektstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, direktkandidatStimmzettelPosition);
    String bezeichnung = formatDirektstimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet);

    Long anzahlNennungen = 1L;

    fillInKandidatStimmen(direktkandidatStimmenDocument, stimmeingabeDocumentContext, direktkandidat, direktkandidatPosition, direktkandidatPositionFormatted, direktkandidatKennzahlFormatted,
        direktkandidat.getDirektkandidatur(), laufendeNummer, kennung, bezeichnung, anzahlNennungen, anzahlStimmenGesamt, entityAufStimmzettelDaten);
  }

  protected KandidatStimmenDocument createKandidatStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext) {
    return stimmeingabeDocumentBuilder.createKandidatStimmenDocument(stimmeingabeDocumentContext);
  }

  protected void fillInKandidatStimmen(KandidatStimmenDocument kandidatStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, Wahlvorschlag wahlvorschlag, Long position,
      String positionFormatted, String kennzahlFormatted, Kandidatur kandidatur, Long laufendeNummer, String kennung, String bezeichnung, Long anzahlNennungen, Long anzahlStimmenGesamt,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    fillInStimmen(kandidatStimmenDocument, position, positionFormatted, kennzahlFormatted, kennung, bezeichnung);

    kandidatStimmenDocument.anzahlNennungen = anzahlNennungen;

    if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
      Long anzahlStimmenAnteilAusWahlvorschlagstimmvergabe = 0L;
      Long anzahlStimmenAnteilAusEinzelstimmvergabe = 0L;

      if (wahlvorschlag.isListe() && anzahlStimmenGesamt != null && anzahlStimmenGesamt > 0) { // ELECT-9434
        Long listeAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = 0L; // Cf. AbstractByStimmeingabeDocumentFactoryHelper#fillInByListeStimmen!
        Long listenkandidaturAnzahlNennungen = Math.max(fixNullToZero(kandidatStimmenDocument.anzahlNennungen), 1L);

        anzahlStimmenAnteilAusWahlvorschlagstimmvergabe = listeAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert * listenkandidaturAnzahlNennungen;
        anzahlStimmenAnteilAusEinzelstimmvergabe = Math.max(0L, anzahlStimmenGesamt - anzahlStimmenAnteilAusWahlvorschlagstimmvergabe);
      }

      kandidatStimmenDocument.anzahlStimmen = anzahlStimmenGesamt;
      kandidatStimmenDocument.anteilStimmenAusWahlvorschlagstimmvergabe = anzahlStimmenAnteilAusWahlvorschlagstimmvergabe;
      kandidatStimmenDocument.anteilStimmenAusEinzelstimmvergabe = anzahlStimmenAnteilAusEinzelstimmvergabe;
    }

    kandidatStimmenDocument.familienname = kandidatur.getFamilienname();
    kandidatStimmenDocument.vorname = kandidatur.getRufname();

    kandidatStimmenDocument.kandidatur = stimmeingabeDocumentBuilder.createKandidaturdatenDokument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, wahlvorschlag, kandidatur, laufendeNummer);
  }

  protected GruppeStimmenDocument createErgebnisDatenPolitischeGruppeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> gruppenImGebiet, 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, StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    GruppeStimmenDocument gruppeStimmenDocument = createGruppeStimmenDocument(stimmeingabeDocumentContext);

    fillInPolitischeGruppeStimmen(gruppeStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, wahlvorschlagStimmzettelPosition, direktkandidat,
        anzahlDirektstimmen, liste, anzahlListenstimmen, anzahlListenstimmenNurListe, anzahlListenstimmenNurListenkandidaturen, anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert,
        anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert);

    return gruppeStimmenDocument;
  }

  protected GruppeStimmenDocument createGruppeStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext) {
    return stimmeingabeDocumentBuilder.createGruppeStimmenDocument(stimmeingabeDocumentContext);
  }

  protected void fillInPolitischeGruppeStimmen(GruppeStimmenDocument gruppeStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, 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) {
    String gruppeBezeichnung = gruppeImGebiet.getKurznameAufGebiet();

    String direktstimmenGruppeBezeichnung = formatDirektstimmeAnzahlGueltigeGruppeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet);
    String direktstimmenKennung = formatDirektstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, wahlvorschlagStimmzettelPosition);
    String direktstimmenBezeichnung = formatDirektstimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet);

    String listenstimmenGruppeBezeichnung = formatListenstimmeAnzahlGueltigeGruppeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet);
    String listenstimmenKennung = formatListenstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet, wahlvorschlagStimmzettelPosition);
    String listenstimmenBezeichnung = formatListenstimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppenImGebiet, gruppeImGebiet);

    fillInStimmen(gruppeStimmenDocument, wahlvorschlagStimmzettelPosition, "", gruppeBezeichnung);

    gruppeStimmenDocument.erststimmenErfassbar = direktkandidat != null;

    if (gruppeStimmenDocument.erststimmenErfassbar) {
      gruppeStimmenDocument.erststimmenGruppeBezeichnung = direktstimmenGruppeBezeichnung;
      gruppeStimmenDocument.erststimmenKennung = direktstimmenKennung;
      gruppeStimmenDocument.erststimmenBezeichnung = direktstimmenBezeichnung;

      if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
        gruppeStimmenDocument.anzahlErststimmen = anzahlDirektstimmen;
      }
    }

    gruppeStimmenDocument.zweitstimmenErfassbar = liste != null;

    if (gruppeStimmenDocument.zweitstimmenErfassbar) {
      gruppeStimmenDocument.zweitstimmenGruppeBezeichnung = listenstimmenGruppeBezeichnung;
      gruppeStimmenDocument.zweitstimmenKennung = listenstimmenKennung;
      gruppeStimmenDocument.zweitstimmenBezeichnung = listenstimmenBezeichnung;

      if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
        gruppeStimmenDocument.anzahlZweitstimmen = anzahlListenstimmen;
        gruppeStimmenDocument.anzahlZweitstimmenNurListe = anzahlListenstimmenNurListe;
        gruppeStimmenDocument.anzahlZweitstimmenNurListenkandidaturen = anzahlListenstimmenNurListenkandidaturen;
      }
    }

    if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
      gruppeStimmenDocument.stimmzettelWahlvorschlagUnveraendert = anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert;
      gruppeStimmenDocument.stimmzettelWahlvorschlagVeraendert = anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert;
      gruppeStimmenDocument.stimmzettelWahlvorschlagGesamt = sum(anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert);
      gruppeStimmenDocument.stimmzettelWahlvorschlagPanaschiert = anzahlStimmzettelNurEinzelnerWahlvorschlagPanaschiert;
    }
  }

  private void fillInStimmen(AbstractStimmenDocument stimmenDocument, WahlvorschlagStimmzettelPosition wahlvorschlagStimmzettelPosition, String kennung, String bezeichnung) {
    fillInStimmen(stimmenDocument, wahlvorschlagStimmzettelPosition.getPosition(), wahlvorschlagStimmzettelPosition.formatPosition(), wahlvorschlagStimmzettelPosition.formatKennzahl(), kennung,
        bezeichnung);
  }

  private void fillInStimmen(AbstractStimmenDocument stimmenDocument, Long position, String positionFormatted, String kennzahlFormatted, String kennung, String bezeichnung) {
    stimmenDocument.position = position;

    stimmenDocument.positionFormatted = positionFormatted;
    stimmenDocument.kennzahlFormatted = kennzahlFormatted;

    stimmenDocument.kennung = kennung;
    stimmenDocument.bezeichnung = bezeichnung;
  }

  // Cf. GebietMitReferendumVorlagenDocument#bestimmeReferendumVorlagen, GebietMitDetails#erstelleReferendumVorlagenErgebnis!
  protected void fillInErgebnisDatenReferendumStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, G gebiet,
      StimmeingabeDocumentErgebnisDaten<G> ergebnisDaten) {
    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();

    List<GruppeImGebiet> referendumVorlagenImGebiet = ergebnisDaten.getWahldaten().getReferendumVorlagenSortiertNachPositionAufGebiet(gebiet).stream() //
        .map(referendumVorlage -> ergebnisDaten.getGruppeImGebiet(referendumVorlage, gebiet)) //
        .collect(Collectors.toList());
    Collection<Wahlvorschlag> wahlvorschlaege = ergebnisDaten.getWahlvorschlaege();

    StimmeingabeDocumentReferendumErgebnisDaten<G> ergebnisDatenReferendumOptionen = ergebnisDaten.getErgebnisDatenReferendumOptionen();

    for (int i = 0; i < referendumVorlagenImGebiet.size(); i++) {
      GruppeImGebiet referendumVorlageImGebiet = referendumVorlagenImGebiet.get(i);
      Gruppe referendumVorlage = referendumVorlageImGebiet.getGruppe();

      Long anzahlStimmenGueltig = ergebnisDatenReferendumOptionen.getStimmenFuerReferendumOptionenGueltig(referendumVorlage, gebiet);
      Long anzahlStimmenUngueltig = ergebnisDatenReferendumOptionen.getStimmenFuerReferendumOptionenUngueltig(referendumVorlage, gebiet);
      Long anzahlStimmenAbgegeben = ergebnisDatenReferendumOptionen.getStimmenFuerReferendumOptionenAbgegeben(referendumVorlage, gebiet);

      List<ReferendumOption> referendumOptionenSorted = referendumVorlageImGebiet.getWahlvorschlaegeAbgeleitet(wahlvorschlaege).stream() //
          .filter(Wahlvorschlag::isReferendumOption) //
          .map(wahlvorschlag -> wahlvorschlag.castTo(ReferendumOption.class).getEntity()) //
          .collect(toList());
      Map<ReferendumOption, Long> anzahlStimmenPerReferendumOption = new LinkedHashMap<>();

      for (ReferendumOption referendumOption : referendumOptionenSorted) {
        Long anzahlStimmen = ergebnisDatenReferendumOptionen.getStimmenFuerReferendumOption(referendumOption, gebiet);

        anzahlStimmenPerReferendumOption.put(referendumOption, anzahlStimmen);
      }

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

    sortByPosition(stimmeingabeDocument.referendumVorlagen);
  }

  protected void fillInReferendumStimmen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> referendumVorlagenImGebiet, GruppeImGebiet referendumVorlageImGebiet, Long laufendeNummer, Long anzahlStimmenGueltig, Long anzahlStimmenUngueltig,
      Long anzahlStimmenAbgegeben, Map<ReferendumOption, Long> anzahlStimmenPerReferendumOption) {
    ReferendumVorlageStimmenDocument referendumVorlageStimmenDocument = createReferendumVorlageStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, referendumVorlagenImGebiet,
        referendumVorlageImGebiet, laufendeNummer, anzahlStimmenGueltig, anzahlStimmenUngueltig, anzahlStimmenAbgegeben);

    for (Entry<ReferendumOption, Long> entry : anzahlStimmenPerReferendumOption.entrySet()) {
      ReferendumOptionStimmenDocument referendumOptionStimmenDocument = createReferendumOptionStimmenDocument(stimmeingabeDocumentContext, entityAufStimmzettelDaten, referendumVorlagenImGebiet,
          referendumVorlageImGebiet, entry.getKey(), entry.getValue());

      referendumVorlageStimmenDocument.referendumOptionen.add(referendumOptionStimmenDocument);
    }

    sortByPosition(referendumVorlageStimmenDocument.referendumOptionen); // ELECT-8714

    stimmeingabeDocument.referendumVorlagen.add(referendumVorlageStimmenDocument);
  }

  private ReferendumVorlageStimmenDocument createReferendumVorlageStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> referendumVorlagenImGebiet, GruppeImGebiet referendumVorlageImGebiet, Long laufendeNummer, Long anzahlStimmenGueltig, Long anzahlStimmenUngueltig,
      Long anzahlStimmenAbgegeben) {
    ReferendumVorlageStimmenDocument referendumVorlageStimmenDocument = createReferendumVorlageStimmenDocument(stimmeingabeDocumentContext, referendumVorlageImGebiet);

    fillInReferendumVorlageStimmenDocument(referendumVorlageStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet,
        laufendeNummer, anzahlStimmenGueltig, anzahlStimmenUngueltig, anzahlStimmenAbgegeben);

    return referendumVorlageStimmenDocument;
  }

  private ReferendumVorlageStimmenDocument createReferendumVorlageStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, GruppeImGebiet referendumVorlageImGebiet) {
    ReferendumVorlageStimmenDocument referendumVorlageStimmenDocument = stimmeingabeDocumentBuilder.createReferendumVorlageStimmenDocument(stimmeingabeDocumentContext);
    referendumVorlageStimmenDocument.gruppeDokument = stimmeingabeDocumentBuilder.createGruppeDocument(stimmeingabeDocumentContext, referendumVorlageImGebiet.getGruppe());

    return referendumVorlageStimmenDocument;
  }

  private void fillInReferendumVorlageStimmenDocument(ReferendumVorlageStimmenDocument referendumVorlageStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet, GruppeImGebiet referendumVorlageImGebiet, Long laufendeNummer,
      Long anzahlStimmenGueltig, Long anzahlStimmenUngueltig, Long anzahlStimmenAbgegeben) {
    // Cf. ReferendumVorlageMitStimmergebnissenDocument!
    WahlvorschlagStimmzettelPosition referendumVorlageStimmzettelPosition = referendumVorlageImGebiet.getStimmzettelPosition(entityAufStimmzettelDaten);

    referendumVorlageStimmenDocument.position = referendumVorlageStimmzettelPosition.getPosition();

    referendumVorlageStimmenDocument.positionFormatted = referendumVorlageStimmzettelPosition.formatPosition();
    referendumVorlageStimmenDocument.kennzahlFormatted = referendumVorlageStimmzettelPosition.formatKennzahl();

    referendumVorlageStimmenDocument.positionFortlaufend = laufendeNummer;

    referendumVorlageStimmenDocument.kennung = formatReferendumVorlageKennung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet);
    referendumVorlageStimmenDocument.bezeichnung = formatReferendumVorlageBezeichnung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet);

    referendumVorlageStimmenDocument.kennungGueltig = formatReferendumOptionStimmeAnzahlGueltigeGesamtKennung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet);
    referendumVorlageStimmenDocument.bezeichnungGueltig = formatReferendumOptionStimmeAnzahlGueltigeGesamtBezeichnung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet);

    referendumVorlageStimmenDocument.kennungUngueltig = formatReferendumOptionStimmeAnzahlUngueltigeGesamtKennung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet);
    referendumVorlageStimmenDocument.bezeichnungUngueltig = formatReferendumOptionStimmeAnzahlUngueltigeGesamtBezeichnung(entityAufStimmzettelDaten, referendumVorlagenImGebiet,
        referendumVorlageImGebiet);

    if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
      referendumVorlageStimmenDocument.stimmenGueltig = anzahlStimmenGueltig;
      referendumVorlageStimmenDocument.stimmenUngueltig = anzahlStimmenUngueltig;
      referendumVorlageStimmenDocument.stimmen = anzahlStimmenAbgegeben;
    }
  }

  private ReferendumOptionStimmenDocument createReferendumOptionStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Collection<GruppeImGebiet> referendumVorlagenImGebiet, GruppeImGebiet referendumVorlageImGebiet, ReferendumOption referendumOption, Long anzahlStimmen) {
    ReferendumOptionStimmenDocument referendumOptionStimmenDocument = createReferendumOptionStimmenDocument(stimmeingabeDocumentContext);

    fillInReferendumOptionStimmenDocument(referendumOptionStimmenDocument, stimmeingabeDocumentContext, entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet,
        referendumOption, anzahlStimmen);

    return referendumOptionStimmenDocument;
  }

  private ReferendumOptionStimmenDocument createReferendumOptionStimmenDocument(StimmeingabeDocumentContext stimmeingabeDocumentContext) {
    return stimmeingabeDocumentBuilder.createReferendumOptionStimmenDocument(stimmeingabeDocumentContext);
  }

  private void fillInReferendumOptionStimmenDocument(ReferendumOptionStimmenDocument referendumOptionStimmenDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet, GruppeImGebiet referendumVorlageImGebiet, ReferendumOption referendumOption,
      Long anzahlStimmen) {
    // ELECT-9374, cf. ReferendumOptionMitStimmergebnissenDocument!
    referendumOptionStimmenDocument.position = referendumOption.getPosition();

    referendumOptionStimmenDocument.positionFormatted = DokumentFormatter.formatNumber(referendumOption.getPosition(), stimmeingabeDocumentContext.getLocale());
    referendumOptionStimmenDocument.kennzahlFormatted = referendumOptionStimmenDocument.positionFormatted;

    referendumOptionStimmenDocument.kennung = formatReferendumOptionStimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet, referendumOption);
    referendumOptionStimmenDocument.bezeichnung = formatReferendumOptionStimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, referendumVorlagenImGebiet, referendumVorlageImGebiet,
        referendumOption);

    referendumOptionStimmenDocument.referendumOptionKurzbezeichnung = referendumOption.getKurzbezeichnung();
    referendumOptionStimmenDocument.referendumOptionLabel = referendumOption.getLabel();

    if (stimmeingabeDocumentContext.isEingabenAnzahlenStimmenIncluded()) {
      referendumOptionStimmenDocument.anzahlStimmen = anzahlStimmen;
    }
  }

  @Nullable
  protected Long sum(@Nullable Long long1, @Nullable Long long2) {
    if (long1 == null) {
      return long2;
    }

    if (long2 == null) {
      return long1;
    }

    return long1 + long2;
  }

  protected long fixNullToZero(Long value) {
    if (value == null) {
      return 0;
    }

    return value;
  }

  private String formatDirektstimmeAnzahlGueltigeKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet,
      WahlvorschlagStimmzettelPosition direktkandidatStimmzettelPosition) {
    return getDirektstimmeLabelGenerator().formatDirektstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppeImGebiet, direktkandidatStimmzettelPosition);
  }

  private String formatDirektstimmeAnzahlGueltigeBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet) {
    return getDirektstimmeLabelGenerator().formatDirektstimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppeImGebiet, gruppenImGebiet);
  }

  private String formatDirektstimmeAnzahlGueltigeGruppeBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet) {
    return getDirektstimmeLabelGenerator().formatDirektstimmeAnzahlGueltigeGruppeBezeichnung(entityAufStimmzettelDaten, gruppeImGebiet, gruppenImGebiet);
  }

  protected DirektstimmeLabelGenerator getDirektstimmeLabelGenerator() {
    return gruppeStimmeLabelGenerator.getDirektstimmeLabelGenerator();
  }

  private String formatListenstimmeAnzahlGueltigeKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet,
      WahlvorschlagStimmzettelPosition listeStimmzettelPosition) {
    return getListenstimmeLabelGenerator().formatListenstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppeImGebiet, listeStimmzettelPosition);
  }

  private String formatListenstimmeAnzahlGueltigeBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet) {
    return getListenstimmeLabelGenerator().formatListenstimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, gruppeImGebiet, gruppenImGebiet);
  }

  private String formatListenstimmeAnzahlGueltigeGruppeBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet) {
    return getListenstimmeLabelGenerator().formatListenstimmeAnzahlGueltigeGruppeBezeichnung(entityAufStimmzettelDaten, gruppeImGebiet, gruppenImGebiet);
  }

  protected ListenstimmeLabelGenerator getListenstimmeLabelGenerator() {
    return gruppeStimmeLabelGenerator.getListenstimmeLabelGenerator();
  }

  private String formatListenkandidatStimmenAnzahlGueltigeKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet,
      WahlvorschlagStimmzettelPosition listeStimmzettelPosition, ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition) {
    return getListenkandidaturstimmeLabelGenerator().formatListenstimmeAnzahlGueltigeKennung(listeStimmzettelPosition, listenkandidaturStimmzettelPosition);
  }

  private String formatListenkandidatStimmenAnzahlGueltigeBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> gruppenImGebiet, GruppeImGebiet gruppeImGebiet,
      Listenkandidatur listenkandidatur) {
    return getListenkandidaturstimmeLabelGenerator().getListenkandidaturStimmzettelName(entityAufStimmzettelDaten, listenkandidatur).getName();
  }

  protected ListenkandidaturstimmeLabelGenerator getListenkandidaturstimmeLabelGenerator() {
    return getListenstimmeLabelGenerator().getListenkandidaturstimmeLabelGenerator();
  }

  private String formatReferendumVorlageKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet, GruppeImGebiet referendumVorlageImGebiet) {
    return getReferendumStimmeLabelGenerator().formatReferendumVorlageKennung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet);
  }

  private String formatReferendumVorlageBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet) {
    return getReferendumStimmeLabelGenerator().formatReferendumVorlageBezeichnung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet);
  }

  private String formatReferendumOptionStimmeAnzahlGueltigeKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet, ReferendumOption referendumOption) {
    return getReferendumStimmeLabelGenerator().formatReferendumOptionStimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet, referendumOption);
  }

  private String formatReferendumOptionStimmeAnzahlGueltigeBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet, ReferendumOption referendumOption) {
    return getReferendumStimmeLabelGenerator().formatReferendumOptionStimmeAnzahlGueltigeBezeichnung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet,
        referendumOption);
  }

  private String formatReferendumOptionStimmeAnzahlUngueltigeGesamtKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet) {
    return getReferendumStimmeLabelGenerator().formatReferendumOptionStimmeAnzahlUngueltigeGesamtKennung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet);
  }

  private String formatReferendumOptionStimmeAnzahlUngueltigeGesamtBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet) {
    return getReferendumStimmeLabelGenerator().formatReferendumOptionStimmeAnzahlUngueltigeGesamtBezeichnung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet);
  }

  private String formatReferendumOptionStimmeAnzahlGueltigeGesamtKennung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet) {
    return getReferendumStimmeLabelGenerator().formatReferendumOptionStimmeAnzahlGueltigeGesamtKennung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet);
  }

  private String formatReferendumOptionStimmeAnzahlGueltigeGesamtBezeichnung(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<GruppeImGebiet> referendumVorlagenImGebiet,
      GruppeImGebiet referendumVorlageImGebiet) {
    return getReferendumStimmeLabelGenerator().formatReferendumOptionStimmeAnzahlGueltigeGesamtBezeichnung(entityAufStimmzettelDaten, referendumVorlageImGebiet, referendumVorlagenImGebiet);
  }

  protected ReferendumStimmeLabelGenerator getReferendumStimmeLabelGenerator() {
    return gruppeStimmeLabelGenerator.getReferendumStimmeLabelGenerator();
  }

  protected static void sortByPosition(List< ? extends AbstractStimmenDocument> stimmenDocuments) {
    Collections.sort(stimmenDocuments, new StimmenDocumentByPositionComparator());
  }

  protected void fillInErfassteStimmzettel(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, List<Stimmzettel> erfassteStimmzettel) {
    for (Stimmzettel stimmzettel : erfassteStimmzettel) {
      stimmeingabeDocument.erfassteStimmzettel.add(new ErfassterStimmzettelDocument(stimmeingabeDocumentContext, stimmzettel));
    }
  }

  protected void fillInKopfzahlen(StimmeingabeDocument stimmeingabeDocument, StimmeingabeDocumentContext stimmeingabeDocumentContext, G gebiet, Long anzahlWahlberechtigteGesamt,
      Long anzahlWahlberechtigteOhneWahlschein, Long anzahlWahlberechtigteMitWahlschein, Long anzahlWahlberechtigteNichtImWaehlerverzeichnis, Long anzahlWaehlerGesamt,
      Long anzahlWaehlerOhneWahlschein, Long anzahlWaehlerMitWahlschein, Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl, Long anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl,
      Long anzahlWaehlerMitVollmacht, Function<G, Long> anzahlWahlberechtigteOhneWahlscheinFct, Function<G, Long> anzahlWahlberechtigteMitWahlscheinFct,
      Function<G, Long> anzahlWahlberechtigteNichtImWaehlerverzeichnisFct) {
    if (stimmeingabeDocumentContext.isEingabenAnzahlenWahlberechtigteIncluded()) {
      stimmeingabeDocument.wahlberechtigteOhneWahlschein = anzahlWahlberechtigteOhneWahlschein;
      stimmeingabeDocument.wahlberechtigteMitWahlschein = anzahlWahlberechtigteMitWahlschein;
      stimmeingabeDocument.wahlberechtigteNichtImWaehlerverzeichnis = anzahlWahlberechtigteNichtImWaehlerverzeichnis;
      stimmeingabeDocument.wahlberechtigteGesamt = anzahlWahlberechtigteGesamt;
    }

    if (stimmeingabeDocumentContext.isGebieteAnzahlenWahlberechtigteIncluded()) {
      Long anzahlWahlberechtigteOhneWahlscheinAmGebiet = anzahlWahlberechtigteOhneWahlscheinFct.apply(gebiet);
      Long anzahlWahlberechtigteMitWahlscheinAmGebiet = anzahlWahlberechtigteMitWahlscheinFct.apply(gebiet);
      Long anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet = anzahlWahlberechtigteNichtImWaehlerverzeichnisFct.apply(gebiet);

      Long anzahlWahlberechtigteGesamtAmGebiet = null;
      anzahlWahlberechtigteGesamtAmGebiet = sum(anzahlWahlberechtigteGesamtAmGebiet, anzahlWahlberechtigteOhneWahlscheinAmGebiet);
      anzahlWahlberechtigteGesamtAmGebiet = sum(anzahlWahlberechtigteGesamtAmGebiet, anzahlWahlberechtigteMitWahlscheinAmGebiet);
      anzahlWahlberechtigteGesamtAmGebiet = sum(anzahlWahlberechtigteGesamtAmGebiet, anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet);

      if (anzahlWahlberechtigteGesamtAmGebiet != null && anzahlWahlberechtigteGesamtAmGebiet > 0) { // ELECTKOM-615
        stimmeingabeDocument.wahlberechtigteOhneWahlschein = anzahlWahlberechtigteOhneWahlscheinAmGebiet;
        stimmeingabeDocument.wahlberechtigteMitWahlschein = anzahlWahlberechtigteMitWahlscheinAmGebiet;
        stimmeingabeDocument.wahlberechtigteNichtImWaehlerverzeichnis = anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet;
        stimmeingabeDocument.wahlberechtigteGesamt = anzahlWahlberechtigteGesamtAmGebiet;
      }
    }

    // ELECT-8018, LEGACY!!!
    stimmeingabeDocument.a1 = stimmeingabeDocument.wahlberechtigteOhneWahlschein;
    stimmeingabeDocument.a2 = stimmeingabeDocument.wahlberechtigteMitWahlschein;
    stimmeingabeDocument.a3 = stimmeingabeDocument.wahlberechtigteNichtImWaehlerverzeichnis;
    stimmeingabeDocument.a = stimmeingabeDocument.wahlberechtigteGesamt;

    // ELECT-7727, ELECT-9319
    if (stimmeingabeDocumentContext.isEingabenAnzahlenWaehlerIncluded()) {
      StimmenWaehler stimmenWaehler = new StimmenWaehler(gebiet, //
          () -> anzahlWaehlerGesamt, //

          () -> anzahlWaehlerOhneWahlschein, //

          () -> anzahlWaehlerMitWahlschein, //
          () -> anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl, //
          () -> anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, //

          () -> anzahlWaehlerMitVollmacht //
      );

      stimmeingabeDocument.waehlerOhneWahlschein = stimmenWaehler.getAnzahlOhneWahlschein();
      stimmeingabeDocument.waehlerMitWahlschein = stimmenWaehler.getAnzahlMitWahlschein();
      stimmeingabeDocument.waehlerMitWahlscheinAnteilAusUrnenwahl = stimmenWaehler.getAnzahlMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl();
      stimmeingabeDocument.waehlerMitWahlscheinAnteilAusBriefwahl = stimmenWaehler.getAnzahlMitWahlscheinInUrnenwahlAnzahlAusBriefwahl();
      stimmeingabeDocument.waehlerMitVollmacht = stimmenWaehler.getAnzahlMitVollmacht();
      stimmeingabeDocument.waehlerGesamt = stimmenWaehler.getAnzahlGesamt();
    }

    // ELECT-8018, LEGACY!!!
    stimmeingabeDocument.b1 = stimmeingabeDocument.waehlerMitWahlschein;
    stimmeingabeDocument.b2 = stimmeingabeDocument.waehlerOhneWahlschein;
    stimmeingabeDocument.b = stimmeingabeDocument.waehlerGesamt;
  }
}
