
package de.ivu.elect.business.gruppe.boundary;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.ejb.Stateless;
import javax.faces.application.FacesMessage;
import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.AbstractKumulierteStimmenSearch;
import de.ivu.elect.business.ergebnis.control.AbstractKumulierteStimmenSearchParameters;
import de.ivu.elect.business.ergebnis.control.StimmenFilter;
import de.ivu.elect.business.ergebnis.control.StimmenKumulierer;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.entity.AbstractGebietsgruppierung;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.control.KopfzahlenKumulierteStimmenByPositionComparator;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.KopfzahlKumulierteStimmen;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeKopfzahlErfassenModelBuilder;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.AbstractErgebnisGueltigerEingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebnisGueltigerEingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

@Stateless // Stateless, weil ergebnisDatenCreator#createErgebnisDaten eine aktive Session benötigt wird...
public class KopfzahlKumulierteStimmenSearch extends AbstractKumulierteStimmenSearch<AbstractKumulierteStimmenSearchParameters, KopfzahlKumulierteStimmen> {

  protected static final long SYSTEM_GRUPPE_POSTION_OFFSET = 10L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_GESAMT = 0L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_OHNE_WAHLSCHEIN = 1L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_MIT_VOLLMACHT = 2L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_MIT_WAHLSCHEIN = 3L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_ALS_URNENWAHL_IN_URNE = 5L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_NICHT_IM_WAEHLERVERZEICHNIS = 4L;

  protected static final long SYSTEM_GRUPPE_STIMMENART_OFFSET_ALS_BRIEFWAHL_IN_URNE = 6L;

  @Inject
  private transient WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietExtendedDAO;

  @Inject
  private transient StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration;

  public KopfzahlKumulierteStimmenSearch() {
    super();
  }

