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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import javax.annotation.Nonnull;

import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.control.GruppeKumulierteStimmenByGruppeImGebietPositionComparator;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeKumulierteStimmen;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeGruppeStimmenErfassenLabelGenerator;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

public class GruppeKumulierteStimmenSearch extends AbstractGruppeStimmenSearch<GruppeKumulierteStimmenSearchParameters> {

  public GruppeKumulierteStimmenSearch() {
    super();
  }

  @Override
  protected List<GruppeKumulierteStimmen> getGruppeKumulierteStimmenInUntergeordneterGebietshierarchie(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebietSelected, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, List<Wahlvorschlag> wahlvorschlaegeInWahl, Gruppe gruppeGueltige, GruppeKumulierteStimmenSearchParameters searchParameters,
      Supplier<Set<Gebiet>> erfassteGebiete, Map<Gruppe, Set<GruppeImGebiet>> gigNachGruppen) {
    Map<Gruppe, Set<Wahlvorschlag>> wahlvorschlaegeInWahlPerGruppe = mappeWahlvorschlaegeNachGruppe(wahlvorschlaegeInWahl);
    List<GruppeImGebiet> gruppenImGebietAufAusgewaehltemGebiet = bestimmeGruppenImGebietAufAusgewaehltemGebietSchnell(gebietSelected, wahlvorschlaegeInWahl);

    StimmeingabeGruppeStimmenErfassenLabelGenerator stimmeingabeGruppeStimmenErfassenLabelGenerator = stimmeingabeGruppeStimmenErfassenLabelGeneratorFinder
        .getStimmeingabeGruppeStimmenErfassenLabelGeneratorFuerWahl(wahl);

    Map<Gruppe, Set<GruppeImGebiet>> gigInGebietshierachieUebergeordnetNachGruppen = ladeGigInGebietshierachieUebergeordnetNachGruppen(gebietSelected);

    return collectGruppeKumulierteStimmen(wahl, ergebnisart, castTo(gebietSelected, Wahldurchfuehrungsgebiet.class), stimmenPool, stimmenPoolFuerDifferenz, searchParameters,
        wahlvorschlaegeInWahlPerGruppe, gruppenImGebietAufAusgewaehltemGebiet, stimmeingabeGruppeStimmenErfassenLabelGenerator, gigInGebietshierachieUebergeordnetNachGruppen);
  }

