
package de.ivu.elect.was.exchange.ergebnis.zusammenstellung;

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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import javax.inject.Inject;

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

import de.ivu.elect.business.ergebnis.control.AbstractErgebnisDatenWahlberechtigte;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenDirektkandidaturen;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenGueltige;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenListenkandidaturen;
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.WahlverwaltungGebietDescriptor;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationByNummerComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
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.stimmen.boundary.DirektstimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.GruppeStimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.KopfzahlLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.ListenkandidaturstimmeLabelGenerator;
import de.ivu.elect.business.stimmen.boundary.ListenstimmeLabelGenerator;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDatenFactory;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;
import de.ivu.elect.core.exchange.gebietsbaum.WahlverwaltungGebietInformationCollector;
import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.util.TriFunction;

public class ErgebnisZusammenstellungDokumentFactory implements Serializable {

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(ErgebnisZusammenstellungDokumentFactory.class);

  private static final Comparator<WahlverwaltungGebietInformation> GEBIET_DEFAULT_COMPARATOR = WahlverwaltungGebietInformationByNummerComparator.ASCENDING;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient StimmzettelDatenFactory stimmzettelDatenFactory;

  @Inject
  private transient WahlverwaltungGebietInformationCollector wahlverwaltungGebietInformationCollector;

  @Inject
  private transient KopfzahlLabelGenerator kopfzahlLabelGenerator;

  @Inject
  private transient GruppeStimmeLabelGenerator gruppeStimmeLabelGenerator;

  public ErgebnisZusammenstellungDokumentFactory() {
    super();
  }

  public ErgebnisZusammenstellungDokument createByAuswertungsgebiete(ErgebnisDaten ergebnisDaten, List<Wahldurchfuehrungsgebiet> auswertungsgebiete) {
    return create(ergebnisDaten, () -> collectTabellenByAuswertungsgebiete(ergebnisDaten, auswertungsgebiete));
  }

  public ErgebnisZusammenstellungDokument createByGebietsgruppierungen(ErgebnisDaten ergebnisDaten, List<Gebietsgruppierung> gebietsgruppierungen) {
    return create(ergebnisDaten, () -> collectTabellenByGebietsgruppierungen(ergebnisDaten, gebietsgruppierungen));
  }

  private ErgebnisZusammenstellungDokument create(ErgebnisDaten ergebnisDaten, Supplier<List<ErgebnisZusammenstellungTabelle>> tabellenFct) {
    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

    return new ErgebnisZusammenstellungDokument(wahldurchfuehrung.getName(), wahldurchfuehrung.getWahldatum(), ergebnisDaten.getErgebnis().getErgebnisart(), tabellenFct.get());
  }

  private List<ErgebnisZusammenstellungTabelle> collectTabellenByAuswertungsgebiete(ErgebnisDaten ergebnisDaten, List<Wahldurchfuehrungsgebiet> auswertungsgebiete) {
    LOG.info("Erzeuge Zusammenstellung für Auswertungsgebiete (in Gebietsbaum)");

    for (Wahldurchfuehrungsgebiet auswertungsgebiet : auswertungsgebiete) {
      LOG.info("\t Gebiet [art = {}, nummer = {}, name = {}, gebiet in gebietsbaum = {}, wurzelgebiet = {}]", auswertungsgebiet.getGebietsart().getName(), auswertungsgebiet.getNummer(),
          auswertungsgebiet.getName(), true, auswertungsgebiet.isWurzelgebiet());
    }

    Predicate<Gebiet> erfassungsgebietFilter = erfassungsgebiet -> auswertungsgebiete.stream().anyMatch(erfassungsgebiet::isUntergebietVon);

    if (auswertungsgebiete.size() == 1 && auswertungsgebiete.get(0).isWurzelgebiet()) {
      erfassungsgebietFilter = erfassungsgebiet -> true;
    }

    return collectTabellen(ergebnisDaten, erfassungsgebietFilter);
  }

  private List<ErgebnisZusammenstellungTabelle> collectTabellenByGebietsgruppierungen(ErgebnisDaten ergebnisDaten, List<Gebietsgruppierung> gebietsgruppierungen) {
    LOG.info("Erzeuge Zusammenstellung für Gebietsgruppierungen");

    for (Gebietsgruppierung gebietsgruppierung : gebietsgruppierungen) {
      LOG.info("\t Gebietsgruppierung [art = {}, nummer = {}, name = {}]", gebietsgruppierung.getVerwaltungsart(), gebietsgruppierung.getNummer(), gebietsgruppierung.getName());
    }

    Predicate<Gebiet> erfassungsgebietFilter = erfassungsgebiet -> gebietsgruppierungen.stream()
        .anyMatch(gebietsgruppierung -> gebietsgruppierung.isEnthaeltGebietOderUebergeordnetesGebietVon(erfassungsgebiet));

    return collectTabellen(ergebnisDaten, erfassungsgebietFilter);
  }