  @Override
  protected List<KopfzahlKumulierteStimmen> suche(Wahl wahl, Gebiet gebietSelected, Ergebnis ergebnisSelected, AbstractKumulierteStimmenSearchParameters searchParameters) {
    reset(); // reset bei Aufruf der Suche

    // ELECT-12152
    List<Wahldurchfuehrungsgebiet> alleGebiete = wahldurchfuehrungsgebietExtendedDAO.findByWahl(wahl);
    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : alleGebiete) {
      wahldurchfuehrungsgebiet.setBriefwahlInUrneErfassenInGebietOderDarunter(false);
    }
    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : alleGebiete) {
      if (Boolean.TRUE.equals(wahldurchfuehrungsgebiet.getBriefwahlInUrneErfassen())) {
        setzeBriefwahlUrne(wahldurchfuehrungsgebiet);
      }
    }

    Predicate<Gebiet> erfassungsgebietFilter = createErfassungsgebietFilter(ergebnisSelected, searchParameters);
    StimmenFilter stimmenFilter = createStimmenFilter(erfassungsgebietFilter);

    StimmenPool stimmenPool = createStimmenPoolReadonly(ergebnisSelected, stimmenFilter);
    StimmenPool stimmenPoolFuerDifferenz = createStimmenPoolReadonlyFuerDifferenz(wahl, searchParameters, stimmenFilter);

    List<KopfzahlKumulierteStimmen> result = collectKopfzahlenKumulierteStimmenInUntergeordneterGebietshierarchie(wahl, gebietSelected, ergebnisSelected, findAlleWahlvorschlaege(wahl), stimmenPool,
        stimmenPoolFuerDifferenz, erfassungsgebietFilter);

    if (searchParameters.getGebietsgruppierung() == null && Stimmabgabeart.GESAMT.equals(searchParameters.getStimmabgabeart())) {
      KopfzahlKumulierteStimmen wahlberechtigeGesamt = null;
      KopfzahlKumulierteStimmen wahlberechtigeMitWahlschein = null;

      KopfzahlKumulierteStimmen waehlerGesamt = null;
      KopfzahlKumulierteStimmen waehlerMitWahlschein = null;

      for (KopfzahlKumulierteStimmen kopfzahlKumulierteStimmen : result) {
        Stimmenart stimmenart = kopfzahlKumulierteStimmen.getStimmenart();

        if (Stimmenart.MIT_WAHLSCHEIN.equals(stimmenart)) {
          if (kopfzahlKumulierteStimmen.isSystemGruppeWahlberechtigte()) {
            wahlberechtigeMitWahlschein = kopfzahlKumulierteStimmen;
          } else if (kopfzahlKumulierteStimmen.isSystemGruppeWaehler()) {
            waehlerMitWahlschein = kopfzahlKumulierteStimmen;
          }
        }

        if (Stimmenart.GESAMT.equals(stimmenart)) {
          if (kopfzahlKumulierteStimmen.isSystemGruppeWahlberechtigte()) {
            wahlberechtigeGesamt = kopfzahlKumulierteStimmen;
          } else if (kopfzahlKumulierteStimmen.isSystemGruppeWaehler()) {
            waehlerGesamt = kopfzahlKumulierteStimmen;
          }
        }
      }

      if (wahlberechtigeGesamt != null && waehlerGesamt != null) {
        Long anzahlWahlberechtigeGesamt = wahlberechtigeGesamt.getAnzahl();

        if (anzahlWahlberechtigeGesamt != null && anzahlWahlberechtigeGesamt > 0L) {
          Long anzahlWaehlerGesamt = waehlerGesamt.getAnzahl();

          if (anzahlWaehlerGesamt != null && anzahlWaehlerGesamt > anzahlWahlberechtigeGesamt) { // ELECTKOM-3218
            waehlerGesamt.addMessage(FacesMessage.SEVERITY_WARN, "gruppe.kumulierte.stimmen.mehr.waehler.als.wahlberechtigte");
          }
        }
      }

      if (wahlberechtigeMitWahlschein != null && waehlerMitWahlschein != null) {
        Long anzahlWahlberechtigeMitWahlschein = wahlberechtigeMitWahlschein.getAnzahl();

        if (anzahlWahlberechtigeMitWahlschein != null && anzahlWahlberechtigeMitWahlschein > 0L) {
          Long anzahlWaehlerMitWahlschein = waehlerMitWahlschein.getAnzahl();

          if (anzahlWaehlerMitWahlschein != null && anzahlWaehlerMitWahlschein > anzahlWahlberechtigeMitWahlschein) { // ELECTKOM-241
            waehlerMitWahlschein.addMessage(FacesMessage.SEVERITY_ERROR, "gruppe.kumulierte.stimmen.mehr.wahlscheine.eingenommen.als.ausgegeben");
          }
        }
      }
    }

    return result;
  }

  private void setzeBriefwahlUrne(Gebiet gebiet) {
    if (gebiet != null) {
      gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity().setBriefwahlInUrneErfassenInGebietOderDarunter(true);
      setzeBriefwahlUrne(gebiet.getUebergeordnetesGebiet());
    }
  }

  private List<KopfzahlKumulierteStimmen> collectKopfzahlenKumulierteStimmenInUntergeordneterGebietshierarchie(Wahl wahl, Gebiet gebietSelected, Ergebnis ergebnisSelected,
      List<Wahlvorschlag> alleWahlvorschlage, StimmenPool stimmenPool, StimmenPool stimmenPoolFuerDifferenz, Predicate<Gebiet> erfassungsgebietFilter) {
    List<KopfzahlKumulierteStimmen> result = new ArrayList<>();

    List<GruppeImGebiet> gruppenImGebietAufAusgewaehltemGebiet = bestimmeGruppenImGebietAufAusgewaehltemGebietSchnell(gebietSelected, alleWahlvorschlage);

    StimmeingabeKopfzahlErfassenModelBuilder stimmeingabeKopfzahlModelBuilder = getStimmeingabeKopfzahlErfassenModelBuilder(gebietSelected);
    StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator = getStimmeingabeKopfzahlErfassenLabelGenerator(gebietSelected);

    for (GruppeImGebiet gruppeImGebiet : gruppenImGebietAufAusgewaehltemGebiet) {
      if (isSystemGruppeWahlberechtigte(gruppeImGebiet) || isSystemGruppeWaehler(gruppeImGebiet) || isSystemGruppeUebrige(gruppeImGebiet)) {
        long gruppenPositionBasis10 = gruppeImGebiet.getRelativePositionAbgeleitet() * SYSTEM_GRUPPE_POSTION_OFFSET; // Platz zw. den Positionen der verschiedenen SystemGruppeImGebiet schaffen

        result.addAll(createKopfzahlenKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, erfassungsgebietFilter, stimmeingabeKopfzahlModelBuilder,
            stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, gruppenPositionBasis10));
      }
    }

    createKopfzahlenKummierteStimmenAnteilUndDifferenz(result);

    return result;
  }

  private List<KopfzahlKumulierteStimmen> createKopfzahlenKumulierteStimmen(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool, StimmenPool stimmenPoolFuerDifferenz,
      Predicate<Gebiet> erfassungsgebietFilter, StimmeingabeKopfzahlErfassenModelBuilder stimmeingabeKopfzahlModelBuilder,
      StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10) {
    List<KopfzahlKumulierteStimmen> result = new ArrayList<>();

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = castTo(gruppeImGebiet.getGebiet(), Wahldurchfuehrungsgebiet.class);
    WahlartErgebnisart ergebnisart = Ergebniseingangsart.getKorrespondierendeErgebniseingangsart(ergebnisSelected);

    boolean gruppeImGebietEqualsWahlberechtigte = isSystemGruppeWahlberechtigte(gruppeImGebiet);
    boolean gruppeImGebietEqualsWaehler = isSystemGruppeWaehler(gruppeImGebiet);

    boolean wahlberechtigteOhneWahlscheinRendered = gruppeImGebietEqualsWahlberechtigte
        && stimmeingabeKopfzahlModelBuilder.isWahlberechtigteOhneWahlscheinCreated(wahldurchfuehrungsgebiet, ergebnisart);
    boolean waehlerOhneWahlscheinRendered = gruppeImGebietEqualsWaehler && stimmeingabeKopfzahlModelBuilder.isWaehlerOhneWahlscheinCreated(wahldurchfuehrungsgebiet, ergebnisart);

    if (wahlberechtigteOhneWahlscheinRendered || waehlerOhneWahlscheinRendered) {
      createKopfzahlKumulierteStimmenOhneWahlschein(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, gruppenPositionBasis10,
          erfassungsgebietFilter).ifPresent(result::add);
    }

    boolean waehlerMitVollmachtRendered = gruppeImGebietEqualsWaehler && stimmeingabeKopfzahlModelBuilder.isWaehlerMitVollmachtCreated(wahldurchfuehrungsgebiet, ergebnisart);

    if (waehlerMitVollmachtRendered) {
      createKopfzahlKumulierteStimmenMitVollmacht(gebietSelected, ergebnisSelected, stimmenPool, null, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, gruppenPositionBasis10,
          erfassungsgebietFilter).ifPresent(result::add);
    }

    boolean wahlberechtigteMitWahlscheinRendered = gruppeImGebietEqualsWahlberechtigte && stimmeingabeKopfzahlModelBuilder.isWahlberechtigteMitWahlscheinCreated(wahldurchfuehrungsgebiet, ergebnisart);
    boolean waehlerMitWahlscheinRendered = gruppeImGebietEqualsWaehler && stimmeingabeKopfzahlModelBuilder.isWaehlerMitWahlscheinCreated(wahldurchfuehrungsgebiet, ergebnisart);

    if (wahlberechtigteMitWahlscheinRendered || waehlerMitWahlscheinRendered) {
      createKopfzahlKumulierteStimmenMitWahlschein(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, gruppenPositionBasis10,
          erfassungsgebietFilter).ifPresent(result::add);
    }

    boolean wahlberechtigteNichtImWaehlerverzeichnisRendered = gruppeImGebietEqualsWahlberechtigte && isWahlberechtigteNichtImWaehlerverzeichnisRelevant(wahldurchfuehrungsgebiet, ergebnisart);

    if (wahlberechtigteNichtImWaehlerverzeichnisRendered) {
      createKopfzahlKumulierteStimmenNichtImWaehlerverzeichnis(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet,
          gruppenPositionBasis10, erfassungsgebietFilter).ifPresent(result::add);
    }

    boolean waehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlRendered = gruppeImGebietEqualsWaehler
        && stimmeingabeKopfzahlModelBuilder.isWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlCreated(wahldurchfuehrungsgebiet, ergebnisart);

    boolean waehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlRendered = gruppeImGebietEqualsWaehler
        && stimmeingabeKopfzahlModelBuilder.isWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlCreated(wahldurchfuehrungsgebiet, ergebnisart);

    if (waehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlRendered) {
      createKopfzahlKumulierteStimmenMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator,
          gruppeImGebiet, gruppenPositionBasis10, erfassungsgebietFilter, waehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlRendered).ifPresent(result::add);
    }

    if (waehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlRendered) {
      createKopfzahlKumulierteStimmenMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator,
          gruppeImGebiet, gruppenPositionBasis10, erfassungsgebietFilter, waehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlRendered).ifPresent(result::add);
    }

    boolean wahlberechtigteGesamtRendered = gruppeImGebietEqualsWahlberechtigte && stimmeingabeKopfzahlModelBuilder.isWahlberechtigteGesamtCreated(wahldurchfuehrungsgebiet, ergebnisart);
    boolean waehlerGesamtRendered = gruppeImGebietEqualsWaehler && stimmeingabeKopfzahlModelBuilder.isWaehlerGesamtCreated(wahldurchfuehrungsgebiet, ergebnisart);

    if (wahlberechtigteGesamtRendered || waehlerGesamtRendered) {
      createKopfzahlKumulierteStimmenGesamt(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, gruppenPositionBasis10,
          erfassungsgebietFilter).ifPresent(result::add);
    }

    return result;
  }

  private void createKopfzahlenKummierteStimmenAnteilUndDifferenz(List<KopfzahlKumulierteStimmen> alleKumulierteStimmen) {
    KopfzahlKumulierteStimmen kumulierteStimmenWahlberechtigte = null;
    KopfzahlKumulierteStimmen kumulierteStimmenWaehler = null;

    for (KopfzahlKumulierteStimmen kumulierteStimmen : alleKumulierteStimmen) {
      GruppeImGebiet gruppeImGebiet = kumulierteStimmen.getGruppeImGebiet();

      if (Stimmenart.GESAMT.equals(kumulierteStimmen.getStimmenart())) {
        if (isSystemGruppeWahlberechtigte(gruppeImGebiet)) {
          kumulierteStimmenWahlberechtigte = kumulierteStimmen;
        } else if (isSystemGruppeWaehler(gruppeImGebiet)) {
          kumulierteStimmenWaehler = kumulierteStimmen;
        }
      }
    }

    if (kumulierteStimmenWahlberechtigte != null && kumulierteStimmenWaehler != null) {
      Long stimmenWahlberechtigte = kumulierteStimmenWahlberechtigte.getAnzahl();
      Long stimmenWahlberechtigteFuerDifferenz = kumulierteStimmenWahlberechtigte.getAnzahlVorperiode();

      Long stimmenWaehler = kumulierteStimmenWaehler.getAnzahl();
      Long stimmenWaehlerFuerDifferenz = kumulierteStimmenWaehler.getAnzahlVorperiode();

      if (stimmenWaehler != null && stimmenWahlberechtigte != null && stimmenWahlberechtigte != 0) {
        kumulierteStimmenWaehler.setAnteilUngerundet(1.0d * stimmenWaehler / stimmenWahlberechtigte);
        kumulierteStimmenWaehler.setAnteilTooltip(getMessage("kopfzahl.kumulierte.stimmen.tooltip.anteil.waehler.gesamt.zu.wahlberechtigte.gesamt"));
      }

      if (stimmenWaehlerFuerDifferenz != null && stimmenWahlberechtigteFuerDifferenz != null && stimmenWahlberechtigteFuerDifferenz != 0) {
        kumulierteStimmenWaehler.setAnteilVorperiodeUngerundet(1.0d * stimmenWaehlerFuerDifferenz / stimmenWahlberechtigteFuerDifferenz);
        kumulierteStimmenWaehler.setAnteilVorperiodeTooltip(getMessage("kopfzahl.kumulierte.stimmen.tooltip.anteil.ergebnisart.fuer.differenz.waehler.gesamt.zu.wahlberechtigte.gesamt"));
      }
    }
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenOhneWahlschein(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10,
      Predicate<Gebiet> erfassungsgebietFilter) {
    Stimmenart stimmenart = Stimmenart.OHNE_WAHLSCHEIN;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_OHNE_WAHLSCHEIN;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenMitVollmacht(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10,
      Predicate<Gebiet> erfassungsgebietFilter) {
    Stimmenart stimmenart = Stimmenart.MIT_VOLLMACHT;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_MIT_VOLLMACHT;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenMitWahlschein(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10,
      Predicate<Gebiet> erfassungsgebietFilter) {
    Stimmenart stimmenart = Stimmenart.MIT_WAHLSCHEIN;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_MIT_WAHLSCHEIN;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenNichtImWaehlerverzeichnis(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10,
      Predicate<Gebiet> erfassungsgebietFilter) {
    Stimmenart stimmenart = Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_NICHT_IM_WAEHLERVERZEICHNIS;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10,
      Predicate<Gebiet> erfassungsgebietFilter, boolean waehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlRendered) {
    Stimmenart stimmenart = Stimmenart.ALS_URNENWAHL_IN_URNE;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_ALS_URNENWAHL_IN_URNE;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10,
      Predicate<Gebiet> erfassungsgebietFilter, boolean waehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlRendered) {
    Stimmenart stimmenart = Stimmenart.ALS_BRIEFWAHL_IN_URNE;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_ALS_BRIEFWAHL_IN_URNE;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  private Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmenGesamt(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool, StimmenPool stimmenPoolFuerDifferenz,
      StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, long gruppenPositionBasis10, Predicate<Gebiet> erfassungsgebietFilter) {
    Stimmenart stimmenart = Stimmenart.GESAMT;
    long position = gruppenPositionBasis10 + SYSTEM_GRUPPE_STIMMENART_OFFSET_GESAMT;

    return createKopfzahlKumulierteStimmen(gebietSelected, ergebnisSelected, stimmenPool, stimmenPoolFuerDifferenz, stimmeingabeKopfzahlLabelGenerator, gruppeImGebiet, stimmenart, position,
        erfassungsgebietFilter);
  }

  protected Optional<KopfzahlKumulierteStimmen> createKopfzahlKumulierteStimmen(Gebiet gebietSelected, Ergebnis ergebnisSelected, StimmenPool stimmenPool,
      @Nullable StimmenPool stimmenPoolFuerDifferenz, StimmeingabeKopfzahlErfassenLabelGenerator stimmeingabeKopfzahlLabelGenerator, GruppeImGebiet gruppeImGebiet, Stimmenart stimmenart,
      long position, Predicate<Gebiet> erfassungsgebietFilter) {
    Ergebniseingangsart ergebniseingangsart = Ergebniseingangsart.getKorrespondierendeErgebniseingangsart(ergebnisSelected);

    String kennung = stimmeingabeKopfzahlLabelGenerator.getKopfzahlKennung(gruppeImGebiet, ergebniseingangsart, stimmenart);
    String bezeichnung = stimmeingabeKopfzahlLabelGenerator.getKopfzahlBezeichnung(gruppeImGebiet, ergebniseingangsart, stimmenart);

    KopfzahlKumulierteStimmen kopfzahlKumulierteStimmen = new KopfzahlKumulierteStimmen(getEntityAufStimmzettelDaten(), getSystemGruppeEigenschaften(), gruppeImGebiet, stimmenart, null, null);
    kopfzahlKumulierteStimmen.setPosition(position);
    kopfzahlKumulierteStimmen.setKennung(kennung);
    kopfzahlKumulierteStimmen.setBezeichnung(bezeichnung);

    Wahldurchfuehrung wahldurchfuehrung = castTo(gebietSelected.getWahl(), Wahldurchfuehrung.class);
    Gruppe gruppe = gruppeImGebiet.getGruppe();

    if (isSystemGruppeWahlberechtigte(gruppeImGebiet)) {
      Long stimmenAktuell = null;

      if (Stimmenart.GESAMT.equals(stimmenart) && stimmeingabeKopfzahlKonfiguration.isAnyAnzahlWahlberechtigteAmGebietErfassbar(wahldurchfuehrung)) {
        // A = A1 + A2 (+ A3) ausrechnen, da die Implementierungen Wahldurchfuehrungsgebiet#getAnzahlWahlberechtigterGesamtA1A2 und Wahldurchfuehrungsgebiet#getAnzahlWahlberechtigterGesamtA1A2A3
        // immer eine Summe liefern sobald einer der Summanden hinterlegt ist! Da hier aber ggf. auf die erfassten Stimmen zurückgegriffen wird, würde die angezeigte Summe dann nicht stimmen.
        stimmenAktuell = sumIfNotNull(stimmenAktuell,
            calculateSummeAnzahlWahlberechtigte(wahldurchfuehrung, gebietSelected, gruppe, ergebnisSelected, Stimmenart.OHNE_WAHLSCHEIN, stimmenPool, erfassungsgebietFilter));
        stimmenAktuell = sumIfNotNull(stimmenAktuell,
            calculateSummeAnzahlWahlberechtigte(wahldurchfuehrung, gebietSelected, gruppe, ergebnisSelected, Stimmenart.MIT_WAHLSCHEIN, stimmenPool, erfassungsgebietFilter));
        stimmenAktuell = sumIfNotNull(stimmenAktuell,
            calculateSummeAnzahlWahlberechtigte(wahldurchfuehrung, gebietSelected, gruppe, ergebnisSelected, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS, stimmenPool, erfassungsgebietFilter));
      }

      if (stimmenAktuell == null) {
        stimmenAktuell = calculateSummeAnzahlWahlberechtigte(wahldurchfuehrung, gebietSelected, gruppe, ergebnisSelected, stimmenart, stimmenPool, erfassungsgebietFilter);

        if (stimmenAktuell == null) {
          stimmenAktuell = 0L;
        }
      }

      kopfzahlKumulierteStimmen.setAnzahl(stimmenAktuell);
    } else {
      Long stimmenAktuell = null;

      if (isBWertAnzeigen(ergebnisSelected, stimmenart)) {
        stimmenAktuell = getStimmenKumulierer().getSummeFuerSystemGruppeAufGebietUndDarunter(gruppe, gebietSelected, stimmenPool, stimmenart);
      }

      kopfzahlKumulierteStimmen.setAnzahl(stimmenAktuell);
    }

    if (stimmenPoolFuerDifferenz != null) {
      Long stimmenFuerDifferenz = getStimmenKumulierer().getSummeFuerSystemGruppeAufGebietUndDarunter(gruppe, gebietSelected, stimmenPoolFuerDifferenz, stimmenart);
      // TODO ELECT-12588
      kopfzahlKumulierteStimmen.setAnzahlVorperiode(stimmenFuerDifferenz);
    }

    return Optional.of(kopfzahlKumulierteStimmen);
  }

  @Nullable
  protected Long calculateSummeAnzahlWahlberechtigte(Wahldurchfuehrung wahldurchfuehrung, Gebiet gebietSelected, Gruppe gruppe, Ergebnis ergebnisSelected, Stimmenart stimmenart,
      StimmenPool stimmenPool, Predicate<Gebiet> erfassungsgebietFilter) {

    // NOTE ELECTKOM-121, dass erst die A-Werte am Gebiet und dann die erfassten A-Werte ausgewertet werden sollen,
    // cf. AbstractErgebnisDatenWahlberechtigte#ermittleStimmen, StimmergebnisDaten#bestimmeWahlberechtigteGesamt!!!
    if (stimmeingabeKopfzahlKonfiguration.isAnyAnzahlWahlberechtigteAmGebietErfassbar(wahldurchfuehrung)) {
      Long summeAnzahlWahlberechtigteAmGebietHinterlegt = calculateSummeAnzahlWahlberechtigteAmGebietHinterlegt(gebietSelected, ergebnisSelected, stimmenart, erfassungsgebietFilter);
      if (summeAnzahlWahlberechtigteAmGebietHinterlegt != null) {
        return summeAnzahlWahlberechtigteAmGebietHinterlegt;
      }
    }

    Long summeAnzahlWahlberechtigteImErgebnis = getStimmenKumulierer().getSummeFuerSystemGruppeAufGebietUndDarunter(gruppe, gebietSelected, stimmenPool, stimmenart);
    if (summeAnzahlWahlberechtigteImErgebnis != null && summeAnzahlWahlberechtigteImErgebnis > 0) { // ELECT-11104
      return summeAnzahlWahlberechtigteImErgebnis;
    }

    return null;
  }

  private Long calculateSummeAnzahlWahlberechtigteAmGebietHinterlegt(Gebiet gebietSelected, Ergebnis ergebnisSelected, Stimmenart stimmenart, Predicate<Gebiet> erfassungsgebietFilter) {

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietExtendedDAO.findByPrimaryKey(gebietSelected.getPk());
    Ergebniseingangsart ergebniseingangsart = Ergebniseingangsart.getKorrespondierendeErgebniseingangsart(ergebnisSelected);

    List<Wahldurchfuehrungsgebiet> erfassungsgebieteUnterhalbAusgezaehlt = findeAusgezaehlteErfassungsgebieteUnterhalbVon(ergebnisSelected, wahldurchfuehrungsgebiet);

    erfassungsgebieteUnterhalbAusgezaehlt.removeIf(erfassungsgebietFilter.negate()); // ELECT-6430

    Long result = null;
    for (Wahldurchfuehrungsgebiet erfassungsgebietAusgezaehlt : erfassungsgebieteUnterhalbAusgezaehlt) {
      Long resultGebiet = getAnzahlAmGebietDirekt(erfassungsgebietAusgezaehlt, stimmenart, ergebniseingangsart);
      if (resultGebiet != null) {
        // ELECT-12040
        for (Wahldurchfuehrungsgebiet other : wahldurchfuehrungsgebietExtendedDAO.findByErfasstFuerGebiete(erfassungsgebietAusgezaehlt)) {
          Long resultOther = getAnzahlAmGebietDirekt(other, stimmenart, ergebniseingangsart);
          if (resultOther != null) {
            resultGebiet += resultOther;
          }
        }
      }
      result = sumIfNotNull(result, resultGebiet);
    }

    return result;
  }

  @Nullable
  private Long getAnzahlAmGebietDirekt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmenart stimmenart, Ergebniseingangsart ergebniseingangsart) {

    switch (stimmenart) {
      case OHNE_WAHLSCHEIN :
        return wahldurchfuehrungsgebiet.getAnzahlWahlberechtigteOhneWahlschein();

      case MIT_WAHLSCHEIN :
        return wahldurchfuehrungsgebiet.getAnzahlWahlberechtigteMitWahlschein();

      case NICHT_IM_WAEHLERVERZEICHNIS :
        if (isWahlberechtigteNichtImWaehlerverzeichnisRelevant(wahldurchfuehrungsgebiet, ergebniseingangsart)) { // ELECT-7293
          return wahldurchfuehrungsgebiet.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis();
        }

        break;
      case GESAMT :
        break; // EXPLIZIT BERECHNET, cf. KopfzahlKumulierteStimmenSearch#createKopfzahlKumulierteStimmen!!!
      default :
        break;
    }
    return null;
  }

  private Map<Ergebnis, Map<Wahldurchfuehrungsgebiet, List<Wahldurchfuehrungsgebiet>>> ausgezaehlteErfassungsgebieteUnterhalbVon = new HashMap<>();

  private List<Wahldurchfuehrungsgebiet> findeAusgezaehlteErfassungsgebieteUnterhalbVon(Ergebnis ergebnisSelected, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new ArrayList<>(ausgezaehlteErfassungsgebieteUnterhalbVon.computeIfAbsent(ergebnisSelected, x -> new HashMap<>()).computeIfAbsent(wahldurchfuehrungsgebiet, x -> {
      List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();
      List<ErgebnisGueltigerEingang> gueltigeEingaenge = ergebnisGueltigerEingangExtendedDAO.findByErgebnis(ergebnisSelected);
      result = gueltigeEingaenge.stream().map(AbstractErgebnisGueltigerEingang::getWahldurchfuehrungsgebiet).filter(Objects::nonNull) // ELECTKOM-1715
          .filter(g -> g.isEqualOderUntergebietVon(wahldurchfuehrungsgebiet)).collect(Collectors.toList());
      return result;
    }));
  }

  private StimmenPool createStimmenPoolReadonlyFuerDifferenz(Wahl wahl, AbstractKumulierteStimmenSearchParameters searchParameters, StimmenFilter stimmenFilter) {
    List<Ergebnis> ergebnisseFuerDifferenz = ergebnisExtendedDAO.findByWahlAndErgebnisart(wahl, searchParameters.getErgebnisartFuerDifferenz());

    if (!ergebnisseFuerDifferenz.isEmpty()) {
      return createStimmenPoolReadonly(ergebnisseFuerDifferenz.get(0), stimmenFilter);
    }

    return StimmenPool.createStimmenPoolLeer();
  }

  private StimmenPool createStimmenPoolReadonly(Ergebnis ergebnis, StimmenFilter stimmenFilter) {
    return StimmenPool.createStimmenPoolReadonly(readonlyExtendedStimmenLoader, stimmenExtendedDAO, ergebnis, stimmenFilter);
  }

  private StimmenFilter createStimmenFilter(Predicate<Gebiet> erfassungsgebietFilter) {
    return StimmenKumulierer.createStimmenFilter(erfassungsgebietFilter, ergebnisStimmenFilter);
  }

  protected Predicate<Gebiet> createErfassungsgebietFilter(Ergebnis ergebnis, AbstractKumulierteStimmenSearchParameters searchParameters) {
    return searchParameters.getStimmabgabeart().filterGebiete(() -> collectErfassteGebiete(ergebnis))
        .and(AbstractGebietsgruppierung.getGebietsgruppierungFilter(gebietsgruppierungExtendedDAO.attach(searchParameters.getGebietsgruppierung())));
  }

  @Override
  protected Comparator<KopfzahlKumulierteStimmen> getDefaultComparator() {
    return new KopfzahlenKumulierteStimmenByPositionComparator();
  }

  private void reset() {
    ausgezaehlteErfassungsgebieteUnterhalbVon = new HashMap<>();
  }

  protected boolean isBWertAnzeigen(Ergebnis ergebnis, Stimmenart stimmenart) {
    return true;
  }

  private boolean isWahlberechtigteNichtImWaehlerverzeichnisRelevant(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, WahlartErgebnisart ergebnisart) {
    return getStimmeingabeKopfzahlErfassenModelBuilder(wahldurchfuehrungsgebiet).isWahlberechtigteNichtImWaehlerverzeichnisCreated(wahldurchfuehrungsgebiet, ergebnisart);
  }

  private StimmeingabeKopfzahlErfassenModelBuilder getStimmeingabeKopfzahlErfassenModelBuilder(Gebiet gebietSelected) {
    return getStimmeingabeErfassenDescription(gebietSelected).getStimmeingabeKopfzahlErfassenModelBuilder();
  }

  private StimmeingabeKopfzahlErfassenLabelGenerator getStimmeingabeKopfzahlErfassenLabelGenerator(Gebiet gebietSelected) {
    return getStimmeingabeErfassenDescription(gebietSelected).getStimmeingabeKopfzahlErfassenLabelGenerator();
  }

  private Long sumIfNotNull(Long one, Long another) {
    if (another == null) {
      return one;
    }

    if (one == null) {
      return another;
    }

    return one + another;
  }
}