  protected List<GruppeKumulierteStimmen> collectGruppeKumulierteStimmen(Wahl wahl, WahlartErgebnisart ergebnisart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, StimmenPool stimmenPool,
      StimmenPool stimmenPoolFuerDifferenz, GruppeKumulierteStimmenSearchParameters searchParameters, Map<Gruppe, Set<Wahlvorschlag>> wahlvorschlaegeInWahlPerGruppe,
      List<GruppeImGebiet> gruppenImGebietAufAusgewaehltemGebiet, StimmeingabeGruppeStimmenErfassenLabelGenerator stimmeingabeGruppeStimmenErfassenLabelGenerator,
      Map<Gruppe, Set<GruppeImGebiet>> gigInGebietshierachieUebergeordnetNachGruppen) {
    List<GruppeKumulierteStimmen> result = new ArrayList<>();

    Long anzahlDirektstimmenInsgesamt = 0L;
    Long anzahlListenstimmenInsgesamt = 0L;

    Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertInsgesamt = 0L;
    Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertInsgesamt = 0L;

    Long anzahlDirektstimmenFuerDifferenzInsgesamt = 0L;
    boolean anzahlDirektstimmenFuerDifferenzInsgesamtVorhanden = false;
    Long anzahlListenstimmenFuerDifferenzInsgesamt = 0L;
    boolean anzahlListenstimmenFuerDifferenzInsgesamtVorhanden = false;

    Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertFuerDifferenzInsgesamt = 0L;
    Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertFuerDifferenzInsgesamt = 0L;

    Long anzahlDirektstimmenInsgesamtSystemGruppen = 0L;
    Long anzahlListenstimmenInsgesamtSystemGruppen = 0L;

    Long anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppen = 0L;
    boolean anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden = false;
    Long anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppen = 0L;
    boolean anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden = false;

    GruppeImGebiet systemGruppeWaehlerImGebiet = null;
    GruppeImGebiet systemGruppeUebrigeImGebiet = null;

    Gebiet direktwahlgebiet = wahldurchfuehrungsgebiet.getUebergeordnetesDirektwahlgebiet();

    for (GruppeImGebiet gruppeImGebiet : gruppenImGebietAufAusgewaehltemGebiet) {
      if (isGruppeImGebietApplicable(gruppeImGebiet)) {
        Gruppe gruppe = gruppeImGebiet.getGruppe();

        Long anzahlDirektstimmen = getStimmenKumulierer().getSummeDirektstimmenFuerPolitischeGruppeAufGebietUndDarunter(gruppe, wahldurchfuehrungsgebiet, stimmenPool);
        Long anzahlDirektstimmenFuerDifferenz = getStimmenKumulierer().getSummeDirektstimmenFuerPolitischeGruppeAufGebietUndDarunter(gruppe, wahldurchfuehrungsgebiet, stimmenPoolFuerDifferenz);
        boolean anzahlDirektstimmenFuerDifferenzVorhanden = !stimmenPoolFuerDifferenz.getDirektstimmen(gruppe).isEmpty();

        Long anzahlListenstimmen = getStimmenKumulierer().getSummeListenstimmenFuerPolitischeGruppeAufGebietUndDarunter(gruppe, wahldurchfuehrungsgebiet, stimmenPool);
        Long anzahlListenstimmenFuerDifferenz = getStimmenKumulierer().getSummeListenstimmenFuerPolitischeGruppeAufGebietUndDarunter(gruppe, wahldurchfuehrungsgebiet, stimmenPoolFuerDifferenz);
        boolean anzahlListenstimmenFuerDifferenzVorhanden = !stimmenPoolFuerDifferenz.getListenstimmenDerGruppe(gruppe).isEmpty();

        Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = getStimmenKumulierer().getSummeListenstimmenEinzelnerWahlvorschlagUnveraendertFuerPolitischeGruppeAufGebietUndDarunter(gruppe,
            wahldurchfuehrungsgebiet, stimmenPool);
        Long anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertFuerDifferenz = getStimmenKumulierer()
            .getSummeListenstimmenEinzelnerWahlvorschlagUnveraendertFuerPolitischeGruppeAufGebietUndDarunter(gruppe, wahldurchfuehrungsgebiet, stimmenPoolFuerDifferenz);

        Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert = getStimmenKumulierer().getSummeListenstimmenEinzelnerWahlvorschlagVeraendertFuerPolitischeGruppeAufGebietUndDarunter(gruppe,
            wahldurchfuehrungsgebiet, stimmenPool);
        Long anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertFuerDifferenz = getStimmenKumulierer()
            .getSummeListenstimmenEinzelnerWahlvorschlagVeraendertFuerPolitischeGruppeAufGebietUndDarunter(gruppe, wahldurchfuehrungsgebiet, stimmenPoolFuerDifferenz);

        GruppeKumulierteStimmen gruppeKumulierteStimmen = createGruppeKumulierteStimmen(gruppeImGebiet, anzahlDirektstimmen, anzahlListenstimmen,
            anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert, anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert, gigInGebietshierachieUebergeordnetNachGruppen);
        gruppeKumulierteStimmen.setAnzahlDirektstimmenVorperiode(anzahlDirektstimmenFuerDifferenz);
        gruppeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeVorhanden(anzahlDirektstimmenFuerDifferenzVorhanden);
        gruppeKumulierteStimmen.setAnzahlListenstimmenVorperiode(anzahlListenstimmenFuerDifferenz);
        gruppeKumulierteStimmen.setAnzahlListenstimmenVorperiodeVorhanden(anzahlListenstimmenFuerDifferenzVorhanden);

        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertVorperiode(anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertFuerDifferenz);
        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertVorperiode(anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertFuerDifferenz);

        if (gruppeImGebiet.isSystemGruppe() || gruppeImGebiet.hatWahlvorschlagAufStimmzettelInUebergeordneterGebietshierarchie(wahlvorschlaegeInWahlPerGruppe)) {
          String kennung = stimmeingabeGruppeStimmenErfassenLabelGenerator.getGruppeStimmenKennung(getEntityAufStimmzettelDaten(), gruppeImGebiet, ergebnisart, gruppenImGebietAufAusgewaehltemGebiet);
          String name = stimmeingabeGruppeStimmenErfassenLabelGenerator.getGruppeStimmenBezeichnung(getEntityAufStimmzettelDaten(), gruppeImGebiet, ergebnisart, gruppenImGebietAufAusgewaehltemGebiet);

          gruppeKumulierteStimmen.setKennung(kennung);
          gruppeKumulierteStimmen.setName(name);
        }

        if (direktwahlgebiet != null) {
          Direktkandidat direktkandidat = findeDirektkandidat(gruppeImGebiet, direktwahlgebiet, wahlvorschlaegeInWahlPerGruppe);

          if (direktkandidat != null) {
            Direktkandidatur direktkandidatur = direktkandidat.getDirektkandidatur();

            gruppeKumulierteStimmen.setNameDirektkandidat(direktkandidatur.getStimmzettelPersonendaten(getEntityAufStimmzettelDaten()).getName());
            gruppeKumulierteStimmen.setKurzname(direktkandidat.getGruppeImGebiet().getKurznameAufGebiet());
          }
        }

        result.add(gruppeKumulierteStimmen);

        if (gruppeImGebiet.isSystemGruppe()) {
          anzahlDirektstimmenInsgesamtSystemGruppen += anzahlDirektstimmen;
          anzahlListenstimmenInsgesamtSystemGruppen += anzahlListenstimmen;

          anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppen += anzahlDirektstimmenFuerDifferenz;
          anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden |= anzahlDirektstimmenFuerDifferenzVorhanden;
          anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppen += anzahlListenstimmenFuerDifferenz;
          anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden |= anzahlListenstimmenFuerDifferenzVorhanden;
        } else {
          anzahlDirektstimmenInsgesamt += anzahlDirektstimmen;
          anzahlListenstimmenInsgesamt += anzahlListenstimmen;

          anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertInsgesamt += anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert;
          anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertInsgesamt += anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert;

          gruppeKumulierteStimmen.setHatDirektkandidatenInGebietshierarchie(hatZugelasseneDirektkandidatenInGebietshierarchie(wahlvorschlaegeInWahlPerGruppe, gruppeImGebiet));
          gruppeKumulierteStimmen.setHatListeInGebietshierarchie(gruppeImGebiet.hatListeInGebietshierarchie(wahlvorschlaegeInWahlPerGruppe));
        }

        // ELECT-3283
        if (isSystemGruppeGueltige(gruppeImGebiet)) {
          anzahlDirektstimmenFuerDifferenzInsgesamt = anzahlDirektstimmenFuerDifferenz;
          anzahlDirektstimmenFuerDifferenzInsgesamtVorhanden |= anzahlDirektstimmenFuerDifferenzVorhanden;
          anzahlListenstimmenFuerDifferenzInsgesamt = anzahlListenstimmenFuerDifferenz;
          anzahlListenstimmenFuerDifferenzInsgesamtVorhanden |= anzahlListenstimmenFuerDifferenzVorhanden;
        }
      } else if (isSystemGruppeWaehler(gruppeImGebiet)) {
        systemGruppeWaehlerImGebiet = gruppeImGebiet;
      } else if (isSystemGruppeUebrige(gruppeImGebiet)) {
        systemGruppeUebrigeImGebiet = gruppeImGebiet;
      }
    }

    for (GruppeKumulierteStimmen gruppeKumulierteStimmen : result) {
      if (gruppeKumulierteStimmen.isSystemGruppe()) {
        gruppeKumulierteStimmen.setPosition(getSystemGruppePosition(gruppeKumulierteStimmen));

        gruppeKumulierteStimmen.setAnzahlDirektstimmenAllerGruppen(anzahlDirektstimmenInsgesamtSystemGruppen);
        gruppeKumulierteStimmen.setAnzahlListenstimmenAllerGruppen(anzahlListenstimmenInsgesamtSystemGruppen);

        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertAllerGruppen(null);
        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertAllerGruppen(null);

        gruppeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeAllerGruppen(anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppen);
        gruppeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeAllerGruppenVorhanden(anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden);
        gruppeKumulierteStimmen.setAnzahlListenstimmenVorperiodeAllerGruppen(anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppen);
        gruppeKumulierteStimmen.setAnzahlListenstimmenVorperiodeAllerGruppenVorhanden(anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden);

        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertVorperiodeAllerGruppen(null);
        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertVorperiodeAllerGruppen(null);
      } else {
        gruppeKumulierteStimmen.setAnzahlDirektstimmenAllerGruppen(anzahlDirektstimmenInsgesamt);
        gruppeKumulierteStimmen.setAnzahlListenstimmenAllerGruppen(anzahlListenstimmenInsgesamt);

        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertAllerGruppen(anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertInsgesamt);
        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertAllerGruppen(anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertInsgesamt);

        gruppeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeAllerGruppen(anzahlDirektstimmenFuerDifferenzInsgesamt);
        gruppeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeAllerGruppenVorhanden(anzahlDirektstimmenFuerDifferenzInsgesamtVorhanden);
        gruppeKumulierteStimmen.setAnzahlListenstimmenVorperiodeAllerGruppen(anzahlListenstimmenFuerDifferenzInsgesamt);
        gruppeKumulierteStimmen.setAnzahlListenstimmenVorperiodeAllerGruppenVorhanden(anzahlListenstimmenFuerDifferenzInsgesamtVorhanden);

        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertVorperiodeAllerGruppen(anzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertFuerDifferenzInsgesamt);
        gruppeKumulierteStimmen.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertVorperiodeAllerGruppen(anzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertFuerDifferenzInsgesamt);
      }
    }

    if (systemGruppeWaehlerImGebiet != null) {
      String kennung = stimmeingabeGruppeStimmenErfassenLabelGenerator.getGruppeStimmenKennung(getEntityAufStimmzettelDaten(), systemGruppeWaehlerImGebiet, ergebnisart,
          gruppenImGebietAufAusgewaehltemGebiet);
      String bezeichnung = stimmeingabeGruppeStimmenErfassenLabelGenerator.getGruppeStimmenBezeichnung(getEntityAufStimmzettelDaten(), systemGruppeWaehlerImGebiet, ergebnisart,
          gruppenImGebietAufAusgewaehltemGebiet);

      if (isNoneBlank(kennung, bezeichnung)) { // ELECT-9824, nicht hinzufügen, falls keine Kennung oder Bezeichnung vorhanden...
        GruppeKumulierteStimmen abgegebeneStimmenInsgesamt = createGruppeKumulierteStimmen(systemGruppeWaehlerImGebiet, anzahlDirektstimmenInsgesamtSystemGruppen,
            anzahlListenstimmenInsgesamtSystemGruppen, null, null, gigInGebietshierachieUebergeordnetNachGruppen);

        long position = getSystemGruppePosition(abgegebeneStimmenInsgesamt);
        abgegebeneStimmenInsgesamt.setPosition(position);
        abgegebeneStimmenInsgesamt.setKennung(kennung);
        abgegebeneStimmenInsgesamt.setName(bezeichnung);

        abgegebeneStimmenInsgesamt.setAnzahlDirektstimmenAllerGruppen(anzahlDirektstimmenInsgesamtSystemGruppen);
        abgegebeneStimmenInsgesamt.setAnzahlListenstimmenAllerGruppen(anzahlListenstimmenInsgesamtSystemGruppen);

        abgegebeneStimmenInsgesamt.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertAllerGruppen(null);
        abgegebeneStimmenInsgesamt.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertAllerGruppen(null);

        abgegebeneStimmenInsgesamt.setAnzahlDirektstimmenVorperiode(anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppen);
        abgegebeneStimmenInsgesamt.setAnzahlDirektstimmenVorperiodeVorhanden(anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden);
        abgegebeneStimmenInsgesamt.setAnzahlListenstimmenVorperiode(anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppen);
        abgegebeneStimmenInsgesamt.setAnzahlListenstimmenVorperiodeVorhanden(anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden);

        abgegebeneStimmenInsgesamt.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertVorperiode(null);
        abgegebeneStimmenInsgesamt.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertVorperiode(null);

        abgegebeneStimmenInsgesamt.setAnzahlDirektstimmenVorperiodeAllerGruppen(anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppen);
        abgegebeneStimmenInsgesamt.setAnzahlDirektstimmenVorperiodeAllerGruppenVorhanden(anzahlDirektstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden);
        abgegebeneStimmenInsgesamt.setAnzahlListenstimmenVorperiodeAllerGruppen(anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppen);
        abgegebeneStimmenInsgesamt.setAnzahlListenstimmenVorperiodeAllerGruppenVorhanden(anzahlListenstimmenFuerDifferenzInsgesamtSystemGruppenVorhanden);

        abgegebeneStimmenInsgesamt.setAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendertVorperiodeAllerGruppen(null);
        abgegebeneStimmenInsgesamt.setAnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendertVorperiodeAllerGruppen(null);

        result.add(abgegebeneStimmenInsgesamt);
      }
    }

    // WASDE-563 Einzelbewerber oberhalb von Direktkandidatengebieten zusammenfassen
    // WASDE-1720 Voperiodenwerte nicht mehr antretender Gruppen berechnen
    result = einzelbewerberUndOffeneVorperiodenstimmenZuUebrige(result, wahl, wahldurchfuehrungsgebiet, searchParameters.isSystemGruppeUebrigeIncludesEinzelbewerber(), systemGruppeUebrigeImGebiet,
        anzahlDirektstimmenInsgesamt, anzahlListenstimmenInsgesamt, anzahlDirektstimmenFuerDifferenzInsgesamt, anzahlListenstimmenFuerDifferenzInsgesamt, gigInGebietshierachieUebergeordnetNachGruppen,
        stimmenPool);

    return result;
  }