  private List<ErgebnisZusammenstellungTabelle> collectTabellen(ErgebnisDaten ergebnisDaten, Predicate<Gebiet> erfassungsgebietFilter) {
    return collectTabellen(ergebnisDaten, new WahlverwaltungGebietInformation(ergebnisDaten.getWurzelgebiet()), collectErfassungsgebietePerStimmzettelgebiet(ergebnisDaten, erfassungsgebietFilter));
  }

  private List<ErgebnisZusammenstellungTabelle> collectTabellen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformation wurzelgebiet,
      SortedMap<WahlverwaltungGebietInformation, List<WahlverwaltungGebietDescriptor>> erfassungsgebietePerStimmzettelgebiet) {
    List<ErgebnisZusammenstellungTabelle> result = new ArrayList<>();

    boolean existsAnyStimmzettelgebietMitErfassungsgebietUntergeordnet = true; // Standardfall...

    for (var entry : erfassungsgebietePerStimmzettelgebiet.entrySet()) {
      WahlverwaltungGebietInformation stimmzettelgebiet = entry.getKey();
      List<WahlverwaltungGebietDescriptor> erfassungsgebiete = entry.getValue();

      if (erfassungsgebiete.isEmpty()) {
        LOG.info("ÜBERSPRINGE Gesamttabelle für Stimmzettelgebiet ohne Erfassungsgebiete [art = {}, nummer = {}, name = {}, gebiet in gebietsbaum = {}]", stimmzettelgebiet.getArt(),
            stimmzettelgebiet.getNummer(), stimmzettelgebiet.getName(), stimmzettelgebiet.getGebiet().isPresent());
      } else {
        LOG.info("Erzeuge Gesamttabelle für Stimmzettelgebiet [art = {}, nummer = {}, name = {}, gebiet in gebietsbaum = {}, erfassungsgebiete = {}]", stimmzettelgebiet.getArt(),
            stimmzettelgebiet.getNummer(), stimmzettelgebiet.getName(), stimmzettelgebiet.getGebiet().isPresent(), erfassungsgebiete.size());

        existsAnyStimmzettelgebietMitErfassungsgebietUntergeordnet &= erfassungsgebiete.size() > 1 || !stimmzettelgebiet.equals(erfassungsgebiete.get(0).getErfassungsgebiet());

        result.add(createTabelle(ergebnisDaten, stimmzettelgebiet, erfassungsgebiete));
      }
    }

    if (wurzelgebiet.isWahlgebiet() && erfassungsgebietePerStimmzettelgebiet.size() > 1 && !existsAnyStimmzettelgebietMitErfassungsgebietUntergeordnet) { // ELECT-16267
      LOG.info("VERWERFE GESAMTTABELLEN FÜR STIMMZETTELGEBIETE, da alle Stimmzettelgebiete gleichzeitig Erfassungsgebiete sind!!!");

      /*
       * Bspw. KOM-BY BZW (Bezirk)...
       * - Wahlkreis -> Wurzelgebiet == Wahlgebiet == Listenwahlgebiet
       * -- Stimmkreis 1 -> Erfassungsgebiet == Direktwahlgebiet == Stimmzettelgebiet
       * -- Stimmkreis 2
       * -- ...
       */

      LOG.info("ERZEUGE NUR GESAMTTABELLE FÜR WURZELGEBIET [art = {}, nummer = {}, name = {}, gebiet in gebietsbaum = {}, ERFASSUNGSGEBIETE == STIMMZETTELGEBIETE = {}]", wurzelgebiet.getArt(),
          wurzelgebiet.getNummer(), wurzelgebiet.getName(), wurzelgebiet.getGebiet().isPresent(), erfassungsgebietePerStimmzettelgebiet.size());

      Collection<ErgebnisZusammenstellungZeile> zeilen = new ArrayList<>();

      for (ErgebnisZusammenstellungTabelle tabelle : result) {
        zeilen.addAll(tabelle.getZeilen()); // ELECT-16320
      }

      result.clear();
      result.add(new ErgebnisZusammenstellungTabelle(wurzelgebiet, zeilen));
    }

    return result;
  }

  private SortedMap<WahlverwaltungGebietInformation, List<WahlverwaltungGebietDescriptor>> collectErfassungsgebietePerStimmzettelgebiet(ErgebnisDaten ergebnisDaten,
      Predicate<Gebiet> erfassungsgebietFilter) {
    SortedMap<WahlverwaltungGebietInformation, List<WahlverwaltungGebietDescriptor>> result = new TreeMap<>(GEBIET_DEFAULT_COMPARATOR);

    TriFunction<WahlverwaltungGebietInformation, WahlverwaltungGebietInformation, WahlverwaltungGebietDescriptor, WahlverwaltungGebietDescriptor> erfassungsgebietMapper = (
        stimmzettelgebietInformation, gemeindeInformation, erfassungsgebietDescriptor) -> erfassungsgebietDescriptor;

    for (var entryPerStimmzettelgebiet : wahlverwaltungGebietInformationCollector.collectErfassungsgebieteGroupedByDefault(ergebnisDaten, true, false, erfassungsgebietFilter, erfassungsgebietMapper)
        .entrySet()) {
      WahlverwaltungGebietInformation stimmzettelgebiet = entryPerStimmzettelgebiet.getKey();

      List<WahlverwaltungGebietDescriptor> erfassungsgebieteInStimmzettelgebiet = entryPerStimmzettelgebiet.getValue().entrySet().stream() //
          .flatMap(entryPerGemeinde -> entryPerGemeinde.getValue().stream()) //
          .collect(toList());

      if (stimmzettelgebiet.isUnbekannt()) {
        throw new ElectCommonException("ergebnis.export.message.stimmzettelgebiet.not.found", stimmzettelgebiet.getArt(), erfassungsgebieteInStimmzettelgebiet.size());
      }

      result.put(stimmzettelgebiet, erfassungsgebieteInStimmzettelgebiet);
    }

    return result;
  }

  private ErgebnisZusammenstellungTabelle createTabelle(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformation stimmzettelgebiet, List<WahlverwaltungGebietDescriptor> erfassungsgebiete) {
    if (erfassungsgebiete.isEmpty()) {
      return new ErgebnisZusammenstellungTabelle(stimmzettelgebiet, new ArrayList<>());
    }

    StimmzettelDaten stimmzettelDaten = stimmzettelDatenFactory.create(entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten(), erfassungsgebiete.get(0).getGebiet(),
        ergebnisDaten.getWahlvorschlaege()); // Stimmzettelgebiet -> ALLE Erfassungsgebiete haben dieselben Wahlvorschläge...

    List<Liste> listen = stimmzettelDaten.getListen();
    Map<Liste, List<Listenkandidatur>> listenkandidaturenPerListe = listen.stream().collect(toMap(Function.identity(), stimmzettelDaten::getListenkandidaturen));
    List<Direktkandidatur> direktkandidaturen = stimmzettelDaten.getDirektkandidaten().stream().map(Direktkandidat::getDirektkandidatur).collect(toList());

    List<ErgebnisZusammenstellungZeile> zeilen = erfassungsgebiete.stream() //
        .map(erfassungsgebiet -> createZeile(ergebnisDaten, erfassungsgebiet, listen, listenkandidaturenPerListe, direktkandidaturen)) //
        .collect(toList());

    return new ErgebnisZusammenstellungTabelle(stimmzettelgebiet, zeilen);
  }

  private ErgebnisZusammenstellungZeile createZeile(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietDescriptor erfassungsgebiet, List<Liste> listen,
      Map<Liste, List<Listenkandidatur>> listenkandidaturenPerListe, List<Direktkandidatur> direktkandidaturen) {
    Stimmabgabeart stimmabgabeart = Stimmabgabeart.GESAMT; // Erfassungsgebiet -> ist Urnenwahlgebiet oder Briefwahlgebiet oder bereits kumuliert...
    GebietUndStimmabgabeart gebietUndStimmabgabeart = new GebietUndStimmabgabeart(castTo(erfassungsgebiet.getGebiet(), Wahldurchfuehrungsgebiet.class), stimmabgabeart);

    ErgebnisZusammenstellungZeileDatengebiet datengebiet = createDatengebiet(erfassungsgebiet);

    return new ErgebnisZusammenstellungZeile( //
        datengebiet, //
        getStimmabgabeart(datengebiet), //

        createErfassung(ergebnisDaten, gebietUndStimmabgabeart), //

        createWaehler(ergebnisDaten, gebietUndStimmabgabeart), //
        createWahlberechtigte(ergebnisDaten, gebietUndStimmabgabeart), //

        createStimmzettel(ergebnisDaten, gebietUndStimmabgabeart), //
        createStimmen(ergebnisDaten, gebietUndStimmabgabeart), //

        collectListen(ergebnisDaten, gebietUndStimmabgabeart, listen, listenkandidaturenPerListe), //
        collectDirektkandidaturen(ergebnisDaten, gebietUndStimmabgabeart, direktkandidaturen) //
    );
  }

  private ErgebnisZusammenstellungZeileDatengebiet createDatengebiet(WahlverwaltungGebietDescriptor erfassungsgebiet) {
    Gebiet gebiet = erfassungsgebiet.getGebiet();

    if (gebiet.isUrnenwahlgebiet()) {
      return new ErgebnisZusammenstellungZeileUrnenwahlgebiet(erfassungsgebiet);
    }

    if (gebiet.isBriefwahlgebiet()) {
      return new ErgebnisZusammenstellungZeileBriefwahlgebiet(erfassungsgebiet);
    }

    return new ErgebnisZusammenstellungZeileErfassungsgebiet(erfassungsgebiet);
  }

  private Stimmabgabeart getStimmabgabeart(ErgebnisZusammenstellungZeileDatengebiet datengebiet) {
    if (datengebiet.isUrnenwahlgebiet()) {
      return Stimmabgabeart.URNE;
    }

    if (datengebiet.isBriefwahlgebiet()) {
      return Stimmabgabeart.BRIEF;
    }

    return Stimmabgabeart.GESAMT;
  }

  private ErgebnisZusammenstellungErfassung createErfassung(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return new ErgebnisZusammenstellungErfassung(ergebnisDaten.getErgebnis().getErgebnisart(), gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
  }

  private ErgebnisZusammenstellungWaehler createWaehler(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    KopfzahlLabelGenerator kopfzahlLabelGenerator = getKopfzahlLabelGenerator();

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    ErgebnisDatenWaehler ergebnisDatenWaehler = ergebnisDaten.getErgebnisDatenWaehler();

    String anzahlOhneWahlscheinKennung = kopfzahlLabelGenerator.getAnzahlWaehlerOhneWahlscheinKennung(wahldurchfuehrungsgebiet);
    Long anzahlOhneWahlschein = ergebnisDatenWaehler.getSummeWaehlerOhneWahlschein(gebietUndStimmabgabeart);

    String anzahlMitWahlscheinKennung = kopfzahlLabelGenerator.getAnzahlWaehlerMitWahlscheinKennung(wahldurchfuehrungsgebiet);
    Long anzahlMitWahlschein = ergebnisDatenWaehler.getSummeWaehlerMitWahlschein(gebietUndStimmabgabeart);

    String anzahlMitWahlscheinInUrnenwahlAnzahlAusBriefwahlKennung = kopfzahlLabelGenerator.getAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlKennung(wahldurchfuehrungsgebiet);
    Long anzahlMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = ergebnisDatenWaehler.getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(gebietUndStimmabgabeart);

    String anzahlMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlKennung = kopfzahlLabelGenerator.getAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlKennung(wahldurchfuehrungsgebiet);
    Long anzahlMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = ergebnisDatenWaehler.getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(gebietUndStimmabgabeart);

    String anzahlGesamtKennung = kopfzahlLabelGenerator.getAnzahlWaehlerGesamtKennung(wahldurchfuehrungsgebiet);
    Long anzahlGesamt = ergebnisDatenWaehler.getSummeWaehlerGesamt(gebietUndStimmabgabeart);

    return new ErgebnisZusammenstellungWaehler( //
        anzahlOhneWahlscheinKennung, anzahlOhneWahlschein, //
        anzahlMitWahlscheinKennung, anzahlMitWahlschein, //
        anzahlMitWahlscheinInUrnenwahlAnzahlAusBriefwahlKennung, anzahlMitWahlscheinInUrnenwahlAnzahlAusBriefwahl, //
        anzahlMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlKennung, anzahlMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl, //
        anzahlGesamtKennung, anzahlGesamt //
    );
  }

  private ErgebnisZusammenstellungWahlberechtigte createWahlberechtigte(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    KopfzahlLabelGenerator kopfzahlLabelGenerator = getKopfzahlLabelGenerator();

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    AbstractErgebnisDatenWahlberechtigte ergebnisDatenWahlberechtigte = ergebnisDaten.getErgebnisDatenWahlberechtigte();

    String anzahlOhneWahlscheinKennung = kopfzahlLabelGenerator.getAnzahlWahlberechtigteOhneWahlscheinKennung(wahldurchfuehrungsgebiet);
    Long anzahlOhneWahlschein = ergebnisDatenWahlberechtigte.getSummeWahlberechtigteOhneWahlschein(gebietUndStimmabgabeart);

    String anzahlMitWahlscheinKennung = kopfzahlLabelGenerator.getAnzahlWahlberechtigteMitWahlscheinKennung(wahldurchfuehrungsgebiet);
    Long anzahlMitWahlschein = ergebnisDatenWahlberechtigte.getSummeWahlberechtigteMitWahlschein(gebietUndStimmabgabeart);

    String anzahlNichtImWaehlerverzeichnisKennung = kopfzahlLabelGenerator.getAnzahlWahlberechtigteNichtImWaehlerverzeichnisKennung(wahldurchfuehrungsgebiet);
    Long anzahlNichtImWaehlerverzeichnis = ergebnisDatenWahlberechtigte.getSummeWahlberechtigteNichtImWaehlerverzeichnis(gebietUndStimmabgabeart);

    String anzahlGesamtKennung = kopfzahlLabelGenerator.getAnzahlWahlberechtigteGesamtKennung(wahldurchfuehrungsgebiet);
    Long anzahlGesamtInklusiveNichtImWaehlerverzeichnis = ergebnisDatenWahlberechtigte.getSummeWahlberechtigteGesamtInklusiveNichtImWaehlerverzeichnis(gebietUndStimmabgabeart);
    Long anzahlGesamtExklusiveNichtImWaehlerverzeichnis = ergebnisDatenWahlberechtigte.getSummeWahlberechtigteGesamtExklusiveNichtImWaehlerverzeichnis(gebietUndStimmabgabeart);
    Long anzahlGesamt = ergebnisDatenWahlberechtigte.getSummeWahlberechtigteGesamt(gebietUndStimmabgabeart);

    return new ErgebnisZusammenstellungWahlberechtigte( //
        anzahlOhneWahlscheinKennung, anzahlOhneWahlschein, //
        anzahlMitWahlscheinKennung, anzahlMitWahlschein, //
        anzahlNichtImWaehlerverzeichnisKennung, anzahlNichtImWaehlerverzeichnis, //
        anzahlGesamtKennung, anzahlGesamtInklusiveNichtImWaehlerverzeichnis, anzahlGesamtExklusiveNichtImWaehlerverzeichnis, anzahlGesamt //
    );
  }

  private ErgebnisZusammenstellungStimmzettel createStimmzettel(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    DirektstimmeLabelGenerator direktstimmeLabelGenerator = getDirektstimmeLabelGenerator();
    ListenstimmeLabelGenerator listenstimmeLabelGenerator = getListenstimmeLabelGenerator();

    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    boolean wahlMitDirektstimmen = wahldurchfuehrung.isWahlMitDirektkandidatengebieten();
    boolean wahlMitListenstimmen = wahldurchfuehrung.isWahlMitListengebieten();

    ErgebnisDatenStimmzettel ergebnisDatenStimmzettel = ergebnisDaten.getErgebnisDatenStimmzettel();

    Long anzahlGueltig = ergebnisDatenStimmzettel.getStimmzettelAktuellGueltig(gebietUndStimmabgabeart);
    Long anzahlUngueltig = ergebnisDatenStimmzettel.getStimmzettelAktuellUngueltig(gebietUndStimmabgabeart);
    Long anzahlUnbestimmt = ergebnisDatenStimmzettel.getStimmzettelAktuellUnbestimmt(gebietUndStimmabgabeart);
    Long anzahlGesamt = ergebnisDatenStimmzettel.getStimmzettelAktuellGesamt(gebietUndStimmabgabeart);

    String direktstimmenGueltigKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlGueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long direktstimmenGueltig = wahlMitDirektstimmen ? anzahlGueltig : 0L;

    String direktstimmenUngueltigKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlUngueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long direktstimmenUngueltig = wahlMitDirektstimmen ? anzahlUngueltig : 0L;

    String direktstimmenUnbestimmtKennung = "";
    Long direktstimmenUnbestimmt = wahlMitDirektstimmen ? anzahlUnbestimmt : 0L;

    String direktstimmenGesamtKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlAbgegebeneGesamtKennung(wahldurchfuehrungsgebiet);
    Long direktstimmenGesamt = wahlMitDirektstimmen ? anzahlGesamt : 0L;

    String listenstimmenGueltigKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlGueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenGueltig = wahlMitListenstimmen ? anzahlGueltig : 0L;

    String listenstimmenUngueltigKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlUngueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenUngueltig = wahlMitListenstimmen ? anzahlUngueltig : 0L;

    String listenstimmenUnbestimmtKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlUnbestimmteGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenUnbestimmt = wahlMitListenstimmen ? anzahlUnbestimmt : 0L;

    String listenstimmenNurEinzelnerWahlvorschlagUnveraendertKennung = "";
    Long listenstimmenNurEinzelnerWahlvorschlagUnveraendert = wahlMitListenstimmen ? ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(gebietUndStimmabgabeart) : 0L;

    String listenstimmenNurEinzelnerWahlvorschlagVeraendertKennung = "";
    Long listenstimmenNurEinzelnerWahlvorschlagVeraendert = wahlMitListenstimmen ? ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(gebietUndStimmabgabeart) : 0L;

    String listenstimmenNurEinzelnerWahlvorschlagPanaschiertKennung = "";
    Long listenstimmenNurEinzelnerWahlvorschlagPanaschiert = wahlMitListenstimmen ? ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(gebietUndStimmabgabeart) : 0L;

    String listenstimmenKeinEinzelnerWahlvorschlagVeraendertKennung = "";
    Long listenstimmenKeinEinzelnerWahlvorschlagVeraendert = wahlMitListenstimmen ? ergebnisDatenStimmzettel.getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert(gebietUndStimmabgabeart) : 0L;

    String listenstimmenKeinEinzelnerWahlvorschlagPanaschiertKennung = "";
    Long listenstimmenKeinEinzelnerWahlvorschlagPanaschiert = wahlMitListenstimmen ? ergebnisDatenStimmzettel.getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert(gebietUndStimmabgabeart) : 0L;

    String listenstimmenGesamtKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlAbgegebeneGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenGesamt = wahlMitListenstimmen ? anzahlGesamt : 0L;

    return new ErgebnisZusammenstellungStimmzettel( //
        direktstimmenGueltigKennung, direktstimmenGueltig, //
        direktstimmenUngueltigKennung, direktstimmenUngueltig, //
        direktstimmenUnbestimmtKennung, direktstimmenUnbestimmt, //
        direktstimmenGesamtKennung, direktstimmenGesamt, //

        listenstimmenGueltigKennung, listenstimmenGueltig, //
        listenstimmenUngueltigKennung, listenstimmenUngueltig, //
        listenstimmenUnbestimmtKennung, listenstimmenUnbestimmt, //
        listenstimmenNurEinzelnerWahlvorschlagUnveraendertKennung, listenstimmenNurEinzelnerWahlvorschlagUnveraendert, //
        listenstimmenNurEinzelnerWahlvorschlagVeraendertKennung, listenstimmenNurEinzelnerWahlvorschlagVeraendert, //
        listenstimmenNurEinzelnerWahlvorschlagPanaschiertKennung, listenstimmenNurEinzelnerWahlvorschlagPanaschiert, //
        listenstimmenKeinEinzelnerWahlvorschlagVeraendertKennung, listenstimmenKeinEinzelnerWahlvorschlagVeraendert, //
        listenstimmenKeinEinzelnerWahlvorschlagPanaschiertKennung, listenstimmenKeinEinzelnerWahlvorschlagPanaschiert, //
        listenstimmenGesamtKennung, listenstimmenGesamt //
    );
  }

  private ErgebnisZusammenstellungStimmen createStimmen(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    DirektstimmeLabelGenerator direktstimmeLabelGenerator = getDirektstimmeLabelGenerator();
    ListenstimmeLabelGenerator listenstimmeLabelGenerator = getListenstimmeLabelGenerator();

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    ErgebnisDatenGueltige ergebnisDatenGueltige = ergebnisDaten.getErgebnisDatenGueltige();

    String direktstimmenGueltigKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlGueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long direktstimmenGueltig = ergebnisDatenGueltige.getErststimmenAktuellGueltig(gebietUndStimmabgabeart);

    String direktstimmenUngueltigKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlUngueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long direktstimmenUngueltig = ergebnisDatenGueltige.getErststimmenAktuellUngueltig(gebietUndStimmabgabeart);

    String direktstimmenGesamtKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlAbgegebeneGesamtKennung(wahldurchfuehrungsgebiet);
    Long direktstimmenGesamt = ergebnisDatenGueltige.getErststimmenAktuellGesamt(gebietUndStimmabgabeart);

    String listenstimmenGueltigKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlGueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenGueltig = ergebnisDatenGueltige.getZweitstimmenAktuellGueltig(gebietUndStimmabgabeart);

    String listenstimmenUngueltigKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlUngueltigeGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenUngueltig = ergebnisDatenGueltige.getZweitstimmenAktuellUngueltig(gebietUndStimmabgabeart);

    String listenstimmenGesamtKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlAbgegebeneGesamtKennung(wahldurchfuehrungsgebiet);
    Long listenstimmenGesamt = ergebnisDatenGueltige.getZweitstimmenAktuellGesamt(gebietUndStimmabgabeart);

    return new ErgebnisZusammenstellungStimmen( //
        direktstimmenGueltigKennung, direktstimmenGueltig, //
        direktstimmenUngueltigKennung, direktstimmenUngueltig, //
        direktstimmenGesamtKennung, direktstimmenGesamt, //

        listenstimmenGueltigKennung, listenstimmenGueltig, //
        listenstimmenUngueltigKennung, listenstimmenUngueltig, //
        listenstimmenGesamtKennung, listenstimmenGesamt //
    );
  }

  private List<ErgebnisZusammenstellungDirektkandidatur> collectDirektkandidaturen(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart,
      List<Direktkandidatur> direktkandidaturen) {
    return direktkandidaturen.stream().map(direktkandidatur -> createDirektkandidatur(ergebnisDaten, gebietUndStimmabgabeart, direktkandidatur)).collect(toList());
  }

  private ErgebnisZusammenstellungDirektkandidatur createDirektkandidatur(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart, Direktkandidatur direktkandidatur) {
    DirektstimmeLabelGenerator direktstimmeLabelGenerator = getDirektstimmeLabelGenerator();

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    PolitischeGruppe politischeGruppe = direktkandidatur.getGruppe();
    GruppeImGebiet gruppeImGebiet = direktkandidatur.getGruppeImGebiet();

    Direktkandidat direktkandidat = direktkandidatur.getDirektkandidat();

    WahlvorschlagStimmzettelPosition direktkandidatStimmzettelPosition = direktkandidat.getStimmzettelPosition(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten);

    if (direktkandidatStimmzettelPosition == null) {
      direktkandidatStimmzettelPosition = WahlvorschlagStimmzettelPosition.EMPTY;
    }

    Optional<Kandidatur> identischVerknuepfteListenkandidatur = direktkandidatur.getIdentischVerknuepfteKandidaturen().stream().filter(Kandidatur::isListenkandidatur).findFirst();

    ErgebnisDatenDirektkandidaturen ergebnisDatenDirektkandidaturen = ergebnisDaten.getErgebnisDatenDirektkandidaturen();
    String stimmenGesamtKennung = direktstimmeLabelGenerator.formatDirektstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppeImGebiet, direktkandidatStimmzettelPosition);
    Long stimmenGesamt = ergebnisDatenDirektkandidaturen.getStimmenFuerDirektkandidatur(direktkandidatur, gebietUndStimmabgabeart);

    return new ErgebnisZusammenstellungDirektkandidatur( //
        createGruppe(ergebnisDaten, politischeGruppe), //

        direktkandidatStimmzettelPosition, //
        direktkandidat.getKurznameOderKennwort(), //

        identischVerknuepfteListenkandidatur.map(Kandidatur::getPk), //

        direktkandidatur.getPk(), //
        direktkandidatur.getStimmzettelPersonendaten(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten), //

        direktkandidatur.getTitel(), //
        direktkandidatur.getRufname(), //
        direktkandidatur.getFamilienname(), //
        direktkandidatur.getFamiliennameBestandteil(), //

        stimmenGesamtKennung, stimmenGesamt //
    );
  }

  private List<ErgebnisZusammenstellungListe> collectListen(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart, List<Liste> listen,
      Map<Liste, List<Listenkandidatur>> listenkandidaturenPerListe) {
    return listen.stream().map(liste -> createListe(ergebnisDaten, gebietUndStimmabgabeart, liste, listenkandidaturenPerListe.getOrDefault(liste, new ArrayList<>()))).collect(toList());
  }

  private ErgebnisZusammenstellungListe createListe(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart, Liste liste, List<Listenkandidatur> listenkandidaturen) {
    ListenstimmeLabelGenerator listenstimmeLabelGenerator = getListenstimmeLabelGenerator();
    ListenkandidaturstimmeLabelGenerator listenkandidaturstimmeLabelGenerator = getListenkandidaturstimmeLabelGenerator();

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    PolitischeGruppe politischeGruppe = liste.getGruppe();
    GruppeImGebiet gruppeImGebiet = liste.getGruppeImGebiet();

    WahlvorschlagStimmzettelPosition listeStimmzettelPosition = liste.getStimmzettelPosition(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten);

    if (listeStimmzettelPosition == null) {
      listeStimmzettelPosition = WahlvorschlagStimmzettelPosition.EMPTY;
    }

    ErgebnisDatenStimmzettel ergebnisDatenStimmzettel = ergebnisDaten.getErgebnisDatenStimmzettel();
    ErgebnisDatenListenkandidaturen ergebnisDatenListenkandidaturen = ergebnisDaten.getErgebnisDatenListenkandidaturen();

    String stimmzettelNurEinzelnerWahlvorschlagUnveraendertKennung = "";
    Long stimmzettelNurEinzelnerWahlvorschlagUnveraendert = ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(liste, gebietUndStimmabgabeart);

    String stimmzettelNurEinzelnerWahlvorschlagVeraendertKennung = "";
    Long stimmzettelNurEinzelnerWahlvorschlagVeraendert = ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(liste, gebietUndStimmabgabeart);

    String stimmzettelNurEinzelnerWahlvorschlagPanaschiertKennung = "";
    Long stimmzettelNurEinzelnerWahlvorschlagPanaschiert = ergebnisDatenStimmzettel.getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(liste, gebietUndStimmabgabeart);

    String stimmzettelKeinEinzelnerWahlvorschlagVeraendertKennung = "";
    Long stimmzettelKeinEinzelnerWahlvorschlagVeraendert = ergebnisDatenStimmzettel.getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert(liste, gebietUndStimmabgabeart);

    String stimmzettelKeinEinzelnerWahlvorschlagPanaschiertKennung = "";
    Long stimmzettelKeinEinzelnerWahlvorschlagPanaschiert = ergebnisDatenStimmzettel.getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert(liste, gebietUndStimmabgabeart);

    String stimmenNurListeKennung = listenkandidaturstimmeLabelGenerator.formatListenstimmeAnzahlGueltigeListeAbzueglichStimmenFuerAlleListenkandidaturenKennung(gruppeImGebiet,
        listeStimmzettelPosition);
    Long stimmenNurListe = ergebnisDatenListenkandidaturen.getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(politischeGruppe, gebietUndStimmabgabeart);

    String stimmenNurListenkandidaturenKennung = "";
    Long stimmenNurListenkandidaturen = ergebnisDatenListenkandidaturen.getStimmenFuerListenkandidaturen(liste, gebietUndStimmabgabeart);

    String stimmenGesamtKennung = listenstimmeLabelGenerator.formatListenstimmeAnzahlGueltigeKennung(entityAufStimmzettelDaten, gruppeImGebiet, listeStimmzettelPosition);
    Long stimmenGesamt = ergebnisDaten.getZweitstimmenAktuell(liste, wahldurchfuehrungsgebiet, gebietUndStimmabgabeart.getStimmabgabeart());

    return new ErgebnisZusammenstellungListe( //
        createGruppe(ergebnisDaten, politischeGruppe), //

        listeStimmzettelPosition, //
        liste.getKurznameOderKennwort(), //

        stimmzettelNurEinzelnerWahlvorschlagUnveraendertKennung, stimmzettelNurEinzelnerWahlvorschlagUnveraendert, //
        stimmzettelNurEinzelnerWahlvorschlagVeraendertKennung, stimmzettelNurEinzelnerWahlvorschlagVeraendert, //
        stimmzettelNurEinzelnerWahlvorschlagPanaschiertKennung, stimmzettelNurEinzelnerWahlvorschlagPanaschiert, //
        stimmzettelKeinEinzelnerWahlvorschlagVeraendertKennung, stimmzettelKeinEinzelnerWahlvorschlagVeraendert, //
        stimmzettelKeinEinzelnerWahlvorschlagPanaschiertKennung, stimmzettelKeinEinzelnerWahlvorschlagPanaschiert, //

        stimmenNurListeKennung, stimmenNurListe, //
        stimmenNurListenkandidaturenKennung, stimmenNurListenkandidaturen, //
        stimmenGesamtKennung, stimmenGesamt, //

        collectListenkandidaturen(ergebnisDaten, gebietUndStimmabgabeart, listenkandidaturen) //
    );
  }

  private List<ErgebnisZusammenstellungListenkandidatur> collectListenkandidaturen(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart,
      List<Listenkandidatur> listenkandidaturen) {
    return listenkandidaturen.stream().map(listenkandidatur -> createListenkandidatur(ergebnisDaten, gebietUndStimmabgabeart, listenkandidatur)).collect(toUnmodifiableList());
  }

  private ErgebnisZusammenstellungListenkandidatur createListenkandidatur(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart, Listenkandidatur listenkandidatur) {
    ListenkandidaturstimmeLabelGenerator listenkandidaturstimmeLabelGenerator = getListenkandidaturstimmeLabelGenerator();

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    PolitischeGruppe politischeGruppe = listenkandidatur.getGruppe();

    Liste liste = listenkandidatur.getListe();

    WahlvorschlagStimmzettelPosition listeStimmzettelPosition = liste.getStimmzettelPosition(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten);
    ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = listenkandidatur.getStimmzettelPosition(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten);

    if (listenkandidaturStimmzettelPosition == null) {
      listenkandidaturStimmzettelPosition = ListenkandidaturStimmzettelPosition.EMPTY;
    }

    Optional<Kandidatur> identischVerknuepfteDirektkandidatur = listenkandidatur.getIdentischVerknuepfteKandidaturen().stream().filter(Kandidatur::isDirektkandidatur).findFirst();

    ErgebnisDatenListenkandidaturen ergebnisDatenListenkandidaturen = ergebnisDaten.getErgebnisDatenListenkandidaturen();
    String stimmenGesamtKennung = listenkandidaturstimmeLabelGenerator.formatListenstimmeAnzahlGueltigeKennung(listeStimmzettelPosition, listenkandidaturStimmzettelPosition);
    Long stimmenGesamt = ergebnisDatenListenkandidaturen.getStimmenFuerListenkandidatur(listenkandidatur, gebietUndStimmabgabeart);

    return new ErgebnisZusammenstellungListenkandidatur( //
        createGruppe(ergebnisDaten, politischeGruppe), //

        listenkandidaturStimmzettelPosition, //

        identischVerknuepfteDirektkandidatur.map(Kandidatur::getPk), //

        listenkandidatur.getPk(), //
        listenkandidatur.getStimmzettelPersonendaten(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten), //

        listenkandidatur.getTitel(), //
        listenkandidatur.getRufname(), //
        listenkandidatur.getFamilienname(), //
        listenkandidatur.getFamiliennameBestandteil(), //

        stimmenGesamtKennung, stimmenGesamt //
    );
  }

  private ErgebnisZusammenstellungGruppe createGruppe(ErgebnisDaten ergebnisDaten, PolitischeGruppe politischeGruppe) {
    return new ErgebnisZusammenstellungGruppe(politischeGruppe.getPk(), politischeGruppe.getGruppeArt(), politischeGruppe.getSchluessel(), politischeGruppe.getKurzname(),
        politischeGruppe.getBerechnetePosition(ergebnisDaten.getPolitischeGruppen()));
  }

  private KopfzahlLabelGenerator getKopfzahlLabelGenerator() {
    return kopfzahlLabelGenerator;
  }

  private GruppeStimmeLabelGenerator getGruppeStimmeLabelGenerator() {
    return gruppeStimmeLabelGenerator;
  }

  private DirektstimmeLabelGenerator getDirektstimmeLabelGenerator() {
    return getGruppeStimmeLabelGenerator().getDirektstimmeLabelGenerator();
  }

  private ListenstimmeLabelGenerator getListenstimmeLabelGenerator() {
    return getGruppeStimmeLabelGenerator().getListenstimmeLabelGenerator();
  }

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