
package de.ivu.elect.business.wahl.control;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Collections.emptyList;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.control.SitzePool;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.control.StimmenPoolFactory;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.fremdkandidatur.entity.Fremdkandidatur;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsartTemplate;
import de.ivu.elect.business.gebietsbaum.control.GebietByGebietsartAndPositionComparator;
import de.ivu.elect.business.gebietsbaum.control.GebietByNummerComparator;
import de.ivu.elect.business.gebietsbaum.control.GebietByPositionComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gruppe.control.GruppeByPositionComparator;
import de.ivu.elect.business.gruppe.control.GruppeByPositionOrStimmzettelPositionAufGebietComparator;
import de.ivu.elect.business.gruppe.entity.AbstractGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietReihenfolgeVorsortierer;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.entity.InternetSeite;
import de.ivu.elect.business.kandidatenportal.entity.WvpWarnung;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturByRelativePositionComparator;
import de.ivu.elect.business.kandidatur.control.StimmzettelFormatter;
import de.ivu.elect.business.kandidatur.entity.AbstractKandidatur;
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.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.pruefung.boundary.ZulassungPruefer;
import de.ivu.elect.business.staat.entity.StaatenGruppe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenStimmzettelPositionBerechnung;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenVorgeladenFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDatenFactory;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumArt;
import de.ivu.elect.business.veranstaltung.entity.Veranstaltung;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlausschuss.entity.AbstractWahlausschussmitglied;
import de.ivu.elect.business.wahlausschuss.entity.WahlausschussEntschaedigung;
import de.ivu.elect.business.wahlausschuss.entity.Wahlausschussmitglied;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungVerlauf;
import de.ivu.elect.business.wahlrechtspruefung.entity.Pruefkriterium;
import de.ivu.elect.business.wahlrechtspruefung.entity.Wahlrechtspruefung;
import de.ivu.elect.business.wahlvorschlag.control.ReferendumOptionByPositionComparator;
import de.ivu.elect.business.wahlvorschlag.entity.AbstractWahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.util.MultiMapUtil;

// TODO ELECT-3147 "Zugelassen" vs. Auf Stimmzettel
public class Wahldaten<Wahlobject extends Wahl, Gebietobject extends Gebiet> extends WahldatenLoader<Gebietobject> implements Serializable {

  private static final long serialVersionUID = 1L;

  private final Wahlobject wahl;

  private final SystemGruppeEigenschaften systemGruppeEigenschaften;

  private final List<Gruppe> systemgruppen = new ArrayList<>();

  private final AbstractWahlParameterLoader wahlParameterLoader;

  private final ZulassungPruefer zulassungPruefer;

  private final StimmzettelFormatter stimmzettelFormatter;

  private final EntityAufStimmzettelDatenStimmzettelPositionBerechnung entityAufStimmzettelDatenStimmzettelPositionBerechnung;

  private final GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer;

  private final SitzePool sitzeVorperiodePool = new SitzePool(this::getSitzeVorperiode, this::getListen);

  private final Loader<Pruefkriterium> pruefkriterienLoader;
  private List<Pruefkriterium> pruefkriterien;

  private final Loader<Wahlrechtspruefung> wahlrechtspruefungenLoader;
  private List<Wahlrechtspruefung> wahlrechtspruefungen;

  private final Loader<WvpWarnung> wvpWarnungenLoader;
  private List<WvpWarnung> wvpWarnungen;

  public Wahldaten(Wahlobject wahl, Loader<Gebietobject> gebieteLoader, Loader<Gruppe> gruppenLoader, Loader<GruppeImGebiet> gruppenImGebietLoader, Loader<Wahlvorschlag> wahlvorschlaegeLoader,
      Loader<Kandidatur> kandidaturenLoader, Loader<PersonenVerknuepfung> personenVerknuepfungenLoader, StimmenLoader stimmenLoader, Loader<Gewaehlt> gewaehlteVorperiodeLoader,
      Loader<Sitze> sitzeVorperiodeLoader, Loader<WahlvorschlagsPerson> wahlvorschlagsPersonenLoader, Loader<Wahlausschussmitglied> wahlausschussmitgliederLoader,
      Loader<WahlausschussEntschaedigung> wahlausschussEntschaedigungenLoader, Loader<StaatenGruppe> staatenGruppenLoader, Loader<Gebietsgruppierung> gebietsgruppierungenLoader,
      SystemGruppeEigenschaften systemGruppeEigenschaften, AbstractWahlParameterLoader wahlParameterLoader, ZulassungPruefer zulassungPruefer, StimmzettelFormatter stimmzettelFormatter,
      EntityAufStimmzettelDatenStimmzettelPositionBerechnung entityAufStimmzettelDatenStimmzettelPositionBerechnung, GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer,
      Loader<GebietPolygonPoint> gebietPolygonPointsLoader, Loader<Fremdkandidatur> fremdkandidaturenLoader, Loader<InternetSeite> internetSeitenLoader,
      Loader<WahlbeteiligungVerlauf> wahlbeteiligungVerlaeufeLoader, Loader<StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationenLoader, Loader<Veranstaltung> veranstaltungenLoader,
      Loader<Stimmzettel> stimmzettelLoader, Loader<StimmzettelKopfdatum> stimmzettelKopfdatenLoader, Loader<Pruefkriterium> pruefkriterienLoader,
      Loader<Wahlrechtspruefung> wahlrechtspruefungenLoader, Loader<WvpWarnung> wvpWarnungenLoader) {
    super(gebieteLoader, gruppenLoader, gruppenImGebietLoader, wahlvorschlaegeLoader, wahlvorschlagsPersonenLoader, wahlausschussmitgliederLoader, kandidaturenLoader, personenVerknuepfungenLoader,
        stimmenLoader, gewaehlteVorperiodeLoader, sitzeVorperiodeLoader, staatenGruppenLoader, gebietsgruppierungenLoader, wahlausschussEntschaedigungenLoader, gebietPolygonPointsLoader,
        fremdkandidaturenLoader, internetSeitenLoader, wahlbeteiligungVerlaeufeLoader, stimmeingabePlausiKonfigurationenLoader, veranstaltungenLoader, stimmzettelLoader, stimmzettelKopfdatenLoader);

    this.wahl = wahl;

    this.wahlParameterLoader = wahlParameterLoader;
    this.zulassungPruefer = zulassungPruefer;
    this.stimmzettelFormatter = stimmzettelFormatter;
    this.entityAufStimmzettelDatenStimmzettelPositionBerechnung = entityAufStimmzettelDatenStimmzettelPositionBerechnung;
    this.gruppeImGebietReihenfolgeVorsortierer = gruppeImGebietReihenfolgeVorsortierer;

    this.systemGruppeEigenschaften = systemGruppeEigenschaften;

    this.pruefkriterienLoader = pruefkriterienLoader;
    this.wahlrechtspruefungenLoader = wahlrechtspruefungenLoader;

    this.wvpWarnungenLoader = wvpWarnungenLoader; // ELECT-11642
  }

  public Wahlobject getWahl() {
    return wahl;
  }

  private Gebietobject wurzelgebiet;

  public Gebietobject getWurzelgebiet() {
    if (wurzelgebiet == null) {
      wurzelgebiet = getGebiete().stream().filter(gebiet -> !gebiet.hasUebergeordnetesGebiet()).findFirst().orElse(null);
    }

    return wurzelgebiet;
  }

  private Map<PolitischeGruppe, List<Wahlvorschlag>> wahlvorschlaegeNachGruppen;