  private Direktkandidat findeDirektkandidat(GruppeImGebiet gruppeImGebiet, Gebiet direktwahlgebiet, Map<Gruppe, Set<Wahlvorschlag>> wahlvorschlaegeInWahlPerGruppe) {
    return wahlvorschlaegeInWahlPerGruppe.getOrDefault(gruppeImGebiet.getGruppe(), emptySet()).stream()
        .filter(wahlvorschlag -> wahlvorschlag.isDirektkandidat() && wahlvorschlag.getGebiet().equals(direktwahlgebiet)).findFirst()
        .map(wahlvorschlag -> wahlvorschlag.castTo(Direktkandidat.class).getEntity()).orElse(null);

  }

  private List<GruppeKumulierteStimmen> einzelbewerberUndOffeneVorperiodenstimmenZuUebrige(List<GruppeKumulierteStimmen> result, Wahl wahl, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      boolean systemGruppeUebrigeIncludesEinzelbewerber, GruppeImGebiet systemGruppeUebrigeImGebiet, Long anzahlDirektstimmenInsgesamt, Long anzahlListenstimmenInsgesamt,
      Long anzahlDirektstimmenFuerDifferenzInsgesamt, Long anzahlListenstimmenFuerDifferenzInsgesamt, Map<Gruppe, Set<GruppeImGebiet>> gigNachGruppen, StimmenPool stimmenPool) {

    Long anzahlEinzelbewerberDirektstimmen = null;
    Long anzahlEinzelbewerberListenstimmen = null;

    Long anzahlDirektstimmenAufgelistetFuerDifferenz = 0L;
    Long anzahlListenstimmenAufgelistetFuerDifferenz = 0L;

    long hoechstePositionPolitischeGruppe = Long.MIN_VALUE;

    List<GruppeKumulierteStimmen> resultFiltered = new ArrayList<>(result.size());

    for (GruppeKumulierteStimmen gruppeKumulierteStimmen : result) {
      Gruppe gruppe = gruppeKumulierteStimmen.getGruppe();

      if (gruppe.isPolitischeGruppe()) {
        if (gruppeKumulierteStimmen.getAnzahlDirektstimmenVorperiode() != null) {
          anzahlDirektstimmenAufgelistetFuerDifferenz += gruppeKumulierteStimmen.getAnzahlDirektstimmenVorperiode();
        }

        if (gruppeKumulierteStimmen.getAnzahlListenstimmenVorperiode() != null) {
          anzahlListenstimmenAufgelistetFuerDifferenz += gruppeKumulierteStimmen.getAnzahlListenstimmenVorperiode();
        }
      }

      if (systemGruppeUebrigeIncludesEinzelbewerber && gruppe.isEinzelbewerber()) {
        Long anzahlDirektstimmen = gruppeKumulierteStimmen.getAnzahlDirektstimmen();

        if (anzahlDirektstimmen != null) {
          if (anzahlEinzelbewerberDirektstimmen == null) {
            anzahlEinzelbewerberDirektstimmen = anzahlDirektstimmen;
          } else {
            anzahlEinzelbewerberDirektstimmen += anzahlDirektstimmen;
          }
        }
      } else {
        resultFiltered.add(gruppeKumulierteStimmen);
      }

      if (gruppe.isPolitischeGruppe() && gruppeKumulierteStimmen.getPosition() != null && hoechstePositionPolitischeGruppe < gruppeKumulierteStimmen.getPosition()) {
        hoechstePositionPolitischeGruppe = gruppeKumulierteStimmen.getPosition();
      }
    }

    if (systemGruppeUebrigeIncludesEinzelbewerber) {
      anzahlEinzelbewerberDirektstimmen = fixUebrigeDirektstimmenInSondergebieten(anzahlEinzelbewerberDirektstimmen, systemGruppeUebrigeImGebiet, stimmenPool);
    }

    GruppeKumulierteStimmen uebrigeKumulierteStimmen = createGruppeKumulierteStimmen(systemGruppeUebrigeImGebiet, anzahlEinzelbewerberDirektstimmen, anzahlEinzelbewerberListenstimmen, null, null,
        gigNachGruppen);

    // WASDE-627
    Long anzahlSonstigeDirektstimmenFuerDifferenzAusgerechnet = null;
    Long anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet = null;

    if (anzahlDirektstimmenFuerDifferenzInsgesamt != null) {
      anzahlSonstigeDirektstimmenFuerDifferenzAusgerechnet = anzahlDirektstimmenFuerDifferenzInsgesamt - anzahlDirektstimmenAufgelistetFuerDifferenz;
    }

    if (anzahlListenstimmenFuerDifferenzInsgesamt != null) {
      anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet = anzahlListenstimmenFuerDifferenzInsgesamt - anzahlListenstimmenAufgelistetFuerDifferenz;
    }

    uebrigeKumulierteStimmen.setPosition(hoechstePositionPolitischeGruppe + 1L);

    uebrigeKumulierteStimmen.setHatListeInGebietshierarchie(anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet != null && anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet > 0L);

    uebrigeKumulierteStimmen.setAnzahlDirektstimmenVorperiode(anzahlSonstigeDirektstimmenFuerDifferenzAusgerechnet);
    uebrigeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeVorhanden(anzahlSonstigeDirektstimmenFuerDifferenzAusgerechnet != null && anzahlSonstigeDirektstimmenFuerDifferenzAusgerechnet > 0L);
    uebrigeKumulierteStimmen.setAnzahlListenstimmenVorperiode(anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet);
    uebrigeKumulierteStimmen.setAnzahlListenstimmenVorperiodeVorhanden(anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet != null && anzahlSonstigeListenstimmenFuerDifferenzAusgerechnet > 0L);

    uebrigeKumulierteStimmen.setAnzahlDirektstimmenAllerGruppen(anzahlDirektstimmenInsgesamt);
    uebrigeKumulierteStimmen.setAnzahlListenstimmenAllerGruppen(anzahlListenstimmenInsgesamt);

    uebrigeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeAllerGruppen(anzahlDirektstimmenFuerDifferenzInsgesamt);
    uebrigeKumulierteStimmen.setAnzahlDirektstimmenVorperiodeAllerGruppenVorhanden(anzahlDirektstimmenFuerDifferenzInsgesamt != null && anzahlDirektstimmenFuerDifferenzInsgesamt > 0L);
    uebrigeKumulierteStimmen.setAnzahlListenstimmenVorperiodeAllerGruppen(anzahlListenstimmenFuerDifferenzInsgesamt);
    uebrigeKumulierteStimmen.setAnzahlListenstimmenVorperiodeAllerGruppenVorhanden(anzahlListenstimmenFuerDifferenzInsgesamt != null && anzahlListenstimmenFuerDifferenzInsgesamt > 0L);

    // Nur anzeigen, wenn relevant...
    boolean oberstesDirektwahlgebietSelected = wahl.isWahlMitDirektkandidatengebieten() && wahldurchfuehrungsgebiet.getUebergeordnetesDirektwahlgebiet() == null;

    boolean existsDirektstimmenVorperiode = uebrigeKumulierteStimmen.isAnzahlDirektstimmenVorperiodeVorhanden();
    boolean existsListenstimmenVorperiode = uebrigeKumulierteStimmen.isAnzahlListenstimmenVorperiodeVorhanden();

    if (oberstesDirektwahlgebietSelected || existsDirektstimmenVorperiode || existsListenstimmenVorperiode) {
      resultFiltered.add(uebrigeKumulierteStimmen);
    }

    return resultFiltered;
  }