  public Map<PolitischeGruppe, List<Wahlvorschlag>> getWahlvorschlaegeNachGruppen() {
    if (wahlvorschlaegeNachGruppen == null) {
      wahlvorschlaegeNachGruppen = new HashMap<>();
      for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
        if (!wahlvorschlaegeNachGruppen.containsKey(wahlvorschlag.getGruppe())) {
          wahlvorschlaegeNachGruppen.put(wahlvorschlag.getGruppe(), new ArrayList<Wahlvorschlag>());
        }
        wahlvorschlaegeNachGruppen.get(wahlvorschlag.getGruppe()).add(wahlvorschlag);
      }
    }
    return wahlvorschlaegeNachGruppen;
  }

  private transient List<Liste> listen;

  public List<Liste> getListenDerGruppe(Gruppe gruppe) {
    return getListen().stream().filter(l -> l.getGruppe().equals(gruppe)).collect(Collectors.toList());
  }

  public List<Liste> getListen() {
    if (listen != null) {
      return listen;
    }

    listen = new ArrayList<>();
    for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
      if (wahlvorschlag.isListe()) {
        listen.add(wahlvorschlag.castTo(Liste.class).getEntity());
      }
    }

    return listen;
  }

  public List<Liste> getZugelasseneListen() {
    return getListen().stream().filter(AbstractWahlvorschlag::isZugelassen).collect(Collectors.toList());
  }

  public List<Direktkandidat> getZugelasseneDirektkandidaten() {
    return getDirektkandidaten().stream().filter(AbstractWahlvorschlag::isZugelassen).collect(Collectors.toList());
  }

  public Map<Gebiet, List<Liste>> getListenNachGebiet() {
    Map<Gebiet, List<Liste>> result = new HashMap<>();
    for (Liste liste : getListen()) {
      Gebiet gebiet = liste.getGebiet();
      MultiMapUtil.getArrayList(result, gebiet).add(liste);
    }
    return result;
  }

  public List<Liste> getListenZuGebiet(Gebietobject gebiet) {
    return getListen().stream().filter(liste -> liste.getGebiet().equals(gebiet)).collect(Collectors.toList());
  }

  public List<Liste> getZugelasseneListenZuGebiet(Gebietobject gebiet) {
    return getListen().stream().filter(liste -> liste.isZugelassen() && liste.getGebiet().equals(gebiet)).collect(Collectors.toList());
  }

  public List<Direktkandidat> getZugelasseneDirektkandidatenZuGebiet(Gebietobject gebiet) {
    return getDirektkandidaten().stream().filter(direktkandidat -> direktkandidat.isZugelassen() && direktkandidat.getGebiet().equals(gebiet)).collect(Collectors.toList());
  }

  public List<Liste> getListenZuGebietOhneListenverbindung(Gebietobject gebiet) {
    return getListenZuGebiet(gebiet).stream().filter(liste -> liste.getListenverbindung() == null).collect(Collectors.toList());
  }

  public List<Liste> getZugelasseneListenZuGebietOhneListenverbindung(Gebietobject gebiet) {
    return getZugelasseneListenZuGebiet(gebiet).stream().filter(liste -> liste.getListenverbindung() == null).collect(Collectors.toList());
  }

  private Map<Gebiet, Set<Listenverbindung>> listenverbindungenPerGebiet;

  public Collection<Listenverbindung> getListenverbindungen() {
    Collection<Listenverbindung> result = new ArrayList<>();

    for (Set<Listenverbindung> listenverbindungen : getListenverbindungenPerGebiet().values()) {
      result.addAll(listenverbindungen);
    }

    return result;
  }

  public Collection<Listenverbindung> getListenverbindungenFuerGebiet(Gebiet gebietobject) {
    Set<Listenverbindung> listenverbindungen = getListenverbindungenPerGebiet().get(gebietobject);

    if (listenverbindungen == null) {
      return new HashSet<>();
    }

    return listenverbindungen;
  }

  private Map<Gebiet, Set<Listenverbindung>> getListenverbindungenPerGebiet() {
    if (listenverbindungenPerGebiet == null) {
      listenverbindungenPerGebiet = new HashMap<>();

      for (Liste liste : getListen()) {
        if (liste.getListenverbindung() != null) {
          Set<Listenverbindung> listenverbindungen = listenverbindungenPerGebiet.computeIfAbsent(liste.getGebiet(), k -> new HashSet<>());

          listenverbindungen.add(liste.getListenverbindung());
        }
      }
    }

    return listenverbindungenPerGebiet;
  }

  private transient Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeProGruppe;

  public Map<Gruppe, Set<Wahlvorschlag>> getAlleWahlvorschlaegeProGruppe() {
    if (alleWahlvorschlaegeProGruppe == null) {
      alleWahlvorschlaegeProGruppe = new HashMap<>();
      for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
        alleWahlvorschlaegeProGruppe.computeIfAbsent(wahlvorschlag.getGruppe(), x -> new HashSet<>()).add(wahlvorschlag);
      }
    }
    return alleWahlvorschlaegeProGruppe;
  }

  private transient List<Direktkandidat> direktkandidaten;

  public List<Direktkandidat> getDirektkandidaten() {
    if (direktkandidaten != null) {
      return direktkandidaten;
    }

    direktkandidaten = new ArrayList<>();
    for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
      if (wahlvorschlag.isDirektkandidat()) {
        direktkandidaten.add(wahlvorschlag.castTo(Direktkandidat.class).getEntity());
      }
    }

    return direktkandidaten;
  }
  private transient List<ReferendumOption> referendumOptionen;

  public List<ReferendumOption> getReferendumOptionen() {
    if (referendumOptionen != null) {
      return referendumOptionen;
    }

    referendumOptionen = new ArrayList<>();

    for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
      if (wahlvorschlag.isReferendumOption()) {
        referendumOptionen.add(wahlvorschlag.getReferendumOption());
      }
    }
    referendumOptionen.sort(new ReferendumOptionByPositionComparator());

    return referendumOptionen;
  }

  private final Map<Gebietobject, List<GruppeImGebiet>> gruppenImGebietZuGebiet = new HashMap<>();

  @Deprecated // nicht weiter verwenden, GruppeImGebiet soll laaaangfristig abgelöst werden
  public List<GruppeImGebiet> getGruppenImGebietZuGebiet(Gebietobject gebiet) {
    if (!gruppenImGebietZuGebiet.containsKey(gebiet)) {
      List<GruppeImGebiet> result = getGruppenImGebiet().stream().filter(gruppeImGebiet -> gebiet.equals(gruppeImGebiet.getGebiet())).collect(Collectors.toList());
      gruppenImGebietZuGebiet.put(gebiet, result);
    }
    return gruppenImGebietZuGebiet.get(gebiet);
  }

  private transient Map<Gruppe, List<Direktkandidatur>> direktkandidaturenNachGruppe;

  private transient Map<Gruppe, List<Direktkandidat>> direktkandidatenNachGruppe;

  private transient Map<Gruppe, List<Listenkandidatur>> listenkandidaturenNachGruppe;

  private transient List<Direktkandidatur> direktkandidaturen;

  public List<Direktkandidatur> getDirektkandidaturen() {
    if (direktkandidaturen == null) {
      mappeKandidaturenNachGruppe();
    }
    return direktkandidaturen;
  }

  public Map<Gruppe, List<Listenkandidatur>> getListenkandidaturenNachGruppe() {
    if (listenkandidaturenNachGruppe == null) {
      mappeKandidaturenNachGruppe();
    }
    return listenkandidaturenNachGruppe;
  }

  public Map<Gruppe, List<Direktkandidatur>> getDirektkandidaturenNachGruppe() {
    if (direktkandidaturenNachGruppe == null) {
      mappeKandidaturenNachGruppe();
    }
    return direktkandidaturenNachGruppe;
  }

  public Map<Gruppe, List<Direktkandidat>> getDirektkandidatenNachGruppe() {
    if (direktkandidatenNachGruppe == null) {
      mappeKandidaturenNachGruppe();
    }
    return direktkandidatenNachGruppe;
  }


  private void mappeKandidaturenNachGruppe() {
    direktkandidaturenNachGruppe = new HashMap<>();
    listenkandidaturenNachGruppe = new HashMap<>();
    direktkandidatenNachGruppe = new HashMap<>();
    direktkandidaturen = new ArrayList<>();
    for (Kandidatur kandidatur : getKandidaturen()) {
      Gruppe gruppeDerKandidatur = kandidatur.getGruppe();
      if (!direktkandidaturenNachGruppe.containsKey(gruppeDerKandidatur)) {
        direktkandidaturenNachGruppe.put(gruppeDerKandidatur, new ArrayList<Direktkandidatur>());
      }

      if (!listenkandidaturenNachGruppe.containsKey(gruppeDerKandidatur)) {
        listenkandidaturenNachGruppe.put(gruppeDerKandidatur, new ArrayList<Listenkandidatur>());
      }

      if (kandidatur.isDirektkandidatur()) {
        Direktkandidatur direktkandidatur = kandidatur.castTo(Direktkandidatur.class).getEntity();
        direktkandidaturen.add(direktkandidatur);
        direktkandidaturenNachGruppe.get(gruppeDerKandidatur).add(direktkandidatur);
        direktkandidatenNachGruppe.computeIfAbsent(gruppeDerKandidatur, x->new ArrayList<>()).add(direktkandidatur.getDirektkandidat());
      } else if (kandidatur.isListenkandidatur() && !kandidatur.isListenkandidaturMarkiertAlsErsatzkandidatur()) { // ELECT-9310, keine Ersatzkandidaturen in den ErgebnisDaten!
        listenkandidaturenNachGruppe.get(gruppeDerKandidatur).add(kandidatur.castTo(Listenkandidatur.class).getEntity());
      }
    }

    for (List<Listenkandidatur> listenkandidaturen : listenkandidaturenNachGruppe.values()) {
      listenkandidaturen.sort(new ListenkandidaturByRelativePositionComparator());
    }
  }

  private transient Map<Gruppe, Map<Gebiet, GruppeImGebiet>> gruppenImGebietNachGruppe = null;

  @Deprecated // nicht weiter verwenden, GruppeImGebiet soll laaaangfristig abgelöst werden
  public Map<Gebiet, GruppeImGebiet> getGruppenImGebietFuerGruppe(Gruppe gruppe) {
    if (gruppenImGebietNachGruppe == null) {
      mappeGruppenImGebietNachGruppe();
    }
    return gruppenImGebietNachGruppe.get(gruppe);
  }

  @Deprecated // nicht weiter verwenden, GruppeImGebiet soll laaaangfristig abgelöst werden
  public GruppeImGebiet getGruppeImGebietZuGruppeUndGebiet(Gruppe gruppe, Gebietobject gebietobject) {
    return getGruppenImGebietFuerGruppe(gruppe).get(gebietobject);
  }

  private void mappeGruppenImGebietNachGruppe() {
    gruppenImGebietNachGruppe = new HashMap<>();
    for (Gruppe gruppe : getGruppen()) {
      gruppenImGebietNachGruppe.put(gruppe, new HashMap<>());
    }

    for (GruppeImGebiet gruppeImGebiet : getGruppenImGebiet()) {
      gruppenImGebietNachGruppe.get(gruppeImGebiet.getGruppe()).put(gruppeImGebiet.getGebiet(), gruppeImGebiet);
    }
  }

  private transient Map<Gruppe, Set<Gebietobject>> gebieteInDenenGruppeVorkommt;

  @Deprecated // nicht weiter verwenden, GruppeImGebiet soll laaaangfristig abgelöst werden
  public Set<Gebietobject> getGebieteInDenenGruppeVorkommt(Gruppe gruppe) {
    if (gebieteInDenenGruppeVorkommt == null) {
      mappeGebieteInDenenGruppeVorkommt();
    }
    return gebieteInDenenGruppeVorkommt.get(gruppe);
  }

  private void mappeGebieteInDenenGruppeVorkommt() {
    gebieteInDenenGruppeVorkommt = new HashMap<>();
    if (gruppenImGebietNachGruppe == null) {
      mappeGruppenImGebietNachGruppe();
    }
    for (Map.Entry<Gruppe, Map<Gebiet, GruppeImGebiet>> entry : gruppenImGebietNachGruppe.entrySet()) {
      HashSet<Gebietobject> gebiete = new HashSet<>();
      gebieteInDenenGruppeVorkommt.put(entry.getKey(), gebiete);
      for (GruppeImGebiet gruppeImGebiet : entry.getValue().values()) {
        gebiete.add((Gebietobject) gruppeImGebiet.getGebiet());
      }
    }
  }

  private transient Map<Gebietobject, Set<Gruppe>> gruppenMitGruppenImGebiet = null;

  public Set<Gruppe> getGruppenMitGruppenImGebiet(Gebietobject gebiet) {
    if (gruppenMitGruppenImGebiet == null) {
      mappeGruppenMitGruppenImGebiet();
    }
    return gruppenMitGruppenImGebiet.get(gebiet);
  }

  private void mappeGruppenMitGruppenImGebiet() {
    gruppenMitGruppenImGebiet = new HashMap<>();
    for (Gebietobject gebietobject : getGebiete()) {
      HashSet<Gruppe> gruppenImGebiet = new HashSet<>();
      gruppenMitGruppenImGebiet.put(gebietobject, gruppenImGebiet);
      for (Gruppe gruppe : getGruppen()) {
        if (getGebieteInDenenGruppeVorkommt(gruppe).contains(gebietobject)) {
          gruppenImGebiet.add(gruppe);
        }
      }
    }
  }

  private transient Map<Gruppe, Map<Gebietobject, GruppeImGebiet>> gruppenImGebietNachGruppeUndGebiet;

  public GruppeImGebiet getGruppeImGebiet(Gruppe gruppe, Gebietobject gebietobject) {
    if (gruppenImGebietNachGruppeUndGebiet == null) {
      mappeGruppenImGebietNachGruppeUndGebiet();
    }
    return gruppenImGebietNachGruppeUndGebiet.get(gruppe).get(gebietobject);
  }

  private void mappeGruppenImGebietNachGruppeUndGebiet() {
    gruppenImGebietNachGruppeUndGebiet = new HashMap<>();
    for (Gruppe gruppe : getGruppen()) {
      gruppenImGebietNachGruppeUndGebiet.put(gruppe, new HashMap<Gebietobject, GruppeImGebiet>());
    }
    for (GruppeImGebiet gruppeImGebiet : getGruppenImGebiet()) {
      gruppenImGebietNachGruppeUndGebiet.get(gruppeImGebiet.getGruppe()).put((Gebietobject) gruppeImGebiet.getGebiet(), gruppeImGebiet);
    }
  }

  private final Map<Gebietobject, List<Gruppe>> gruppenSortiertNachPositionAufGebiet = new HashMap<>();

  public List<Gruppe> getGruppenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    return gruppenSortiertNachPositionAufGebiet.computeIfAbsent(gebietobject, i -> getGruppenZugelassenSortiertNachPositionAufGebietIntern(gebietobject, null));
  }

  public List<Gruppe> getGruppenSortiertNachPositionAufWurzelgebiet() {
    return getGruppenSortiertNachPositionAufGebiet(getWurzelgebiet());
  }

  public List<Gruppe> getGruppenZugelassenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    return getGruppenZugelassenSortiertNachPositionAufGebietIntern(gebietobject, Stimmenart.GESAMT);
  }

  public List<Gruppe> getGruppenMitDirektkandidatZugelassenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    return getGruppenZugelassenSortiertNachPositionAufGebietIntern(gebietobject, Stimmenart.DIREKTSTIMME);
  }

  public List<Gruppe> getGruppenMitListeZugelassenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    return getGruppenZugelassenSortiertNachPositionAufGebietIntern(gebietobject, Stimmenart.LISTENSTIMME);
  }

  public List<PolitischeGruppe> getPolitischeGruppenMitListeZugelassenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    List<Gruppe> gruppen = getGruppenZugelassenSortiertNachPositionAufGebietIntern(gebietobject, Stimmenart.LISTENSTIMME);
    return gruppen.stream().filter(AbstractGruppe::isPolitischeGruppe).map(gruppe -> gruppe.castTo(PolitischeGruppe.class).getEntity())
        .collect(Collectors.toCollection(() -> new ArrayList<>(gruppen.size())));
  }

  public List<PolitischeGruppe> getPolitischeGruppenMitDirektkandidatZugelassenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    List<Gruppe> gruppen = getGruppenZugelassenSortiertNachPositionAufGebietIntern(gebietobject, Stimmenart.DIREKTSTIMME);
    return gruppen.stream().filter(AbstractGruppe::isPolitischeGruppe).map(gruppe -> gruppe.castTo(PolitischeGruppe.class).getEntity())
        .collect(Collectors.toCollection(() -> new ArrayList<>(gruppen.size())));
  }

  private List<Gruppe> getGruppenZugelassenSortiertNachPositionAufGebietIntern(Gebietobject gebietobject, @Nullable Stimmenart stimmenart) {

    List<Gruppe> result = new ArrayList<>();
    List<GruppeImGebiet> gruppenImGebietLokal = new ArrayList<>();
    for (Gruppe gruppe : getGruppenMitGruppenImGebiet(gebietobject)) {

      if (gruppe.getGruppeImGebietNachGebiet() == null) {
        gruppe.setGruppeImGebietNachGebiet(() -> getGruppenImGebietFuerGruppe(gruppe)); // beschleunigt GruppeImGebietByPositionAbgeleitetComparator;
      }

      GruppeImGebiet gruppeImGebiet = getGruppeImGebiet(gruppe, gebietobject);
      if (isZugelassen(gruppeImGebiet, stimmenart)) {
        result.add(gruppe);
        gruppenImGebietLokal.add(gruppeImGebiet);
      }
    }

    // ELECTKOM-767
    GruppeByPositionOrStimmzettelPositionAufGebietComparator gruppeByPositionOrStimmzettelPositionAufGebietComparator = new GruppeByPositionOrStimmzettelPositionAufGebietComparator(gebietobject,
        new GruppeByPositionComparator(gruppenImGebietLokal), getAlleWahlvorschlaegeProGruppe(), getStimmzettelPositionenZugelasseneWahlvorschlaegeAufGebiet(gebietobject));

    result.sort(gruppeByPositionOrStimmzettelPositionAufGebietComparator);

    return result;
  }

  private Map<Gebiet, Set<Gruppe>> gruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet;

  private Map<Gebiet, Set<Gruppe>> gruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet;

  private Map<Gebiet, Set<Gruppe>> gruppenMitZugelassenerListeInGebietshierarchieNachGebiet;

  // TODO ELECT-3147 "Zugelassen" vs. Auf Stimmzettel
  protected boolean isZugelassen(GruppeImGebiet gruppeImGebiet, @Nullable Stimmenart stimmenart) {
    if (stimmenart == null) {
      return true; // ohne Stimmartberücksichtigung kann nicht nicht zugelassen sein
    }

    // NOTE ELECTMUC-961, dass hier FRÜHER auf GruppeImGebiet#isStatusUnbestimmtOderZugelassen geprüft wurde

    initZugelasseneGruppenMaps();
    final Set<Gruppe> gruppenZugelassen;
    switch (stimmenart) {
      case GESAMT :
        gruppenZugelassen = gruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet.get(gruppeImGebiet.getGebiet());
        break;
      case DIREKTSTIMME :
        gruppenZugelassen = gruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet.get(gruppeImGebiet.getGebiet());
        break;
      case LISTENSTIMME :
        gruppenZugelassen = gruppenMitZugelassenerListeInGebietshierarchieNachGebiet.get(gruppeImGebiet.getGebiet());
        break;
      default :
        throw new IllegalArgumentException("Stimmenart nicht unterstützt: " + stimmenart);
    }

    return gruppenZugelassen != null && gruppenZugelassen.contains(gruppeImGebiet.getGruppe());
  }

  private void initZugelasseneGruppenMaps() {
    if (gruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet == null || gruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet == null
        || gruppenMitZugelassenerListeInGebietshierarchieNachGebiet == null) {

      gruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet = new HashMap<>();
      gruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet = new HashMap<>();
      gruppenMitZugelassenerListeInGebietshierarchieNachGebiet = new HashMap<>();

      for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
        // TODO ELECT-3147 "Zugelassen" vs. Auf Stimmzettel
        if (wahlvorschlag.isZugelassenOderUnbestimmt()) {
          Set<Gebiet> betroffeneGebiete = new HashSet<>();
          for (Gebietobject gebietobject : getUntersteGebiete()) {
            // NOTE, dass die Listengebiete die unterste Gebietsebene sein können (nämlich im WUS bevor Stimmbezirke importiert wurden)
            if (gebietobject.isEqualOderUntergebietVon(wahlvorschlag.getGebiet())) {
              betroffeneGebiete.add(gebietobject);
              betroffeneGebiete.addAll(gebietobject.getInGebietshierarchieUebergeordneteGebiete());
            }
          }

          PolitischeGruppe gruppe = wahlvorschlag.getGruppe();
          for (Gebiet gebiet : betroffeneGebiete) {
            gruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet.computeIfAbsent(gebiet, x -> new HashSet<>()).add(gruppe);

            if (wahlvorschlag.isDirektkandidat()) {
              gruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet.computeIfAbsent(gebiet, x -> new HashSet<>()).add(gruppe);
            } else if (wahlvorschlag.isListe()) {
              gruppenMitZugelassenerListeInGebietshierarchieNachGebiet.computeIfAbsent(gebiet, x -> new HashSet<>()).add(gruppe);
            }
          }
        }
      }
    }

  }

  private final Map<Gebietobject, List<PolitischeGruppe>> politischeGruppenSortiertNachPositionAufGebiet = new HashMap<>();

  public List<PolitischeGruppe> getPolitischeGruppenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    List<PolitischeGruppe> result = politischeGruppenSortiertNachPositionAufGebiet.get(gebietobject);
    if (result == null) {
      result = new ArrayList<>();
      politischeGruppenSortiertNachPositionAufGebiet.put(gebietobject, result);
      for (Gruppe gruppe : getGruppenSortiertNachPositionAufGebiet(gebietobject)) {
        if (gruppe.isPolitischeGruppe()) {
          result.add(gruppe.castTo(PolitischeGruppe.class).getEntity());
        }
      }
    }

    return result;
  }

  public List<PolitischeGruppe> getPolitischeGruppen() {
    return getGruppen().stream().filter(AbstractGruppe::isPolitischeGruppe).map(gruppe -> gruppe.castTo(PolitischeGruppe.class).getEntity()).collect(Collectors.toList());
  }

  private final Map<Gebietobject, List<Gruppe>> referendumVorlagenSortiertNachPositionAufGebiet = new HashMap<>();

  public List<Gruppe> getReferendumVorlagenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    List<Gruppe> result = referendumVorlagenSortiertNachPositionAufGebiet.get(gebietobject);
    if (result == null) {
      result = new ArrayList<>();
      referendumVorlagenSortiertNachPositionAufGebiet.put(gebietobject, result);
      for (Gruppe gruppe : getGruppenSortiertNachPositionAufGebiet(gebietobject)) {
        if (gruppe.isReferendumVorlage()) {
          result.add(gruppe);
        }
      }
    }
    return result;
  }

  private final Map<Gruppe, List<ReferendumOption>> referendumOptionenSortiertNachPosition = new HashMap<>();

  public List<ReferendumOption> getReferendumOptionenSortiertNachPosition(Gruppe referendumVorlage) {
    List<ReferendumOption> result = referendumOptionenSortiertNachPosition.get(referendumVorlage);
    if (result == null) {
      result = new ArrayList<>();
      referendumOptionenSortiertNachPosition.put(referendumVorlage, result);
      for (ReferendumOption referendumOption : getReferendumOptionen()) {
        if (referendumOption.getGruppe().equals(referendumVorlage)) {
          result.add(referendumOption);
        }
      }
      result.sort(new ReferendumOptionByPositionComparator());
    }

    return result;
  }

  private transient Map<Gruppe, List<ReferendumOption>> referendumOptionenNachReferendumVorlage;

  public Map<Gruppe, List<ReferendumOption>> getReferendumOptionenNachReferendumVorlage() {
    if (referendumOptionenNachReferendumVorlage == null) {
      referendumOptionenNachReferendumVorlage = new HashMap<>();
      for (ReferendumOption referendumOption : getReferendumOptionen()) {
        Gruppe referendumVorlageDerReferendumOption = referendumOption.getGruppe();
        if (!referendumOptionenNachReferendumVorlage.containsKey(referendumVorlageDerReferendumOption)) {
          referendumOptionenNachReferendumVorlage.put(referendumVorlageDerReferendumOption, new ArrayList<ReferendumOption>());
        }

        referendumOptionenNachReferendumVorlage.get(referendumVorlageDerReferendumOption).add(referendumOption);
      }

      for (List<ReferendumOption> referendumOptionen : referendumOptionenNachReferendumVorlage.values()) {
        referendumOptionen.sort(new ReferendumOptionByPositionComparator());
      }
    }
    return referendumOptionenNachReferendumVorlage;
  }

  private final Map<Gebietobject, List<PolitischeGruppe>> politischeGruppenZugelassenSortiertNachPositionAufGebiet = new HashMap<>();

  public List<PolitischeGruppe> getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    List<PolitischeGruppe> result = politischeGruppenZugelassenSortiertNachPositionAufGebiet.get(gebietobject);
    if (result == null) {
      result = new ArrayList<>();
      politischeGruppenZugelassenSortiertNachPositionAufGebiet.put(gebietobject, result);
      for (Gruppe gruppe : getGruppenZugelassenSortiertNachPositionAufGebiet(gebietobject)) {
        if (gruppe.isPolitischeGruppe()) {
          result.add(gruppe.castTo(PolitischeGruppe.class).getEntity());
        }
      }
    }

    return result;
  }

  public List<PolitischeGruppe> getPolitischeGruppenInPraesentationSortiertNachPositionAufGebiet(Gebietobject gebietobject) {
    return getPolitischeGruppenSortiertNachPositionAufGebiet(gebietobject).stream().filter(Gruppe::getInPraesentation).collect(Collectors.toList());
  }

  public List<PolitischeGruppe> getPolitischeGruppenNichtInPraesentation(Gebietobject gebietobject) {
    return getPolitischeGruppenSortiertNachPositionAufGebiet(gebietobject).stream().filter(politischeGruppe -> !politischeGruppe.getInPraesentation()).collect(Collectors.toList());
  }

  public List<Gruppe> getSystemgruppen() {
    if (systemgruppen.isEmpty()) {
      for (Gruppe gruppe : getGruppen()) {
        if (gruppe.isSystemGruppe()) {
          systemgruppen.add(gruppe);
        }
      }
    }

    return systemgruppen;
  }

  private transient Map<GebietsartTemplate, List<Gebietobject>> gebieteNachGebietsart = null;

  private <G extends GebietsartTemplate> Map<G, List<Gebietobject>> getGebieteNachGebietsart(Collection<G> templates) {
    if (gebieteNachGebietsart == null) {
      mappeGebieteNachGebietsart(templates);
    }
    return (Map<G, List<Gebietobject>>) gebieteNachGebietsart;
  }

  private <G extends GebietsartTemplate> void mappeGebieteNachGebietsart(Collection<G> templates) {
    gebieteNachGebietsart = new HashMap<>();

    for (G template : templates) {
      gebieteNachGebietsart.put(template, new ArrayList<Gebietobject>());
    }

    for (Gebietobject gebiet : getGebiete()) {
      gebieteNachGebietsart.get(gebiet.getGebietsart().getCustomGebietsartTemplate(templates)).add(gebiet);
    }

    for (List<Gebietobject> gebieteUnsortiert : gebieteNachGebietsart.values()) {
      gebieteUnsortiert.sort(new GebietByPositionComparator());
    }
  }

  public <G extends GebietsartTemplate> List<Gebietobject> getGebieteMitGebietsartSortiertNachPosition(G gebietsartTemplate, Collection< ? extends G> templates) {
    if (gebieteNachGebietsart == null) {
      mappeGebieteNachGebietsart(templates);
    }

    return gebieteNachGebietsart.get(gebietsartTemplate);
  }

  public <G extends GebietsartTemplate> List<Gebietobject> getGebieteMitGebietsartSortiertNachNummer(G gebietsartTemplate, Collection< ? extends G> templates) {

    List<Gebietobject> result = new ArrayList<>(getGebieteMitGebietsartSortiertNachPosition(gebietsartTemplate, templates));

    result.sort(GebietByNummerComparator.ASCENDING);

    return result;
  }

  private transient Map<Gebietobject, List<Gebietobject>> untergeordneteGebiete = null;

  public List<Gebietobject> getUntergeordneteGebiete(Gebietobject gebiet) {
    if (gebiet == null) {
      return Collections.emptyList();
    }
    if (untergeordneteGebiete == null) {
      mappeGebieteNachHierarchie();
    }
    return untergeordneteGebiete.get(gebiet);
  }

  /**
   * DB-Zugriffe vermeiden => nur nach oben gehen, Baum aufbauen, Gebiete sortieren
   */
  private void mappeGebieteNachHierarchie() {
    untergeordneteGebiete = new HashMap<>();
    for (Gebietobject gebiet : getGebiete()) {
      untergeordneteGebiete.put(gebiet, new ArrayList<Gebietobject>());
    }
    for (Gebietobject gebiet : getGebiete()) {
      if (gebiet.getUebergeordnetesGebiet() != null) {
        untergeordneteGebiete.get(gebiet.getUebergeordnetesGebiet()).add(gebiet);
      }
    }

    for (List<Gebietobject> gebiets : untergeordneteGebiete.values()) {
      gebiets.sort(new GebietByGebietsartAndPositionComparator()); // Sortierung wegen ELECTDD-285 geändert, falls problematisch, hier rückgängig machen und in de.ivu.elect.was.exchange.internet.modell.PresentationGebietsbaum erzeugeRoot() anpassen...
    }
  }

  private transient List<Gebietobject> untersteGebiete;

  public List<Gebietobject> getUntersteGebiete() {
    if (untersteGebiete == null) {
      untersteGebiete = new ArrayList<>();
      for (Gebietobject gebiet : getGebiete()) {
        if (getUntergeordneteGebiete(gebiet).isEmpty()) {
          untersteGebiete.add(gebiet);
        }
      }
    }

    return untersteGebiete;
  }

  private transient List<Gebietobject> erfassungsgebiete;

  /**
   * NOTE, dass im WUS nicht zu jedem Zeitpunkt Erfassungsgebiete vorhanden sind -- cf. getUntersteGebiete()
   */
  public List<Gebietobject> getErfassungsgebiete() {
    if (erfassungsgebiete == null) {
      erfassungsgebiete = new ArrayList<>();
      for (Gebietobject gebietobject : getGebiete()) {
        if (gebietobject.isErfassungsgebiet()) {
          erfassungsgebiete.add(gebietobject);
        }
      }
    }
    return erfassungsgebiete;
  }

  private transient List<Gebietobject> erfassungsgebieteLautGebietsart;

  /**
   * NOTE, dass im WUS nicht zu jedem Zeitpunkt Erfassungsgebiete vorhanden sind -- cf. getUntersteGebiete()
   */
  public List<Gebietobject> getErfassungsgebieteLautGebietsart() {
    if (erfassungsgebieteLautGebietsart == null) {
      erfassungsgebieteLautGebietsart = new ArrayList<>();
      for (Gebietobject gebietobject : getGebiete()) {
        if (gebietobject.isErfassungsgebietLautGebietsart()) {
          erfassungsgebieteLautGebietsart.add(gebietobject);
        }
      }
    }
    return erfassungsgebieteLautGebietsart;
  }

  private final transient Map<Gebietobject, List<Gebietobject>> erfassungsgebieteUnterhalbVon = new HashMap<>();

  /**
   * Liefert für ein Gebiet die untergeordneten Erfassungsgebiete ohne Berücksichtigung von Ergebnis-Relevanz
   *
   * @see #getErfassungsgebieteErgebnisRelevantUnterhalbVon(Gebiet)
   */
  public List<Gebietobject> getErfassungsgebieteUnterhalbVon(Gebietobject gebietobject) {
    return erfassungsgebieteUnterhalbVon.computeIfAbsent(gebietobject, x -> getErfassungsgebiete().stream()
        .filter(unter -> unter.isInGebietshierarchieUntergeordnetesGebietVon(gebietobject))
        .collect(Collectors.toList()));
  }

  private final transient Map<Gebietobject, List<Gebietobject>> erfassungsgebieteLautGebietsartUnterhalbVon = new HashMap<>();

  public List<Gebietobject> getErfassungsgebieteLautGebietsarUnterhalbVon(Gebietobject gebietobject) {
    return erfassungsgebieteLautGebietsartUnterhalbVon.computeIfAbsent(gebietobject, x -> getErfassungsgebieteLautGebietsart().stream()
        .filter(unter -> unter.isInGebietshierarchieUntergeordnetesGebietVon(gebietobject))
        .collect(Collectors.toList()));
  }

  private final transient Map<Gebietobject, List<Gebietobject>> erfassungsgebieteErgebnisRelevantUnterhalbVon = new HashMap<>();

  /**
   * Liefert für ein Gebiet die Gebiete, aus denen sich das Ergebnis kumulieren lässt. ACHTUNG, ist ein Gebiet nicht ergebnisrelevant (z.B. Sondergebiete bei destatis), dann muss das Ergebnis für das
   * Gebiet direkt erfasst werden, d.h. das Ergebnis enthält nur das Gebiet selbst.
   */
  public List<Gebietobject> getErfassungsgebieteErgebnisRelevantUnterhalbVon(Gebietobject gebietobject) {
    List<Gebietobject> result = erfassungsgebieteErgebnisRelevantUnterhalbVon.get(gebietobject);
    if (result == null) {
      result = new ArrayList<>();
      erfassungsgebieteErgebnisRelevantUnterhalbVon.put(gebietobject, result);
      if (gebietobject.isErfassungsgebiet() || !gebietobject.isErgebnisRelevant()) {
        result.add(gebietobject);
      }

      if (gebietobject.isErgebnisRelevant()) {
        for (Gebietobject untergebiet : getUntergeordneteGebiete(gebietobject)) {
          result.addAll(getErfassungsgebieteErgebnisRelevantUnterhalbVon(untergebiet));
        }
      }
    }

    return result;
  }

  private transient Gruppe gruppeUebrige;

  public Gruppe getGruppeUebrige() {
    if (gruppeUebrige == null) {
      gruppeUebrige = getGruppen().stream().filter(gruppe -> gruppe.isGruppeEqualsSystemGruppe(SystemGruppe.UEBRIGE, systemGruppeEigenschaften)).findFirst().orElse(gruppeUebrige);
      if (gruppeUebrige == null) {
        throw new IllegalStateException("Gruppe ÜBRIGE nicht gefunden!");
      }
    }
    return gruppeUebrige;

  }

  private Map<SystemGruppe, Gruppe> gruppenFuerSystemGruppe;

  public Gruppe getGruppeFuerSystemGruppe(SystemGruppe systemGruppe) {
    if (gruppenFuerSystemGruppe == null) {
      gruppenFuerSystemGruppe = new EnumMap<>(SystemGruppe.class);
      for (SystemGruppe systemGruppeLokal : SystemGruppe.values()) {
        getGruppen().stream().filter(AbstractGruppe::isSystemGruppe).filter(gruppe -> gruppe.isGruppeEqualsSystemGruppe(systemGruppeLokal, systemGruppeEigenschaften)).findFirst()
            .ifPresent(gruppe -> gruppenFuerSystemGruppe.put(systemGruppeLokal, gruppe));
      }
    }
    return gruppenFuerSystemGruppe.get(systemGruppe);
  }

  private transient Gruppe gruppeGueltige;

  public Gruppe getGruppeGueltige() {
    if (gruppeGueltige == null) {
      gruppeGueltige = AbstractGruppe.findeSystemGruppe(getGruppen(), SystemGruppe.GUELTIGE, systemGruppeEigenschaften);
    }
    return gruppeGueltige;

  }

  private transient Gruppe gruppeUnGueltige;

  public Gruppe getGruppeUnGueltige() {
    if (gruppeUnGueltige == null) {
      gruppeUnGueltige = AbstractGruppe.findeSystemGruppe(getGruppen(), SystemGruppe.UNGUELTIGE, systemGruppeEigenschaften);
    }
    return gruppeUnGueltige;

  }

  public SystemGruppeEigenschaften getSystemGruppeEigenschaften() {
    return systemGruppeEigenschaften;
  }

  public List<Gebietobject> findGebieteByGebietsarten(Collection<Gebietsart> gebietsarten) {
    return getGebiete().stream().filter(gebiet -> gebietsarten.contains(gebiet.getGebietsart())).collect(Collectors.toList());
  }

  private List<Gebietobject> direktwahlGebiete = null;

  public List<Gebietobject> getDirektwahlGebiete() {
    if (direktwahlGebiete == null) {
      direktwahlGebiete = new ArrayList<>();
      for (Gebietobject gebietobject : getGebiete()) {
        if (gebietobject.isDirektwahlgebiet()) {
          direktwahlGebiete.add(gebietobject);
        }
      }
    }
    return direktwahlGebiete;
  }
  private List<Gebietobject> listenWahlGebiete = null;

  public List<Gebietobject> getListenWahlGebiete() {
    if (listenWahlGebiete == null) {
      listenWahlGebiete = new ArrayList<>();
      for (Gebietobject gebietobject : getGebiete()) {
        if (gebietobject.isListenwahlgebiet()) {
          listenWahlGebiete.add(gebietobject);
        }
      }
    }
    return listenWahlGebiete;
  }

  private Map<Gebietobject, List<Direktkandidatur>> direktkandidaturenNachGebiet;

  public List<Direktkandidatur> getDirektkandidaturenNachGebiet(Gebiet gebiet) {
    if (direktkandidaturenNachGebiet == null) {
      direktkandidaturenNachGebiet = new HashMap<>();

      for (Direktkandidatur direktkandidatur : getDirektkandidaturen()) {
        List<Direktkandidatur> direktkandidaturenImGebiet = direktkandidaturenNachGebiet.get(direktkandidatur.getGebiet());
        if (direktkandidaturenImGebiet == null) {
          direktkandidaturenImGebiet = new ArrayList<>();
          direktkandidaturenNachGebiet.put((Gebietobject) direktkandidatur.getGebiet(), direktkandidaturenImGebiet);
        }
        direktkandidaturenImGebiet.add(direktkandidatur);
      }
      // TODO sortieren??
    }

    List<Direktkandidatur> direktkandidaturen = direktkandidaturenNachGebiet.get(gebiet);
    if (direktkandidaturen == null) {
      return Collections.emptyList();
    }
    return direktkandidaturen;
  }

  private final Map<Gebiet, Set<Direktkandidatur>> direktkandidaturenInGebietshierarchie = new HashMap<>();

  public Set<Direktkandidatur> getDirektkandidaturenInGebietshierarchie(Gebiet gebietobject) {
    if (!direktkandidaturenInGebietshierarchie.containsKey(gebietobject)) {
      HashSet<Direktkandidatur> kandidaturen = new HashSet<>();
      direktkandidaturenInGebietshierarchie.put(gebietobject, kandidaturen);
      for (Direktkandidatur direktkandidatur : getDirektkandidaturen()) {
        if (direktkandidatur.getWahlvorschlag().getGebiet().isInGebietshierarchieVon(gebietobject)) {
          kandidaturen.add(direktkandidatur);
        }
      }
    }
    return direktkandidaturenInGebietshierarchie.get(gebietobject);
  }

  public Set<Direktkandidatur> getZugelasseneDirektkandidaturenInGebietshierarchie(Gebiet gebietobject) {
    return getDirektkandidaturenInGebietshierarchie(gebietobject).stream().filter(AbstractKandidatur::isZugelassen).collect(Collectors.toSet());
  }

  private final Map<Gebiet, Set<Liste>> listenInGebietshierarchie = new HashMap<>();

  public Set<Liste> getListenInGebietshierarchie(Gebiet gebietobject) {
    if (!listenInGebietshierarchie.containsKey(gebietobject)) {
      HashSet<Liste> listen = new HashSet<>();
      listenInGebietshierarchie.put(gebietobject, listen);
      for (Liste liste : getListen()) {
        if (liste.getGebiet().isInGebietshierarchieVon(gebietobject)) {
          listen.add(liste);
        }
      }
    }
    return listenInGebietshierarchie.get(gebietobject);
  }

  private Map<Listenkandidatur, Direktkandidatur> direktkandidaturenFuerListenkandidaturen;

  /**
   * Liefert eine Map aller Paare aus Listenkandidatur und Direktkandidatur, zwischen denen es eine "identisch"-PersonenVerknuepfung gibt. Gibt es zu einer Listenkandidatur mehrere derartige
   * Direktkandidaturen, so ist zufällig eine davon enthalten.
   */
  public Map<Listenkandidatur, Direktkandidatur> getDirektkandidaturenFuerListenkandidaturen() {
    if (direktkandidaturenFuerListenkandidaturen == null) {
      direktkandidaturenFuerListenkandidaturen = new HashMap<>();
      // TODO verbessern, Ansatz nicht optimal

      Map<Personendaten, List<PersonenVerknuepfung>> personendatenMitVerknuepfungen = new HashMap<>();
      for (PersonenVerknuepfung personenVerknuepfung : getPersonenVerknuepfungen()) {
        if (personenVerknuepfung.isIdentisch()) {
          speichereVerknuepfung(personendatenMitVerknuepfungen, personenVerknuepfung, personenVerknuepfung.getPerson1());
          speichereVerknuepfung(personendatenMitVerknuepfungen, personenVerknuepfung, personenVerknuepfung.getPerson2());
        }
      }

      for (Direktkandidatur direktkandidatur : getDirektkandidaturen()) {
        List<PersonenVerknuepfung> personenVerknuepfungs = personendatenMitVerknuepfungen.get(direktkandidatur);
        if (personenVerknuepfungs != null) {
          for (PersonenVerknuepfung personenVerknuepfung : personenVerknuepfungs) {
            Personendaten person1 = personenVerknuepfung.getPerson1();
            Personendaten person2 = personenVerknuepfung.getPerson2();
            Personendaten andere;
            if (direktkandidatur.equals(person1)) {
              andere = person2;
            } else {
              andere = person1;
            }

            if (andere.isInstanceOf(Listenkandidatur.class)) {
              direktkandidaturenFuerListenkandidaturen.put(andere.castTo(Listenkandidatur.class).getEntity(), direktkandidatur);
            }
          }
        }
      }
    }
    return direktkandidaturenFuerListenkandidaturen;
  }

  private void speichereVerknuepfung(Map<Personendaten, List<PersonenVerknuepfung>> personendatenMitVerknuepfungen, PersonenVerknuepfung personenVerknuepfung, Personendaten person) {

    personendatenMitVerknuepfungen.computeIfAbsent(person, egal -> new ArrayList<>()).add(personenVerknuepfung);
  }

  public Map<Gebiet, List<Wahlausschussmitglied>> getWahlausschussmitgliederAufGebiet() {
    return AbstractWahlausschussmitglied.sortiereUndGruppiereNachGebiet(getWahlausschussmitglieder());
  }

  public boolean isStimmenVorperiodeVorhanden() {
    return !getStimmenVorperiode().isEmpty();
  }

  private final Map<Gebietobject, Set<PolitischeGruppe>> politischeGruppeMitListenInGebietshierarchie = new HashMap<>();

  public Set<PolitischeGruppe> getPolitischeGruppeMitListenInGebietshierarchie(Gebietobject gebiet) {
    if (!politischeGruppeMitListenInGebietshierarchie.containsKey(gebiet)) {
      HashSet<PolitischeGruppe> politischeGruppes = new HashSet<>();
      politischeGruppeMitListenInGebietshierarchie.put(gebiet, politischeGruppes);
      for (Liste liste : getListen()) {
        if (!politischeGruppes.contains(liste.getGruppe()) && liste.getGebiet().isInGebietshierarchieVon(gebiet)) {
          politischeGruppes.add(liste.getGruppe());
        }
      }
    }

    return politischeGruppeMitListenInGebietshierarchie.get(gebiet);
  }

  public boolean hatListeInGebietshierarchie(PolitischeGruppe politischeGruppe, Gebietobject gebiet) {
    return getPolitischeGruppeMitListenInGebietshierarchie(gebiet).contains(politischeGruppe);
  }

  private final Map<Gebietobject, Set<PolitischeGruppe>> politischeGruppeMitDirektkandidatenInGebietshierarchie = new HashMap<>();
  private final Map<Gebietobject, Set<PolitischeGruppe>> politischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie = new HashMap<>();

  public Set<PolitischeGruppe> getPolitischeGruppeMitDirektkandidatenInGebietshierarchie(Gebietobject gebiet) {
    if (!politischeGruppeMitDirektkandidatenInGebietshierarchie.containsKey(gebiet)) {
      initPolitischeGruppeMitDirektkandidatenInGebietshierarchie(gebiet);
    }
    return politischeGruppeMitDirektkandidatenInGebietshierarchie.get(gebiet);
  }

  public Set<PolitischeGruppe> getPolitischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie(Gebietobject gebiet) {
    if (!politischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie.containsKey(gebiet)) {
      initPolitischeGruppeMitDirektkandidatenInGebietshierarchie(gebiet);
    }
    return politischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie.get(gebiet);
  }

  private void initPolitischeGruppeMitDirektkandidatenInGebietshierarchie(Gebietobject gebiet) {
    HashSet<PolitischeGruppe> politischeGruppenZugelassen = new HashSet<>();
    HashSet<PolitischeGruppe> politischeGruppenNichtZugelassen = new HashSet<>();

    for (Direktkandidat direktkandidat : getDirektkandidaten()) {
      if (!politischeGruppenZugelassen.contains(direktkandidat.getGruppe())) {
        if (direktkandidat.getGebiet().isInGebietshierarchieVon(gebiet)) {
          if (direktkandidat.isZugelassen()) {
            politischeGruppenZugelassen.add(direktkandidat.getGruppe());
          } else {
            politischeGruppenNichtZugelassen.add(direktkandidat.getGruppe());
          }
        }
      }
    }

    politischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie.put(gebiet, politischeGruppenZugelassen);
    if (politischeGruppenNichtZugelassen.isEmpty()) {
      // Der normale Fall: alle Direktkandidaten sind zugelassen, beide Mengen sind gleich. Merke nur die zugelassenen.
      politischeGruppeMitDirektkandidatenInGebietshierarchie.put(gebiet, politischeGruppenZugelassen);
    } else {
      // Nicht alle sind zugelassen. Die Mengen sind unterschiedlich. Merke hier die Vereinigeungsmenge.
      politischeGruppenNichtZugelassen.addAll(politischeGruppenZugelassen);
      politischeGruppeMitDirektkandidatenInGebietshierarchie.put(gebiet, politischeGruppenNichtZugelassen);
    }
  }

  /**
   * Nur ZUGELASSENE Direktkandidaten werden berücksichtigt.
   */
  public boolean hatDirektkandidatInGebietshierarchie(PolitischeGruppe politischeGruppe, Gebietobject gebiet) {
    return getPolitischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie(gebiet).contains(politischeGruppe);
  }

  public Set<Direktkandidatur> getDirektkandidaturenInGebietshierarchie(PolitischeGruppe politischeGruppe, Gebietobject gebiet) {
    return getDirektkandidaturenInGebietshierarchie(gebiet).stream().filter(direktkandidatur -> direktkandidatur.getGruppe().equals(politischeGruppe)).collect(Collectors.toSet());
  }

  public Set<Liste> getListenInGebietshierarchie(PolitischeGruppe politischeGruppe, Gebietobject gebiet) {
    return getListenInGebietshierarchie(gebiet).stream().filter(liste -> liste.getGruppe().equals(politischeGruppe)).collect(Collectors.toSet());
  }

  private final Map<Gebietobject, Set<PolitischeGruppe>> politischeGruppeMitListenOderDirektkandidatenInGebietshierarchie = new HashMap<>();

  /**
   * Nur ZUGELASSENE Direktkandidaten werden berücksichtigt.
   */
  public Set<PolitischeGruppe> getPolitischeGruppeMitListenOderDirektkandidatenInGebietshierarchie(Gebietobject gebiet) {
    if (!politischeGruppeMitListenOderDirektkandidatenInGebietshierarchie.containsKey(gebiet)) {
      HashSet<PolitischeGruppe> politischeGruppes = new HashSet<>();
      politischeGruppeMitListenOderDirektkandidatenInGebietshierarchie.put(gebiet, politischeGruppes);
      politischeGruppes.addAll(getPolitischeGruppeMitListenInGebietshierarchie(gebiet));
      politischeGruppes.addAll(getPolitischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie(gebiet));
    }

    return politischeGruppeMitListenOderDirektkandidatenInGebietshierarchie.get(gebiet);
  }

  private Map<Gebietobject, List<Listenkandidatur>> listenkandidaturenAufGebiet = null;

  public List<Listenkandidatur> getListenkandidaturenAufGebiet(Gebietobject gebiet) {
    if (listenkandidaturenAufGebiet == null) {
      listenkandidaturenAufGebiet = new HashMap<>();

      for (Kandidatur kandidatur : getKandidaturen()) {
        if (kandidatur.isListenkandidatur()) {
          Listenkandidatur listenkandidatur = castTo(kandidatur, Listenkandidatur.class);
          Gebietobject gebietKandidatur = (Gebietobject) listenkandidatur.getGebiet();

          listenkandidaturenAufGebiet.computeIfAbsent(gebietKandidatur, key -> new ArrayList<>()).add(listenkandidatur);
        }
      }
    }

    return listenkandidaturenAufGebiet.getOrDefault(gebiet, emptyList());
  }

  private final Map<Gebietobject, Map<Wahlvorschlag, Long>> stimmzettelPositionenWahlvorschlaegeAufGebiet = new HashMap<>();

  public Map<Wahlvorschlag, Long> getStimmzettelPositionenZugelasseneWahlvorschlaegeAufGebiet(Gebietobject gebietobject) {
    if (!stimmzettelPositionenWahlvorschlaegeAufGebiet.containsKey(gebietobject)) {
      stimmzettelPositionenWahlvorschlaegeAufGebiet.put(gebietobject, bestimmeStimmzettelPositionenZugelasseneWahlvorschlaegeAufGebiet(gebietobject));
    }

    return stimmzettelPositionenWahlvorschlaegeAufGebiet.get(gebietobject);
  }

  private Map<Wahlvorschlag, Long> bestimmeStimmzettelPositionenZugelasseneWahlvorschlaegeAufGebiet(Gebietobject gebietobject) {
    Map<Wahlvorschlag, Long> result = new HashMap<>();

    for (Wahlvorschlag wahlvorschlag : getZugelasseneWahlvorschlaegeAufGebiet(gebietobject)) {
      result.put(wahlvorschlag, wahlvorschlag.getStimmzettelPosition(gebietobject, getEntityAufStimmzettelDaten()).getPosition());
    }

    return result;
  }

  private Map<Gebiet, List<Wahlvorschlag>> wahlvorschlaegeProGebiet;

  public Map<Gebiet, List<Wahlvorschlag>> getWahlvorschlaegeProGebiet() {
    if (wahlvorschlaegeProGebiet == null) {
      wahlvorschlaegeProGebiet = new HashMap<>();
      for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
        Gebiet gebiet = wahlvorschlag.getGebiet();
        if (!wahlvorschlaegeProGebiet.containsKey(gebiet)) {
          wahlvorschlaegeProGebiet.put(gebiet, new ArrayList<Wahlvorschlag>());
        }
        wahlvorschlaegeProGebiet.get(gebiet).add(wahlvorschlag);

      }

    }
    return wahlvorschlaegeProGebiet;
  }

  private Map<Gruppe, List<Wahlvorschlag>> wahlvorschlaegeProGruppe;

  public Map<Gruppe, List<Wahlvorschlag>> getWahlvorschlaegeProGruppe() {
    if (wahlvorschlaegeProGruppe == null) {
      wahlvorschlaegeProGruppe = new HashMap<>();
      for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {

        Gebiet gebiet = wahlvorschlag.getGebiet();

        PolitischeGruppe gruppe = wahlvorschlag.getGruppe();
        if (!wahlvorschlaegeProGruppe.containsKey(gruppe)) {
          wahlvorschlaegeProGruppe.put(gruppe, new ArrayList<Wahlvorschlag>());
        }
        wahlvorschlaegeProGruppe.get(gruppe).add(wahlvorschlag);
      }
    }
    return wahlvorschlaegeProGruppe;
  }

  private final Map<Gebietobject, List<Wahlvorschlag>> zugelasseneWahlvorschlaegeAufGebiet = new HashMap<>();

  public List<Wahlvorschlag> getZugelasseneWahlvorschlaegeAufGebiet(Gebietobject gebietobject) {

    if (!zugelasseneWahlvorschlaegeAufGebiet.containsKey(gebietobject)) {

      List<Wahlvorschlag> zugelasseneWahlvorschlaege = getWahlvorschlaege().stream()
          .filter(wahlvorschlag -> wahlvorschlag.isZugelassen() && gebietobject.isInGebietshierarchieVon(wahlvorschlag.getGebiet())).collect(Collectors.toList());

      zugelasseneWahlvorschlaegeAufGebiet.put(gebietobject, zugelasseneWahlvorschlaege);

    }

    return zugelasseneWahlvorschlaegeAufGebiet.get(gebietobject);
  }

  private EntityAufStimmzettelDatenFactory entityAufStimmzettelDatenFactory;

  public EntityAufStimmzettelDatenFactory getEntityAufStimmzettelDatenFactory() {
    if (entityAufStimmzettelDatenFactory == null) {
      entityAufStimmzettelDatenFactory = new EntityAufStimmzettelDatenVorgeladenFactory(getWahlParameterLoader(), getZulassungPruefer(), getStimmzettelFormatter(),
          getEntityAufStimmzettelDatenStimmzettelPositionBerechnung(), getStimmenPoolFactory(), getGruppeImGebietReihenfolgeVorsortierer(), getWahlvorschlaege());
    }

    return entityAufStimmzettelDatenFactory;
  }

  private EntityAufStimmzettelDaten entityAufStimmzettelDaten;

  public EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
    if (entityAufStimmzettelDaten == null) {
      entityAufStimmzettelDaten = getEntityAufStimmzettelDatenFactory().getEntityAufStimmzettelDaten();
    }
    return entityAufStimmzettelDaten;
  }

  public AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  public ZulassungPruefer getZulassungPruefer() {
    return zulassungPruefer;
  }

  public StimmzettelFormatter getStimmzettelFormatter() {
    return stimmzettelFormatter;
  }

  public EntityAufStimmzettelDatenStimmzettelPositionBerechnung getEntityAufStimmzettelDatenStimmzettelPositionBerechnung() {
    return entityAufStimmzettelDatenStimmzettelPositionBerechnung;
  }

  public StimmenPoolFactory getStimmenPoolFactory() {
    return ignore -> StimmenPool.createStimmenPoolFuerAlleStimmen(getStimmenVorperiode());
  }

  public GruppeImGebietReihenfolgeVorsortierer getGruppeImGebietReihenfolgeVorsortierer() {
    return gruppeImGebietReihenfolgeVorsortierer;
  }

  public List<Sitze> getSitzeVorperiodeFuerGebiet(Gebiet gebiet) {
    return sitzeVorperiodePool.getSitzeFuerGebiet(gebiet);
  }

  public List<Sitze> getSitzeVorperiodeFuerGebietUndDarunter(Gebiet gebiet) {
    return sitzeVorperiodePool.getSitzeFuerGebietUndDarunter(gebiet);
  }

  public Map<Gruppe, Long> getAnzahlSitzeVorperiodeFuerGebietUndDarunter(Gebiet gebiet) {
    return sitzeVorperiodePool.getAnzahlSitzeFuerGebietUndDarunter(gebiet);
  }

  public Collection<Sitze> getSitzeVorperiodeFuerPolitischeGruppe(PolitischeGruppe politischeGruppe) {
    return sitzeVorperiodePool.getSitzeFuerPolitischeGruppe(politischeGruppe);
  }

  public Sitze getSitzeVorperiodeFuerListe(Liste liste) {
    return sitzeVorperiodePool.getSitzeFuerListe(liste);
  }

  private Map<Gebiet, Set<Gruppe>> getGruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet() {
    initZugelasseneGruppenMaps();
    return gruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet;
  }

  private Map<Gebiet, Set<Gruppe>> getGruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet() {
    initZugelasseneGruppenMaps();
    return gruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet;
  }

  private Map<Gebiet, Set<Gruppe>> getGruppenMitZugelassenerListeInGebietshierarchieNachGebiet() {
    initZugelasseneGruppenMaps();
    return gruppenMitZugelassenerListeInGebietshierarchieNachGebiet;
  }

  public boolean isGruppeHatZugelasseneListeInGebietshierarchie(Gruppe gruppe, Gebiet gebiet) {
    return getGruppenMitZugelassenerListeInGebietshierarchieNachGebiet().getOrDefault(gebiet, Collections.emptySet()).contains(gruppe);
  }

  public boolean isGruppeHatZugelassenenDirektkandidatenInGebietshierarchie(Gruppe gruppe, Gebiet gebiet) {
    return getGruppenMitZugelassenemDirektkandidatInGebietshierarchieNachGebiet().getOrDefault(gebiet, Collections.emptySet()).contains(gruppe);
  }

  public boolean isGruppeHatZugelassenenWahlvorschlagInGebietshierarchie(Gruppe gruppe, Gebiet gebiet) {
    return getGruppenMitZugelassenemWahlvorschlagInGebietshierarchieNachGebiet().getOrDefault(gebiet, Collections.emptySet()).contains(gruppe);
  }

  public boolean isListenVorhanden() {
    return !getListen().isEmpty();
  }

  public boolean isDirektkandidatenVorhanden() {
    return !getDirektkandidaten().isEmpty();
  }

  @Nullable
  public Long getVersionsnummerStimmzettelerfassung(Gebietobject gebiet) {
    return getWertAusStimmzettelKopfdaten(gebiet, StimmzettelKopfdatumArt.VERSIONSNUMMER);
  }

  @Nullable
  protected Long getWertAusStimmzettelKopfdaten(Gebietobject gebiet, StimmzettelKopfdatumArt stimmzettelKopfdatumArt) {
    if (gebiet.isErfassungsgebiet()) {
      List<StimmzettelKopfdatum> stimmzettelKopfdaten = getStimmzettelKopfdaten(gebiet, stimmzettelKopfdatumArt);
      if (stimmzettelKopfdaten.size() == 1) {
        return stimmzettelKopfdaten.get(0).parseWertAsLong();
      }
      return 0L;
    }
    return null;
  }

  public List<Pruefkriterium> getPruefkriterien() {
    if (pruefkriterien == null) {
      pruefkriterien = getPruefkriterienLoader().load();
    }

    return pruefkriterien;
  }

  protected Loader<Pruefkriterium> getPruefkriterienLoader() {
    return pruefkriterienLoader;
  }

  public List<Wahlrechtspruefung> getWahlrechtspruefungen() {
    if (wahlrechtspruefungen == null) {
      wahlrechtspruefungen = getWahlrechtspruefungenLoader().load();
    }

    return wahlrechtspruefungen;
  }

  protected Loader<Wahlrechtspruefung> getWahlrechtspruefungenLoader() {
    return wahlrechtspruefungenLoader;
  }

  public List<WvpWarnung> getWvpWarnungen() {
    if (wvpWarnungen == null) {
      wvpWarnungen = getWvpWarnungenLoader().load();
    }

    return wvpWarnungen;
  }

  protected Loader<WvpWarnung> getWvpWarnungenLoader() {
    return wvpWarnungenLoader;
  }

  private Map<Wahlvorschlag, List<WahlvorschlagsPerson>> wahlvorschlagsPersonenNachWahlvorschlaegen;

  public Map<Wahlvorschlag, List<WahlvorschlagsPerson>> getWahlvorschlagsPersonenNachWahlvorschlaegen() {
    if (wahlvorschlagsPersonenNachWahlvorschlaegen == null) {
      wahlvorschlagsPersonenNachWahlvorschlaegen = new HashMap<>();
      for (WahlvorschlagsPerson wahlvorschlagsPerson : getWahlvorschlagsPersonen()) {
        if (!wahlvorschlagsPersonenNachWahlvorschlaegen.containsKey(wahlvorschlagsPerson.getWahlvorschlag())) {
          wahlvorschlagsPersonenNachWahlvorschlaegen.put(wahlvorschlagsPerson.getWahlvorschlag(), new ArrayList<>());
        }
        wahlvorschlagsPersonenNachWahlvorschlaegen.get(wahlvorschlagsPerson.getWahlvorschlag()).add(wahlvorschlagsPerson);
      }
    }
    return wahlvorschlagsPersonenNachWahlvorschlaegen;
  }

  private Map<Wahlvorschlag, List<WvpWarnung>> wvpWarnungenNachWahlvorschlaegen;

  public Map<Wahlvorschlag, List<WvpWarnung>> getWvpWarnungenNachWahlvorschlaegen() {
    if (wvpWarnungenNachWahlvorschlaegen == null) {
      wvpWarnungenNachWahlvorschlaegen = new HashMap<>();
      for (WvpWarnung wvpWarnung : getWvpWarnungen()) {
        if (!wvpWarnungenNachWahlvorschlaegen.containsKey(wvpWarnung.getWahlvorschlag())) {
          wvpWarnungenNachWahlvorschlaegen.put(wvpWarnung.getWahlvorschlag(), new ArrayList<>());
        }
        wvpWarnungenNachWahlvorschlaegen.get(wvpWarnung.getWahlvorschlag()).add(wvpWarnung);
      }
    }
    return wvpWarnungenNachWahlvorschlaegen;
  }
}