  protected Long fixUebrigeDirektstimmenInSondergebieten(Long anzahlEinzelbewerberDirektstimmen, GruppeImGebiet systemGruppeUebrigeImGebiet, StimmenPool stimmenPool) {
    return anzahlEinzelbewerberDirektstimmen; // bei Destatis überschrieben
  }

  protected long getSystemGruppePosition(GruppeKumulierteStimmen gruppeKumulierteStimmen) {
    return gruppeKumulierteStimmen.getGruppeImGebiet().getRelativePosition() - getSystemGruppePositionOffset();
  }

  protected long getSystemGruppePositionOffset() {
    return 1000000L;
  }

  protected boolean hatZugelasseneDirektkandidatenInGebietshierarchie(Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeNachGruppe, GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebiet.hatZugelassenenDirektkandidatInGebietshierarchie(alleWahlvorschlaegeNachGruppe);
  }

  @Nonnull
  @Override
  protected StimmenPool getStimmenPool(Gebiet gebietSelected, Ergebnis ergebnisFuerDifferenz, Gruppe gruppeGueltige, GruppeKumulierteStimmenSearchParameters searchParameters, List<Gruppe> alleGruppen,
      Supplier<Set<Gebiet>> erfassteGebiete) {
    //  ELECTMUC-926 eager loading
    if (gebietSelected.isErfassungsgebiet()) {
      return StimmenPool.createStimmenPoolFuerAlleStimmen(
          filterStimmen(stimmenExtendedDAO.findByErgebnisOhneListenkandidaturenUndGebietenNoCache(ergebnisFuerDifferenz, asList(gebietSelected)), searchParameters, erfassteGebiete));
    }

    return StimmenPool.createStimmenPoolFuerAlleStimmen(filterStimmen(readonlyExtendedStimmenLoader.readGruppenStimmen(ergebnisFuerDifferenz), searchParameters, erfassteGebiete));
  }

  private boolean isGruppeImGebietApplicable(GruppeImGebiet gruppeImGebiet) {
    if (gruppeImGebiet.isPolitischeGruppe()) {
      // NOTE, dass Wahlvorschläge in der UNTERGEORDNETEN Gebietshierarchie derzeit NICHT berücksichtigt werden sollen:
      // für diese lässt sich auf dem aktuellen Gebiet keine Stimmzettelposition ableiten, was die Anzeige schwierig macht.
      // return gruppeImGebiet.hatWahlvorschlagAufStimmzettelInUebergeordneterGebietshierarchie();

      return true; // Jetzt doch wieder: ELECT-3274
    }

    return isSystemGruppeGueltige(gruppeImGebiet) || isSystemGruppeUngueltige(gruppeImGebiet);
  }

  @Override
  protected Comparator<GruppeKumulierteStimmen> getDefaultComparator() {
    return new GruppeKumulierteStimmenByGruppeImGebietPositionComparator();
  }
}
