
package de.ivu.elect.business.gruppeimgebiet.entity;

import static java.util.Collections.emptyList;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.persistence.Transient;

import de.ivu.elect.business.ergebnis.boundary.AbstractSitzeDAO;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.EntityMitGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeArt;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettel;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
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.ReferendumOption;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;

public abstract class AbstractGruppeImGebiet implements GruppeImGebietBase, EntityMitGruppe<Gruppe>, EntityMitGebiet, EntityAufStimmzettel {

  private static final long serialVersionUID = 1L;

  @Transient
  private transient List<GruppeImGebiet> untergeordneteGruppenImGebietCached = null;

  @Transient
  private transient Collection<Direktkandidat> direktkandidatenInUebergeordneterGebietshierarchieCached = null;

  @Transient
  private transient Collection<Liste> listenInUebergeordneterGebietshierarchieCached = null;

  @Transient
  private transient Collection<ReferendumOption> referendumOptionenInUebergeordneterGebietshierarchieCached = null;

  @Transient
  private transient Boolean hatWahlvorschlagInGebietshierarchieCached = null;

  @Transient
  private transient Boolean isListenStimmenVorperiodeImGebietOderDarunterVorhandenCached = null;

  @Transient
  private transient Boolean isDirektStimmenVorperiodeImGebietOderDarunterVorhandenCached = null;

  @Transient
  private transient Long anzahlListenStimmenVorperiodeKumuliertCached = null;

  @Transient
  private transient Long anzahlDirektStimmenVorperiodeKumuliertCached = null;

  /**
   * Hilfsvariable für das Anzeigen der fortlaufender Position in einer Reihenfolge.
   */
  @Transient
  private Long positionFortlaufend = null;

  /**
   * Hilfsvariable für das Überschreiben der Direktkandidat-vorhanden-Eigenschaft der Gruppe "Übrige".
   */
  @Transient
  private Boolean direktkandidatVorhandenAnzeigen = null;

  @Transient
  private Long anzahlListenStimmenVorperiodeKumuliertZusatz = 0L;

  @Transient
  private boolean listenStimmenVorperiodeZusatzVorhanden = false;

  @Transient
  private Long anzahlDirektStimmenVorperiodeKumuliertZusatz = 0L;

  @Transient
  private transient boolean direktStimmenVorperiodeZusatzVorhanden = false;

  public AbstractGruppeImGebiet() {
    super();
  }

  public boolean hasName() {
    return !isBlank(getName());
  }

  public boolean hasKurzname() {
    return !isBlank(getKurzname());
  }

  public boolean hasZusatzbezeichnung() {
    return !isBlank(getZusatzbezeichnung());
  }

  @Deprecated // teuer, greift mitunter auf getGruppeImGebietsByGruppe zu
  public String getNameAufGebiet() {
    if (hasName()) {
      return getName();
    }

    return getGruppe().getNameAufGebiet(getGebiet());
  }

  @Deprecated // teuer, greift mitunter auf getGruppeImGebietsByGruppe zu
  public String getKurznameAufGebiet() {
    if (hasKurzname()) {
      return getKurzname();
    }

    return getGruppe().getKurznameAufGebiet(getGebiet());
  }

  @Override
  public boolean hasGruppe() {
    return getGruppe() != null;
  }

  public boolean isRelativePositionGesetzt() {
    return getRelativePosition() != null;
  }

  public GruppeArt getGruppeArt() {
    return getGruppe().getGruppeArt();
  }

  public boolean hasUebergeordneteGruppeImGebiet() {
    return getUebergeordneteGruppeImGebiet() != null;
  }

  @Transient
  private transient GruppeImGebiet uebergeordneteGruppeImGebietCached = null;

  @Transient
  private transient boolean uebergeordneteGruppeImGebietErmittelt = false;

  public GruppeImGebiet getUebergeordneteGruppeImGebiet(Map<GruppeImGebietTK, GruppeImGebiet> gruppenImGebiet) {
    Gebiet uebergeordnetesGebiet = getGebiet().getUebergeordnetesGebiet();
    if (uebergeordnetesGebiet == null) {
      return null;
    }

    return gruppenImGebiet.get(new GruppeImGebietTK(uebergeordnetesGebiet.getGebietTK(), getGruppe().getGruppeTK()));
  }

  public GruppeImGebiet getUebergeordneteGruppeImGebiet(Collection<GruppeImGebiet> gruppenImGebiet) {
    Gebiet uebergeordnetesGebiet = getGebiet().getUebergeordnetesGebiet();

    if (uebergeordnetesGebiet == null) {
      return null;
    }

    for (GruppeImGebiet gruppeImGebiet : gruppenImGebiet) {
      if (gruppeImGebiet.getGruppe().equals(getGruppe()) && uebergeordnetesGebiet.equals(gruppeImGebiet.getGebiet())) {
        return gruppeImGebiet;
      }
    }

    return null;
  }

  @Deprecated // teure Operation, vermeiden(!)
  public List<GruppeImGebiet> getUntergeordneteGruppenImGebiet() {
    if (untergeordneteGruppenImGebietCached == null) {
      untergeordneteGruppenImGebietCached = getUntergeordneteGruppenImGebiet(getGruppe().getGruppenImGebiet());
    }
    return untergeordneteGruppenImGebietCached;
  }

  public List<GruppeImGebiet> getUntergeordneteGruppenImGebiet(Collection<GruppeImGebiet> gruppenImGebiet) {
    return getUntergeordneteGruppenImGebiet(mappeNachGruppeGebietSchluesselWennGleicheGruppe(gruppenImGebiet));
  }

  public List<GruppeImGebiet> getUntergeordneteGruppenImGebietInGebietshierarchie(Collection<GruppeImGebiet> gruppenImGebiet) {
    return getUntergeordneteGruppenImGebietInGebietshierarchie(mappeNachGruppeGebietSchluesselWennGleicheGruppe(gruppenImGebiet));
  }

  private Map<GruppeImGebietTK, GruppeImGebiet> mappeNachGruppeGebietSchluesselWennGleicheGruppe(Collection<GruppeImGebiet> gruppenImGebiet) {

    Map<GruppeImGebietTK, GruppeImGebiet> result = new HashMap<>(gruppenImGebiet.size() * 2 + 1);
    for (GruppeImGebiet gruppeImGebiet : gruppenImGebiet) {
      if (gruppeImGebiet.getGruppe().equals(getGruppe())) {
        result.put(gruppeImGebiet.getGruppeImGebietTK(), gruppeImGebiet);
      }
    }

    return result;
  }

  public List<GruppeImGebiet> getUntergeordneteGruppenImGebiet(Map<GruppeImGebietTK, GruppeImGebiet> gruppenImGebiet) {
    List<GruppeImGebiet> result = new ArrayList<>();

    for (Gebiet gebiet : getGebiet().getGebiets()) {
      GruppeImGebiet untergeodneteGruppeImGebiet = gruppenImGebiet.get(new GruppeImGebietTK(gebiet.getGebietTK(), getGruppe().getGruppeTK()));
      if (untergeodneteGruppeImGebiet != null) {
        result.add(untergeodneteGruppeImGebiet);
      }
    }
    return result;
  }

  public List<GruppeImGebiet> getUntergeordneteGruppenImGebietInGebietshierarchie(Map<GruppeImGebietTK, GruppeImGebiet> gruppenImGebiet) {
    List<GruppeImGebiet> result = new ArrayList<>();

    for (Gebiet gebiet : getGebiet().getGebiets()) {
      GruppeImGebiet untergeodneteGruppeImGebiet = gruppenImGebiet.get(new GruppeImGebietTK(gebiet.getGebietTK(), getGruppe().getGruppeTK()));
      if (untergeodneteGruppeImGebiet != null) {
        result.add(untergeodneteGruppeImGebiet);
        result.addAll(untergeodneteGruppeImGebiet.getUntergeordneteGruppenImGebietInGebietshierarchie(gruppenImGebiet));
      }
    }
    return result;
  }

  @Deprecated // teuer
  public List<GruppeImGebiet> getBenachbarteGruppenImGebiet() {
    List<GruppeImGebiet> result = new ArrayList<>();
    GruppeImGebiet gruppeImObergebiet = getUebergeordneteGruppeImGebiet();
    if (gruppeImObergebiet != null) {
      for (GruppeImGebiet gruppeImGebiet : gruppeImObergebiet.getUntergeordneteGruppenImGebiet()) {
        if (!gruppeImGebiet.equals(this)) {
          result.add(gruppeImGebiet);
        }
      }
    }
    return result;
  }

  /**
   * Liefert die Position der Gruppe Im Gebiet falls gesetzt, wenn nicht gesetzt, dann die Position der Gruppe im übergeordneten Gebiet
   */
  @Deprecated // verwendet "getUebergeordneteGruppeImGebiet"
  public Long getRelativePositionAbgeleitet() {
    if (getRelativePosition() != null) {
      return getRelativePosition();
    }

    GruppeImGebiet uebergeordneteGruppeImGebiet = getUebergeordneteGruppeImGebiet();
    if (uebergeordneteGruppeImGebiet == null) {
      return null;
    }
    return uebergeordneteGruppeImGebiet.getRelativePositionAbgeleitet();
  }

  public boolean isEinzelbewerber() {
    return getGruppe().isEinzelbewerber();
  }

  public boolean isParteiWaehlergruppeOderVereinigung() {
    return getGruppe().isParteiWaehlergruppeOderVereinigung();
  }

  public boolean isWaehlergruppe() {
    return getGruppe().isWaehlergruppe();
  }

  public boolean isPartei() {
    return getGruppe().isPartei();
  }

  public boolean isVereinigung() {
    return getGruppe().isVereinigung();
  }

  public boolean isReferendumVorlage() {
    return getGruppe().isReferendumVorlage();
  }

  public boolean isSystemGruppe() {
    return getGruppe().isSystemGruppe();
  }

  public boolean isPolitischeGruppe() {
    return getGruppe().isPolitischeGruppe();
  }

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

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

  @Transient
  private transient Boolean hatListeInGebietshierarchie;

  public boolean hatListeInGebietshierarchie(Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeNachGruppe) {
    if (hatListeInGebietshierarchie == null) {
      hatListeInGebietshierarchie = getGruppe().hatListeInGebietshierarchie(getGebiet(), alleWahlvorschlaegeNachGruppe);
    }
    return hatListeInGebietshierarchie;
  }

  @Deprecated
  // könnte langsam sein, Methode hatListeInGebietshierarchie(alleWahlvorschlaegeNachGruppe) nutzen
  public boolean hatListeInGebietshierarchie() {
    if (hatListeInGebietshierarchie == null) {
      hatListeInGebietshierarchie = hatListeInUebergeordneterGebietshierarchie() || hatListeInUntergeordneterGebietshierarchie();
    }
    return hatListeInGebietshierarchie;
  }

  protected boolean hatListeInUntergeordneterGebietshierarchie() {
    boolean result = hatListe();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hatListeInUntergeordneterGebietshierarchie()) {
          return true;
        }
      }
    }
    return result;
  }

  @Transient
  private transient Boolean hatZugelassenenDirektkandidatInGebietshierarchie;

  public boolean hatZugelassenenDirektkandidatInGebietshierarchie(Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeNachGruppe) {
    if (hatZugelassenenDirektkandidatInGebietshierarchie == null) {
      hatZugelassenenDirektkandidatInGebietshierarchie = getGruppe().hatZugelassenenDirektkandidatInGebietshierarchie(getGebiet(), alleWahlvorschlaegeNachGruppe);

    }
    return hatZugelassenenDirektkandidatInGebietshierarchie;
  }

  protected boolean hatDirektkandidatInUntergeordneterGebietshierarchie() {
    boolean result = hatDirektkandidat();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hatDirektkandidatInUntergeordneterGebietshierarchie()) {
          return true;
        }
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatListeZugelassen() {
    for (Liste liste : getListen()) {
      if (liste.isZugelassen()) {
        return true;
      }
    }
    return false;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatDirektkandidatZugelassen() {
    for (Direktkandidat direktkandidat : getDirektkandidaten()) {
      if (direktkandidat.isZugelassen()) {
        return true;
      }
    }
    return false;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatWahlvorschlagZugelassen() {
    return hatListeZugelassen() || hatDirektkandidatZugelassen() || hatReferendumOptionZugelassen();
  }

  //  /**
  //   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind!
  //   * Cf. ELECT-3141
  //   */
  //  public boolean hatListeZugelassenInGebietshierarchie() {
  //    return hatListeZugelassenInUebergeordneterGebietshierarchie() || hatListeZugelassenInUntergeordneterGebietshierarchie();
  //  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   * <p>
   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige Nachladen der Gebietshierarchie vermeiden
   */
  @Deprecated
  public boolean hatListeZugelassenInUebergeordneterGebietshierarchie() {
    for (Liste liste : getListenInUebergeordneterGebietshierarchie()) {
      if (liste.isZugelassen()) {
        return true;
      }
    }

    return false;
  }

  /**
   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige Nachladen der Gebietshierarchie vermeiden
   */
  @Deprecated // aufwendig, da über GruppeImGebiet, besser direkt über Listen
  public boolean hatListeAufStimmzettelInUebergeordneterGebietshierarchie() {
    for (Liste liste : getListenInUebergeordneterGebietshierarchie()) {
      if (liste.isAufStimmzettel()) {
        return true;
      }
    }

    return false;
  }

  @Transient
  private transient Boolean hatListeAufStimmzettelInUebergeordneterGebietshierarchie = null;

  @Transient
  private transient Boolean hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie = null;

  //  /**
  //   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind!
  //   * Cf. ELECT-3141
  //   */
  //  protected boolean hatListeZugelassenInUntergeordneterGebietshierarchie() {
  //    boolean result = hatListeZugelassen();
  //    if (!result) {
  //      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
  //        if (untergeordneteGruppeImGebiet.hatListeZugelassenInUntergeordneterGebietshierarchie()) {
  //          return true;
  //        }
  //      }
  //    }
  //    return result;
  //  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   * <p>
   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige Nachladen der Gebietshierarchie vermeiden
   */
  public boolean hatReferendumOptionZugelassenInUebergeordneterGebietshierarchie() {
    for (ReferendumOption referendumOption : getReferendumOptionenInUebergeordneterGebietshierarchie()) {
      if (Boolean.TRUE.equals(referendumOption.isZugelassen())) {
        return true;
      }
    }

    return false;
  }

  // NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige
  // Nachladen der Gebietshierarchie vermeiden
  @Deprecated // zu langsam, wegen uebergeordneter GruppeImGebiet
  public boolean hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie() {
    for (ReferendumOption referendumOption : getReferendumOptionenInUebergeordneterGebietshierarchie()) {
      if (referendumOption.isAufStimmzettel()) {
        return true;
      }
    }

    return false;
  }

  @Transient
  private transient Boolean hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie = null;

  public boolean hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie(Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege) {
    if (hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie == null) {
      Map<Gruppe, Collection<Wahlvorschlag>> gruppeCollectionMap = wahlvorschlaege.get();
      if (gruppeCollectionMap.isEmpty()) {
        return hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie(); // alter weg vornehmlich für testfälle...
      }
      hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie = getWahlvorschlaegeAusMap(gruppeCollectionMap).stream().filter(Wahlvorschlag::isReferendumOption)
          .filter(Wahlvorschlag::isZugelassen).filter(w -> w.getGruppe().equals(getGruppe())).filter(w -> w.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(getGebiet())).findFirst()
          .isPresent();
    }
    return hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie;

  }

  protected Collection<Wahlvorschlag> getWahlvorschlaegeAusMap(Map<Gruppe, Collection<Wahlvorschlag>> map) {
    Collection<Wahlvorschlag> result = map.get(getGruppe());
    if (result == null) {
      return Collections.emptyList();
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatDirektkandidatZugelassenInGebietshierarchie() {
    return hatDirektkandidatZugelassenInUebergeordneterGebietshierarchie() || hatDirektkandidatZugelassenInUntergeordneterGebietshierarchie();
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   * <p>
   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige Nachladen der Gebietshierarchie vermeiden
   */
  @Deprecated // zu aufwendig
  public boolean hatDirektkandidatZugelassenInUebergeordneterGebietshierarchie() {
    for (Direktkandidat direktkandidat : getDirektkandidatenInUebergeordneterGebietshierarchie()) {
      if (Boolean.TRUE.equals(direktkandidat.isZugelassen())) {
        return true;
      }
    }

    return false;
  }

  public boolean hatListeAufStimmzettelInUebergeordneterGebietshierarchie(Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> alleWahlvorschlaege) {
    if (hatListeAufStimmzettelInUebergeordneterGebietshierarchie == null) {
      Map<Gruppe, Collection<Wahlvorschlag>> gruppeCollectionMap = alleWahlvorschlaege.get();
      if (gruppeCollectionMap.isEmpty()) {
        return hatListeAufStimmzettelInUebergeordneterGebietshierarchie(); // alter weg vornehmlich für testfälle...
      }

      hatListeAufStimmzettelInUebergeordneterGebietshierarchie = getWahlvorschlaegeAusMap(gruppeCollectionMap).stream().filter(Wahlvorschlag::isListe).filter(Wahlvorschlag::isZugelassen)
          .filter(w -> w.getGruppe().equals(getGruppe())).filter(w -> w.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(getGebiet())).findFirst().isPresent();

    }
    return hatListeAufStimmzettelInUebergeordneterGebietshierarchie;
  }

  /**
   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige Nachladen der Gebietshierarchie vermeiden
   */
  @Deprecated // Aufwendig, da über GruppeImGebiet, besser direkt über Direktkandidaten
  public boolean hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie() {
    return getDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie() != null;
  }

  public boolean hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie(Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> alleWahlvorschlaege) {

    if (hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie == null) {
      Map<Gruppe, Collection<Wahlvorschlag>> gruppeCollectionMap = alleWahlvorschlaege.get();
      if (gruppeCollectionMap.isEmpty()) {
        return hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie(); // alter weg vornehmlich für testfälle...
      }
      hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie = getWahlvorschlaegeAusMap(gruppeCollectionMap).stream().filter(Wahlvorschlag::isDirektkandidat)
          .filter(Wahlvorschlag::isZugelassen).filter(w -> w.getGruppe().equals(getGruppe())).filter(w -> w.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(getGebiet())).findFirst()
          .isPresent();
    }

    return hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie;
  }

  @Nullable
  @Deprecated
  public Direktkandidat getDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie() {
    for (Direktkandidat direktkandidat : getDirektkandidatenInUebergeordneterGebietshierarchie()) {
      if (direktkandidat.isAufStimmzettel()) {
        return direktkandidat;
      }
    }
    return null;
  }

  @Transient
  private transient Direktkandidat direktkandidatAufStimmzettelInUebergeordneterGebietshierarchie = null;
  private transient boolean direktkandidatAufStimmzettelInUebergeordneterGebietshierarchieErmittelt = false;

  public Direktkandidat getDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie(Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege) {
    if (!direktkandidatAufStimmzettelInUebergeordneterGebietshierarchieErmittelt) {

      Map<Gruppe, Collection<Wahlvorschlag>> gruppeCollectionMap = wahlvorschlaege.get();
      if (gruppeCollectionMap.isEmpty()) {
        return getDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie(); // alter weg vornehmlich für testfälle...
      }
      direktkandidatAufStimmzettelInUebergeordneterGebietshierarchieErmittelt = true;
      direktkandidatAufStimmzettelInUebergeordneterGebietshierarchie = getWahlvorschlaegeAusMap(gruppeCollectionMap).stream().filter(Wahlvorschlag::isDirektkandidat)
          .filter(Wahlvorschlag::isAufStimmzettel).filter(w -> w.getGruppe().equals(getGruppe())).filter(w -> w.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(getGebiet()))
          .map(w -> w.castTo(Direktkandidat.class).getEntity()).findFirst().orElse(null);
    }
    return direktkandidatAufStimmzettelInUebergeordneterGebietshierarchie;

  }

  /**
   * NOTE, dass dies keine Listen betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated
  protected boolean hatDirektkandidatZugelassenInUntergeordneterGebietshierarchie() {
    boolean result = hatDirektkandidatZugelassen();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hatDirektkandidatZugelassenInUntergeordneterGebietshierarchie()) {
          return true;
        }
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated
  public boolean hatWahlvorschlagZugelassenInGebietshierarchie() {
    return hatWahlvorschlagZugelassenInUebergeordneterGebietshierarchie() || hatWahlvorschlagZugelassenInUntergeordneterGebietshierarchie();
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   * <p>
   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige Nachladen der Gebietshierarchie vermeiden
   */
  @Deprecated
  public boolean hatWahlvorschlagZugelassenInUebergeordneterGebietshierarchie() {
    return hatDirektkandidatZugelassenInUebergeordneterGebietshierarchie() || hatListeZugelassenInUebergeordneterGebietshierarchie()
        || hatReferendumOptionZugelassenInUebergeordneterGebietshierarchie();
  }

  //  /**
  //   * NOTE, dass die hier verwendeten Methoden GECACHTE Werte verwenden KÖNNEN. So lässt sich das zeitaufwändige
  //   * Nachladen der Gebietshierarchie vermeiden
  //   */
  //  @Deprecated
  //  public boolean hatWahlvorschlagAufStimmzettelInUebergeordneterGebietshierarchie() {
  //    return hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie() || hatListeAufStimmzettelInUebergeordneterGebietshierarchie()
  //        || hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie();
  //  }

  public boolean hatWahlvorschlagAufStimmzettelInUebergeordneterGebietshierarchie(Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege) {
    return hatDirektkandidatAufStimmzettelInUebergeordneterGebietshierarchie(wahlvorschlaege) || hatListeAufStimmzettelInUebergeordneterGebietshierarchie(wahlvorschlaege)
        || hatReferendumOptionAufStimmzettelInUebergeordneterGebietshierarchie(wahlvorschlaege);
  }

  public boolean hatWahlvorschlagAufStimmzettelInUebergeordneterGebietshierarchie(Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeNachGruppe) {
    for (Wahlvorschlag wahlvorschlag : alleWahlvorschlaegeNachGruppe.get(getGruppe())) {
      if (wahlvorschlag.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(getGebiet())) {
        return true;
      }
    }
    return false;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  protected boolean hatWahlvorschlagZugelassenInUntergeordneterGebietshierarchie() {
    boolean result = hatWahlvorschlagZugelassen();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hatWahlvorschlagZugelassenInUntergeordneterGebietshierarchie()) {
          return true;
        }
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatListeZugelassenOderUnbestimmt() {
    return getListen().stream().anyMatch(AbstractWahlvorschlag::isZugelassenOderUnbestimmt);
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatDirektkandidatZugelassenOderUnbestimmt() {
    return getDirektkandidaten().stream().anyMatch(AbstractWahlvorschlag::isZugelassenOderUnbestimmt);
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  private boolean hatReferendumOptionZugelassenOderUnbestimmt() {
    return getReferendumOptionen().stream().anyMatch(AbstractWahlvorschlag::isZugelassenOderUnbestimmt);
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  private boolean hatReferendumOptionZugelassen() {
    return getReferendumOptionen().stream().anyMatch(AbstractWahlvorschlag::isZugelassen);
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatWahlvorschlagZugelassenOderUnbestimmt() {
    return hatListeZugelassenOderUnbestimmt() || hatDirektkandidatZugelassenOderUnbestimmt() || hatReferendumOptionZugelassenOderUnbestimmt();
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated // teuer, besser mit allen Wahlvorschlaegen aufrufen
  public boolean hatListeZugelassenOderUnbestimmtInGebietshierarchie() {
    return hatListeZugelassenOderUnbestimmtInUebergeordneterGebietshierarchie() || hatListeZugelassenOderUnbestimmtInUntergeordneterGebietshierarchie();
  }

  public boolean hatListeZugelassenOderUnbestimmtInGebietshierarchie(Collection<Wahlvorschlag> alleWahlvorschlaegeDerWahl) {
    return alleWahlvorschlaegeDerWahl.stream().filter(wahlvorschlag -> wahlvorschlag.isListe() && wahlvorschlag.isZugelassenOderUnbestimmt())
        .anyMatch(wahlvorschlag -> wahlvorschlag.getGruppe().equals(getGruppe()) && wahlvorschlag.getGebiet().isInGebietshierarchieVon(getGebiet()));
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatListeZugelassenOderUnbestimmtInUebergeordneterGebietshierarchie() {
    boolean result = hatListeZugelassenOderUnbestimmt();
    if (!result) {
      GruppeImGebiet uebergeordneteGruppeImGebiet = getUebergeordneteGruppeImGebiet();
      if (uebergeordneteGruppeImGebiet != null) {
        return uebergeordneteGruppeImGebiet.hatListeZugelassenOderUnbestimmtInUebergeordneterGebietshierarchie();
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatListeZugelassenOderUnbestimmtInUntergeordneterGebietshierarchie() {
    boolean result = hatListeZugelassenOderUnbestimmt();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hatListeZugelassenOderUnbestimmtInUntergeordneterGebietshierarchie()) {
          return true;
        }
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated // teuer
  public boolean hatListeZugelassenOderUnbestimmtInGebietshierarchieVonBenachbartemGebiet() {
    return getBenachbarteGruppenImGebiet().stream().anyMatch(AbstractGruppeImGebiet::hatListeZugelassenOderUnbestimmtInGebietshierarchie);
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated // teuer besser mit allen Wahlvorschlägen aufrufen
  public boolean hatDirektkandidatZugelassenOderUnbestimmtInGebietshierarchie() {
    return hatDirektkandidatZugelassenOderUnbestimmtInUebergeordneterGebietshierarchie() || hatDirektkandidatZugelassenOderUnbestimmtInUntergeordneterGebietshierarchie();
  }

  public boolean hatDirektkandidatZugelassenOderUnbestimmtInGebietshierarchie(Collection<Wahlvorschlag> alleWahlvorschlaegeDerWahl) {
    return alleWahlvorschlaegeDerWahl.stream().filter(wahlvorschlag -> wahlvorschlag.isDirektkandidat() && wahlvorschlag.isZugelassenOderUnbestimmt())
        .anyMatch(wahlvorschlag -> wahlvorschlag.getGruppe().equals(getGruppe()) && wahlvorschlag.getGebiet().isInGebietshierarchieVon(getGebiet()));
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatDirektkandidatZugelassenOderUnbestimmtInUebergeordneterGebietshierarchie() {
    boolean result = hatDirektkandidatZugelassenOderUnbestimmt();
    if (!result) {
      GruppeImGebiet uebergeordneteGruppeImGebiet = getUebergeordneteGruppeImGebiet();
      if (uebergeordneteGruppeImGebiet != null) {
        return uebergeordneteGruppeImGebiet.hatDirektkandidatZugelassenOderUnbestimmtInUebergeordneterGebietshierarchie();
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  public boolean hatDirektkandidatZugelassenOderUnbestimmtInUntergeordneterGebietshierarchie() {
    boolean result = hatDirektkandidatZugelassenOderUnbestimmt();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hatDirektkandidatZugelassenOderUnbestimmtInUntergeordneterGebietshierarchie()) {
          return true;
        }
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated // teuer
  public boolean hatDirektkandidatZugelassenOderUnbestimmtInBenachbartemGebiet() {
    return getBenachbarteGruppenImGebiet().stream().anyMatch(AbstractGruppeImGebiet::hatDirektkandidatZugelassenOderUnbestimmt);
  }

  /**
   * NOTE, dass dies KEINE Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated // teuer
  public boolean hatWahlvorschlagZugelassenOderUnbestimmtInBenachbartemGebiet() {
    return getBenachbarteGruppenImGebiet().stream().anyMatch(AbstractGruppeImGebiet::hatWahlvorschlagZugelassenOderUnbestimmt);
  }

  @Transient
  private transient Boolean hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchieCache;

  public boolean hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchie(Map<PolitischeGruppe, List<Wahlvorschlag>> wahlvorschlageNachGruppe) {

    if (hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchieCache == null) {
      hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchieCache = false;
      List<Wahlvorschlag> wahlvorschlags = wahlvorschlageNachGruppe.get(getGruppe());
      if (wahlvorschlags != null) {
        for (Wahlvorschlag wahlvorschlag : wahlvorschlags) {
          if (wahlvorschlag.isZugelassenOderUnbestimmt()) {
            if (wahlvorschlag.getGebiet().isInGebietshierarchieVon(getGebiet())) {
              hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchieCache = true;
              break;
            }
          }
        }
      }
    }

    return hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchieCache;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  @Deprecated
  public boolean hatWahlvorschlagZugelassenOderUnbestimmtInGebietshierarchie() {
    return hasAnyUebergeordneteGruppeImGebietWahlvorschlagZugelassenOderUnbestimmt() || hasAnyUntergeordneteGruppeImGebietWahlvorschlagZugelassenOderUnbestimmt();
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  protected boolean hasAnyUebergeordneteGruppeImGebietWahlvorschlagZugelassenOderUnbestimmt() {
    boolean result = hatWahlvorschlagZugelassenOderUnbestimmt();
    if (!result) {
      GruppeImGebiet uebergeordneteGruppeImGebiet = getUebergeordneteGruppeImGebiet();
      if (uebergeordneteGruppeImGebiet != null) {
        return uebergeordneteGruppeImGebiet.hasAnyUebergeordneteGruppeImGebietWahlvorschlagZugelassenOderUnbestimmt();
      }
    }
    return result;
  }

  /**
   * NOTE, dass dies keine Wahlvorschläge betrachtet, die zwar auf dem Stimmzettel stehen, jedoch nicht zugelassen sind! Cf. ELECT-3141
   */
  protected boolean hasAnyUntergeordneteGruppeImGebietWahlvorschlagZugelassenOderUnbestimmt() {
    boolean result = hatWahlvorschlagZugelassenOderUnbestimmt();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hasAnyUntergeordneteGruppeImGebietWahlvorschlagZugelassenOderUnbestimmt()) {
          return true;
        }
      }
    }
    return result;
  }

  public boolean hatWahlvorschlag() {
    return !getWahlvorschlaege().isEmpty();
  }

  public boolean hatWahlvorschlagInGebietshierarchie() {
    if (hatWahlvorschlagInGebietshierarchieCached == null) {
      hatWahlvorschlagInGebietshierarchieCached = hasAnyUebergeordneteGruppeImGebietWahlvorschlag() || hasAnyUntergeordneteGruppeImGebietWahlvorschlag();
    }
    return hatWahlvorschlagInGebietshierarchieCached;
  }

  protected boolean hasAnyUebergeordneteGruppeImGebietWahlvorschlag() {
    boolean result = hatWahlvorschlag();

    if (!result) {
      GruppeImGebiet uebergeordneteGruppeImGebiet = getUebergeordneteGruppeImGebiet();
      if (uebergeordneteGruppeImGebiet != null) {
        return uebergeordneteGruppeImGebiet.hasAnyUebergeordneteGruppeImGebietWahlvorschlag();
      }
    }
    return result;
  }

  protected boolean hasAnyUntergeordneteGruppeImGebietWahlvorschlag() {
    boolean result = hatWahlvorschlag();
    if (!result) {
      for (GruppeImGebiet untergeordneteGruppeImGebiet : getUntergeordneteGruppenImGebiet()) {
        if (untergeordneteGruppeImGebiet.hasAnyUntergeordneteGruppeImGebietWahlvorschlag()) {
          return true;
        }
      }
    }
    return result;
  }

  public void setListenInUebergeordneterGebietshierarchie(Collection<Liste> listenInUebergeordneterGebietshierarchie) {
    this.listenInUebergeordneterGebietshierarchieCached = listenInUebergeordneterGebietshierarchie;
  }

  @Deprecated // nicht verwenden, zu langsam(!)
  public Collection<Liste> getListenInUebergeordneterGebietshierarchie() {
    if (listenInUebergeordneterGebietshierarchieCached == null) {
      listenInUebergeordneterGebietshierarchieCached = new ArrayList<>(getListen());

      if (getUebergeordneteGruppeImGebiet() != null) {
        listenInUebergeordneterGebietshierarchieCached.addAll(getUebergeordneteGruppeImGebiet().getListenInUebergeordneterGebietshierarchie());
      }
    }

    return listenInUebergeordneterGebietshierarchieCached;
  }

  @Deprecated // nicht verwenden, zu langsam(!)
  public Liste getListeAufStimmzettelInUebergeordneterGebietshierarchie() {
    Liste result = null;

    for (Liste liste : getListenInUebergeordneterGebietshierarchie()) {
      if (liste.isAufStimmzettel()) {
        if (result == null) {
          result = liste;
        } else {
          throw new IllegalStateException("Zur GruppeImGebiet [" + toString() + "] existieren mehrere Listen auf dem" + "Stimmzettel in der übergeordneten Gebietshierarchie; erwartet wurde eine.");
        }
      }
    }

    if (result != null) {
      return result;
    }

    throw new IllegalStateException("Zur GruppeImGebiet [" + toString() + "] existiert keine Liste auf dem Stimmzettel " + "in der übergeordneten Gebietshierarchie; erwartet wurde eine.");
  }

  public Liste getListeAufStimmzettelInUebergeordneterGebietshierarchie(Collection<Liste> alleListen) {
    for (Liste liste : alleListen) {
      if (liste.isAufStimmzettel() && getGruppe().equals(liste.getGruppe()) && getGebiet().isEqualOderUntergebietVon(liste.getGebiet())) {
        return liste;
      }
    }

    throw new IllegalStateException("Zur GruppeImGebiet [" + toString() + "] existiert keine Liste auf dem Stimmzettel " + "in der übergeordneten Gebietshierarchie; erwartet wurde eine.");
  }

  public @Nullable Liste getListeInUebergeordneterGebietshierarchie() {
    if (getListenInUebergeordneterGebietshierarchie().size() > 1) {
      throw new IllegalStateException(
          "Zur GruppeImGebiet [" + toString() + "] existieren " + getListenInUebergeordneterGebietshierarchie().size() + " Listen in der übergeordneten Gebietshierarchie, erwartet wurde 1.");
    }

    if (getListenInUebergeordneterGebietshierarchie().isEmpty()) {
      return null;
    }

    return getListenInUebergeordneterGebietshierarchie().iterator().next();
  }

  public boolean hatListeInUebergeordneterGebietshierarchie() {
    return !getListenInUebergeordneterGebietshierarchie().isEmpty();
  }

  public void setDirektkandidatenInUebergeordneterGebietshierarchie(Collection<Direktkandidat> direktkandidatenInUebergeordneterGebietshierarchie) {
    this.direktkandidatenInUebergeordneterGebietshierarchieCached = direktkandidatenInUebergeordneterGebietshierarchie;
  }

  @Deprecated // aufwendig
  public Collection<Direktkandidat> getDirektkandidatenInUebergeordneterGebietshierarchie() {
    if (direktkandidatenInUebergeordneterGebietshierarchieCached == null) {
      direktkandidatenInUebergeordneterGebietshierarchieCached = new ArrayList<>(getDirektkandidaten());

      if (getUebergeordneteGruppeImGebiet() != null) {
        direktkandidatenInUebergeordneterGebietshierarchieCached.addAll(getUebergeordneteGruppeImGebiet().getDirektkandidatenInUebergeordneterGebietshierarchie());
      }
    }

    return direktkandidatenInUebergeordneterGebietshierarchieCached;
  }

  public boolean hatDirektkandidatInUebergeordneterGebietshierarchie() {
    return !getDirektkandidatenInUebergeordneterGebietshierarchie().isEmpty();
  }

  public Set<Liste> getListen() {
    return getWahlvorschlaege().stream().filter(AbstractWahlvorschlag::isListe).map(wahlvorschlag -> wahlvorschlag.castTo(Liste.class).getEntity()).collect(Collectors.toSet());
  }

  public Set<Direktkandidat> getDirektkandidaten() {
    Set<Direktkandidat> result = new HashSet<>();

    for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
      if (wahlvorschlag.isDirektkandidat()) {
        result.add(wahlvorschlag.castTo(Direktkandidat.class).getEntity());
      }
    }

    return result;
  }

  public Set<ReferendumOption> getReferendumOptionen() {
    Set<ReferendumOption> result = new HashSet<>();

    for (Wahlvorschlag wahlvorschlag : getWahlvorschlaege()) {
      if (wahlvorschlag.isReferendumOption()) {
        result.add(wahlvorschlag.castTo(ReferendumOption.class).getEntity());
      }
    }

    return result;
  }

  public void setReferendumOptionenInUebergeordneterGebietshierarchie(Collection<ReferendumOption> referendumOptionenInUebergeordneterGebietshierarchie) {
    this.referendumOptionenInUebergeordneterGebietshierarchieCached = referendumOptionenInUebergeordneterGebietshierarchie;
  }

  public Collection<ReferendumOption> getReferendumOptionenInUebergeordneterGebietshierarchie() {
    if (referendumOptionenInUebergeordneterGebietshierarchieCached == null) {
      referendumOptionenInUebergeordneterGebietshierarchieCached = new ArrayList<>(getReferendumOptionen());

      if (getUebergeordneteGruppeImGebiet() != null) {
        referendumOptionenInUebergeordneterGebietshierarchieCached.addAll(getUebergeordneteGruppeImGebiet().getReferendumOptionenInUebergeordneterGebietshierarchie());
      }
    }

    return referendumOptionenInUebergeordneterGebietshierarchieCached;
  }

  public boolean hatReferendumOptionenInUebergeordneterGebietshierarchie() {
    return !getReferendumOptionenInUebergeordneterGebietshierarchie().isEmpty();
  }

  public boolean hasWahlvorschlaege() {
    return !getWahlvorschlaege().isEmpty();
  }

  public Set<Wahlvorschlag> getWahlvorschlaege() {
    return getWahlvorschlagsByGruppeImGebiet();
  }

  public void addWahlvorschlag(Wahlvorschlag wahlvorschlag) {
    addWahlvorschlagByGruppeImGebiet(wahlvorschlag);
  }

  public void removeWahlvorschlag(Wahlvorschlag wahlvorschlag) {
    removeWahlvorschlagByGruppeImGebiet(wahlvorschlag);
  }

  public void removeAllWahlvorschlaege() {
    removeAllWahlvorschlagByGruppeImGebiets();
  }

  @Transient
  private transient Set<Wahlvorschlag> wahlvorschlaegeAbgeleitet;

  public Set<Wahlvorschlag> getWahlvorschlaegeAbgeleitet(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    if (wahlvorschlaegeAbgeleitet == null) {
      wahlvorschlaegeAbgeleitet = new HashSet<>();

      for (Wahlvorschlag wahlvorschlag : alleWahlvorschlaege) {
        if (wahlvorschlag.getGruppeImGebiet().equals(this) || isUeberDieserGruppeImGebiet(wahlvorschlag)) {
          wahlvorschlaegeAbgeleitet.add(wahlvorschlag);
        }
      }
    }

    return wahlvorschlaegeAbgeleitet;
  }

  private boolean isUeberDieserGruppeImGebiet(Wahlvorschlag wahlvorschlag) {
    GruppeImGebiet other = wahlvorschlag.getGruppeImGebiet();

    return Objects.equals(getGruppe(), other.getGruppe()) && getGebiet().isUntergebietVon(other.getGebiet());
  }

  @Deprecated // nicht verwenden, Funktion sehr langsam
  public GruppeImGebiet getUebergeordneteGruppeImGebiet() {
    // Prüfung auf null funktioniert auf oberstem Gebiet nicht...
    if (!uebergeordneteGruppeImGebietErmittelt) {
      if (getGebiet().getUebergeordnetesGebiet() == null) {
        uebergeordneteGruppeImGebietCached = null;
      } else {
        Supplier<Map<Gebiet, GruppeImGebiet>> gruppeImGebietNachGebiet = getGruppe().getGruppeImGebietNachGebiet();
        if (gruppeImGebietNachGebiet != null) {
          uebergeordneteGruppeImGebietCached = gruppeImGebietNachGebiet.get().get(getGebiet().getUebergeordnetesGebiet());
        }
        if (uebergeordneteGruppeImGebietCached == null) {
          uebergeordneteGruppeImGebietCached = getUebergeordneteGruppeImGebiet(getGruppe().getGruppenImGebiet());
        }
      }
      uebergeordneteGruppeImGebietErmittelt = true;
    }
    return uebergeordneteGruppeImGebietCached;
  }

  @Transient
  private transient boolean wahlvorschlagInGebietshierarchieUebergeordnetErmittelt = false;

  @Transient
  private transient Wahlvorschlag wahlvorschlagInGebietshierarchieUebergeordnet;

  /**
   * @see AbstractGruppeImGebiet#getErstenDirektkandidatAbgeleitet
   * @see AbstractGruppeImGebiet#getErsteListeAbgeleitet
   */
  public Wahlvorschlag getErstenWahlvorschlagAbgeleitet(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    if (!wahlvorschlagInGebietshierarchieUebergeordnetErmittelt) {
      wahlvorschlagInGebietshierarchieUebergeordnetErmittelt = true;
      wahlvorschlagInGebietshierarchieUebergeordnet = findWahlvorschlagInGebietshierarchieUebergeordnet(entityAufStimmzettelDaten, Objects::nonNull).orElse(null);
    }

    return wahlvorschlagInGebietshierarchieUebergeordnet;
  }

  @Transient
  private transient boolean direktkandidatInGebietshierarchieUebergeordnetErmittelt = false;

  @Transient
  private transient Direktkandidat direktkandidatInGebietshierarchieUebergeordnet;

  public Direktkandidat getErstenDirektkandidatAbgeleitet(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    if (!direktkandidatInGebietshierarchieUebergeordnetErmittelt) {
      direktkandidatInGebietshierarchieUebergeordnetErmittelt = true;
      direktkandidatInGebietshierarchieUebergeordnet = findWahlvorschlagInGebietshierarchieUebergeordnet(entityAufStimmzettelDaten, Wahlvorschlag::isDirektkandidat)
          .map(wahlvorschlag -> wahlvorschlag.castTo(Direktkandidat.class).getEntity()).orElse(null);
    }

    return direktkandidatInGebietshierarchieUebergeordnet;
  }

  @Transient
  private transient boolean listeInGebietshierarchieUebergeordnetErmittelt = false;

  @Transient
  private transient Liste listeInGebietshierarchieUebergeordnet;

  public Liste getErsteListeAbgeleitet(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    if (!listeInGebietshierarchieUebergeordnetErmittelt) {
      listeInGebietshierarchieUebergeordnetErmittelt = true;
      listeInGebietshierarchieUebergeordnet = findWahlvorschlagInGebietshierarchieUebergeordnet(entityAufStimmzettelDaten, Wahlvorschlag::isListe)
          .map(wahlvorschlag -> wahlvorschlag.castTo(Liste.class).getEntity()).orElse(null);
    }

    return listeInGebietshierarchieUebergeordnet;
  }

  /**
   * Suche zunächst einen Wahlvorschlag meiner Gruppe in meinem Gebiet. Wird ein oder mehrere Wahlvorschläge gefunden, dann liefere einen beliebigen davon. Wenn nicht, dann setze die Suche analog beim
   * übergeordneten Gebiet fort usw. die Gebietshierarchie aufsteigend. Wird bis zum Wurzelgebiet nichts gefunden, antworte null/empty.
   *
   * @param wahlvorschlaegePerGrupppe
   *          eine vorgeladene Map mit allen Wahlvorschläge der Wahl nach Gruppe gruppiert
   * @param wahlvorschlagPredicate
   *
   * @return den ersten Wahlvorschlag meiner Gruppe in der Gebietshierarchie aufsteigend, bei meinem Gebiet beginnend
   */
  private Optional<Wahlvorschlag> findWahlvorschlagInGebietshierarchieUebergeordnet(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Predicate<Wahlvorschlag> wahlvorschlagPredicate) {
    List<Gebiet> gebietshierarchieUebergeordnet = getGebiet().getMitInGebietshierarchieUebergeordnetenGebietenAufsteigend();

    if (gebietshierarchieUebergeordnet.size() == 1) { // == getGebiet() -> Wurzelgebiet...
      Set<Wahlvorschlag> wahlvorschlaege = getWahlvorschlaege();

      for (Wahlvorschlag wahlvorschlag : wahlvorschlaege) {
        if (wahlvorschlag != null && wahlvorschlagPredicate.test(wahlvorschlag)) {
          return Optional.of(wahlvorschlag);
        }
      }
    } else {
      Collection<Wahlvorschlag> wahlvorschlaegeFuerGruppe = entityAufStimmzettelDaten.collectWahlvorschlaegeInWahlPerGruppe(getWahl()).getOrDefault(getGruppe(), emptyList());
      Collection<Wahlvorschlag> wahlvorschlaegeFuerGruppeInGebietshierarchieUebergeordnet = bestimmeWahlvorschlaegeDerGruppeInGebietshierachie(wahlvorschlaegeFuerGruppe);

      Map<Gebiet, List<Wahlvorschlag>> wahlvorschlaegeFuerGruppePerGebiet = wahlvorschlaegeFuerGruppeInGebietshierarchieUebergeordnet.stream().collect(Collectors.groupingBy(Wahlvorschlag::getGebiet));

      for (Gebiet gebiet : gebietshierarchieUebergeordnet) {
        List<Wahlvorschlag> wahlvorschlaege = wahlvorschlaegeFuerGruppePerGebiet.getOrDefault(gebiet, Collections.emptyList());

        for (Wahlvorschlag wahlvorschlag : wahlvorschlaege) {
          if (wahlvorschlag != null && wahlvorschlagPredicate.test(wahlvorschlag)) {
            return Optional.of(wahlvorschlag);
          }
        }
      }
    }

    return Optional.empty();
  }

  public Wahl getWahl() {
    if (getGebiet() != null && getGebiet().getWahl() != null) {
      return getGebiet().getWahl();
    }

    if (getGruppe() != null) {
      return getGruppe().getWahl();
    }

    return null; // passiert bei einer persistenten Entität nicht
  }

  public static Map<GruppeImGebietTK, GruppeImGebiet> mappeNachGruppeGebietSchluessel(Collection<GruppeImGebiet> gruppenImGebiet) {
    return GruppeImGebietTK.mappeGruppeImGebietNachTK(gruppenImGebiet);
  }

  public boolean hatErgebnisVorperiode(StimmenPool vorperiodenStimmen, AbstractSitzeDAO sitzeDAO) {
    return hatStimmenVorperiodeInGebietshierarchie(vorperiodenStimmen) || !getSitze(Ergebnisart.VORPERIODE, sitzeDAO).isEmpty();
  }

  public boolean isListenStimmenVorperiodeImGebietOderDarunterVorhanden(StimmenPool vorperiodenStimmen) {
    if (isListenStimmenVorperiodeImGebietOderDarunterVorhandenCached == null) {

      isListenStimmenVorperiodeImGebietOderDarunterVorhandenCached = listenStimmenVorperiodeZusatzVorhanden;

      if (vorperiodenStimmen.getListenstimmenDerGruppe(getGruppe()).stream().anyMatch(stimmen -> getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(stimmen.getGebiet()))) {
        isListenStimmenVorperiodeImGebietOderDarunterVorhandenCached = true;
      }
    }
    return isListenStimmenVorperiodeImGebietOderDarunterVorhandenCached;
  }

  public boolean isDirektStimmenVorperiodeImGebietOderDarunterVorhanden(StimmenPool vorperiodenStimmen) {
    if (isDirektStimmenVorperiodeImGebietOderDarunterVorhandenCached == null) {
      isDirektStimmenVorperiodeImGebietOderDarunterVorhandenCached = direktStimmenVorperiodeZusatzVorhanden;

      if (vorperiodenStimmen.getDirektstimmen(getGruppe()).stream().anyMatch(stimmen -> getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(stimmen.getGebiet()))) {
        isDirektStimmenVorperiodeImGebietOderDarunterVorhandenCached = true;
      }
    }
    return isDirektStimmenVorperiodeImGebietOderDarunterVorhandenCached;
  }

  @Deprecated // Nicht verwenden, wenn möglich Stimmenkumulierer verwenden...
  public Long getAnzahlListenStimmenVorperiodeKumuliert(StimmenPool vorperiodenStimmen) {
    if (anzahlListenStimmenVorperiodeKumuliertCached == null) {

      anzahlListenStimmenVorperiodeKumuliertCached = anzahlListenStimmenVorperiodeKumuliertZusatz;

      for (Stimmen stimmen : vorperiodenStimmen.getListenstimmenDerGruppe(getGruppe())) {
        if (isStimmenZaehlen(stimmen)) {
          anzahlListenStimmenVorperiodeKumuliertCached += stimmen.getAnzahl();
        }
      }
    }

    return anzahlListenStimmenVorperiodeKumuliertCached;
  }

  protected boolean isStimmenZaehlen(Stimmen stimmen) {
    // Stimmen des ausgewählten Gebietes zählen immer
    if (getGebiet().equals(stimmen.getGebiet())) {
      return true;
    }
    // Sonderfall Berlin Ost/West  WASDE-568
    if (!stimmen.getGebiet().isErgebnisRelevant()) {
      return false;
    }

    return stimmen.getGebiet().isInGebietshierarchieUntergeordnetesGebietVon(getGebiet());
  }

  @Deprecated // Nicht verwenden, wenn möglich Stimmenkumulierer verwenden...
  public Long getAnzahlDirektStimmenVorperiodeKumuliert(StimmenPool vorperiodenStimmen) {
    if (anzahlDirektStimmenVorperiodeKumuliertCached == null) {
      anzahlDirektStimmenVorperiodeKumuliertCached = anzahlDirektStimmenVorperiodeKumuliertZusatz;
      for (Stimmen stimmen : vorperiodenStimmen.getDirektstimmen(getGruppe())) {
        if (isStimmenZaehlen(stimmen)) {
          anzahlListenStimmenVorperiodeKumuliertCached += stimmen.getAnzahl();
        }
      }
    }

    return anzahlDirektStimmenVorperiodeKumuliertCached;
  }

  public boolean hatStimmenVorperiode(StimmenPool vorperiodenStimmen) {
    for (Stimmen stimmen : vorperiodenStimmen.getAlleStimmenDerGruppe(getGruppe())) {
      if (stimmen.getGebiet().isUntergebietVon(getGebiet())) {
        return true;
      }
    }
    return false;
  }

  public boolean hatStimmenVorperiodeInGebietshierarchie(StimmenPool vorperiodenStimmen) {
    for (Stimmen stimmen : vorperiodenStimmen.getAlleStimmenDerGruppe(getGruppe())) {
      if (stimmen.getGebiet().isInGebietshierarchieVon(getGebiet())) {
        return true;
      }
    }
    return false;
  }

  private Collection<Sitze> getSitze(Ergebnisart ergebnisart, AbstractSitzeDAO sitzeDAO) {
    return sitzeDAO.findByGruppeImGebietAndErgebnisart(getGruppe(), getGebiet(), ergebnisart);
  }

  @Deprecated
  // nur für Destatis "irgendwie" wichtig
  public boolean isIrrelevant(Collection<Wahlvorschlag> alleWahlvorschlaege, StimmenPool vorperiodeStimmen) {
    return !isRelevant(alleWahlvorschlaege, vorperiodeStimmen);
  }

  @Transient
  private transient Boolean relevant;

  @Deprecated
  // nur für Destatis "irgendwie" wichtig
  public boolean isRelevant(Collection<Wahlvorschlag> alleWahlvorschlaege, StimmenPool vorperiodeStimmen) {
    if (relevant == null) {
      relevant = bestimmeRelevant(alleWahlvorschlaege, vorperiodeStimmen);
    }
    return relevant;
  }

  private boolean bestimmeRelevant(Collection<Wahlvorschlag> alleWahlvorschlaege, StimmenPool vorperiodeStimmen) {

    // WASDE-546

    /**
     * Notwendige Bedingung ist, dass die Gruppen einen zulassungsfähigen Wahlvorschlag irgendwo in der Gebietshierarchie haben.
     */

    GruppeImGebietStatus status = getStatus(alleWahlvorschlaege);

    if (GruppeImGebietStatus.ZUGELASSEN.equals(status) || GruppeImGebietStatus.UNBESTIMMT.equals(status) || GruppeImGebietStatus.SYSTEMGRUPPE.equals(status)) {
      return true;
    }

    /**
     * Hinreichend: die Gruppe hat Vorperiodenstimmen im aktuellen Gebiet.
     */

    return isVorperiodenstimmenInGebietshierarchieVorhanden(vorperiodeStimmen);
  }

  protected boolean isVorperiodenstimmenInGebietshierarchieVorhanden(StimmenPool vorperiodeStimmen) {
    for (Stimmen stimmen : vorperiodeStimmen.getAlleStimmenDerGruppe(getGruppe())) {
      if (getGebiet().isInGebietshierarchieVon(stimmen.getGebiet())) {
        return true;
      }
    }
    return false;
  }

  public Long getPositionFortlaufend() {
    return positionFortlaufend;
  }

  public void setPositionFortlaufend(Long positionFortlaufend) {
    this.positionFortlaufend = positionFortlaufend;
  }

  public Boolean isDirektkandidatVorhandenAnzeigen() {
    return direktkandidatVorhandenAnzeigen;
  }

  public void setDirektkandidatVorhandenAnzeigen(Boolean direktkandidatVorhandenAnzeigen) {
    this.direktkandidatVorhandenAnzeigen = direktkandidatVorhandenAnzeigen;
  }

  /**
   * Hilfsmethode für "Übrige", da übrige die Listenstimmen der irrelevanten Kandidaturen zugerechnet bekommen.
   */
  public void setListenStimmenVorperiodeKumuliertZusatz(Long anzahlListenStimmenVorperiodeKumuliertZusatz, boolean listenStimmenVorperiodeZusatzVorhanden) {
    this.anzahlListenStimmenVorperiodeKumuliertZusatz = anzahlListenStimmenVorperiodeKumuliertZusatz;
    this.listenStimmenVorperiodeZusatzVorhanden = listenStimmenVorperiodeZusatzVorhanden;
  }

  /**
   * Hilfsmethode für "Übrige", da übrige die Direktstimmen der irrelevanten Kandidaturen zugerechnet bekommen.
   */
  public void setDirektStimmenVorperiodeKumuliertZusatz(Long anzahlDirektStimmenVorperiodeKumuliertZusatz, boolean direktStimmenVorperiodeZusatzVorhanden) {
    this.anzahlDirektStimmenVorperiodeKumuliertZusatz = anzahlListenStimmenVorperiodeKumuliertZusatz;
    this.direktStimmenVorperiodeZusatzVorhanden = listenStimmenVorperiodeZusatzVorhanden;
  }

  /**
   * @deprecated
   * @see AbstractWahlvorschlag#getStimmzettelPosition
   */
  @Deprecated
  public WahlvorschlagStimmzettelPosition getStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    return getStimmzettelPosition(getGebiet(), entityAufStimmzettelDaten);
  }

  /**
   * @deprecated
   * @see AbstractWahlvorschlag#getStimmzettelPosition
   */
  @Override
  @Deprecated
  public WahlvorschlagStimmzettelPosition getStimmzettelPosition(Gebiet gebiet, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    Wahlvorschlag wahlvorschlag = getErstenWahlvorschlagAbgeleitet(entityAufStimmzettelDaten);

    if (wahlvorschlag != null) {
      return wahlvorschlag.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten);
    }

    return entityAufStimmzettelDaten.calculateStimmzettelPosition(gebiet, (Wahlvorschlag) null);
  }

  @Transient
  private transient GruppeImGebietStatus statusCached = null;

  @Deprecated
  // Status an GruppeImGebiet ist nicht mehr sinnvoll => Status am Wahlvorschlag verwenden!
  public GruppeImGebietStatus getStatus(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    if (statusCached == null) {
      statusCached = bestimmeStatus(alleWahlvorschlaege);
    }
    return statusCached;
  }

  protected GruppeImGebietStatus bestimmeStatus(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    if (getGruppe().isSystemGruppe()) {
      return GruppeImGebietStatus.SYSTEMGRUPPE;
    }

    List<Wahlvorschlag> wahlvorschlaegeDerGruppeInGebietshierachie = bestimmeWahlvorschlaegeDerGruppeInGebietshierachie(alleWahlvorschlaege);

    GruppeImGebietStatus gruppeImGebietStatus = GruppeImGebietStatus.TRITT_NICHT_AN;

    for (Wahlvorschlag wahlvorschlag : wahlvorschlaegeDerGruppeInGebietshierachie) {
      if (wahlvorschlag.isUngeprueft()) {
        gruppeImGebietStatus = GruppeImGebietStatus.UNBESTIMMT;
      } else if (wahlvorschlag.isZugelassen()) {
        if (!GruppeImGebietStatus.UNBESTIMMT.equals(gruppeImGebietStatus)) {
          gruppeImGebietStatus = GruppeImGebietStatus.ZUGELASSEN;
        }
      } else if (wahlvorschlag.isNichtZugelassenImSinneAbgelehnt()) {
        if (GruppeImGebietStatus.TRITT_NICHT_AN.equals(gruppeImGebietStatus)) {
          gruppeImGebietStatus = GruppeImGebietStatus.NICHT_ZUGELASSEN;
        }
      }
    }

    return gruppeImGebietStatus;
  }

  @Transient
  private transient List<Wahlvorschlag> bestimmeWahlvorschlaegeDerGruppeInGebietshierachieCached;

  private List<Wahlvorschlag> bestimmeWahlvorschlaegeDerGruppeInGebietshierachie(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    if (alleWahlvorschlaege == null) {
      return Collections.emptyList();
    }

    if (bestimmeWahlvorschlaegeDerGruppeInGebietshierachieCached == null) {
      bestimmeWahlvorschlaegeDerGruppeInGebietshierachieCached = new ArrayList<>();

      for (Wahlvorschlag wahlvorschlag : alleWahlvorschlaege) {
        if (wahlvorschlag.getGruppe().equals(getGruppe()) && wahlvorschlag.getGebiet().isInGebietshierarchieVon(getGebiet())) {
          bestimmeWahlvorschlaegeDerGruppeInGebietshierachieCached.add(wahlvorschlag);
        }
      }
    }

    return bestimmeWahlvorschlaegeDerGruppeInGebietshierachieCached;
  }

  public boolean isStatusTrittNichtAn(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    return GruppeImGebietStatus.TRITT_NICHT_AN.equals(getStatus(alleWahlvorschlaege));
  }

  public boolean isStatusUnbestimmt(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    return GruppeImGebietStatus.UNBESTIMMT.equals(getStatus(alleWahlvorschlaege));
  }

  public boolean isStatusNichtZugelassen(Collection<Wahlvorschlag> alleWahlvorschlaege) {
    return GruppeImGebietStatus.NICHT_ZUGELASSEN.equals(getStatus(alleWahlvorschlaege));
  }

  @Transient
  private transient boolean zugehoerigerWahlvorschlagBestimmt = false;

  @Transient
  private transient Wahlvorschlag zugehoerigerWahlvorschlag;

  @Nullable
  public Wahlvorschlag getZugehoerigenWahlvorschlag(Supplier<Collection<Wahlvorschlag>> zuBeachtendeWahlvorschlaege) {
    if (!zugehoerigerWahlvorschlagBestimmt) {
      zugehoerigerWahlvorschlag = ermittleWahlvorschlag(zuBeachtendeWahlvorschlaege);
      zugehoerigerWahlvorschlagBestimmt = true;
    }

    return zugehoerigerWahlvorschlag;
  }

  private Wahlvorschlag ermittleWahlvorschlag(Supplier<Collection<Wahlvorschlag>> alleWahlvorschlaegeDerWahlSupplier) {
    if (getGruppe().isSystemGruppe()) {
      return null;
    }

    Wahlvorschlag direktAufGebiet = getZugehoerigenWahlvorschlagDirektAufGebiet(alleWahlvorschlaegeDerWahlSupplier);

    if (direktAufGebiet != null) {
      return direktAufGebiet;
    }

    Set<Wahlvorschlag> potentielleWahlvorschlaege = alleWahlvorschlaegeDerWahlSupplier.get().stream()
        .filter(wahlvorschlag -> wahlvorschlag.getGruppe().equals(this.getGruppe()) && this.getGebiet().isUntergebietVon(wahlvorschlag.getGebiet())).collect(Collectors.toSet());

    if (potentielleWahlvorschlaege.isEmpty()) {
      return null;
    }

    List<Wahlvorschlag> wahlvorschlaegeSortiert = new ArrayList<>(potentielleWahlvorschlaege);

    wahlvorschlaegeSortiert.sort((o1, o2) -> {
      if (o1.getGebiet().isUntergebietVon(o2.getGebiet())) {
        return -1;
      }

      if (o2.getGebiet().isUntergebietVon(o1.getGebiet())) {
        return 1;
      }

      // Listen bevorzugt
      if (o1.isListe() && !o2.isListe()) {
        return -1;
      }

      if (o2.isListe() && !o1.isListe()) {
        return 1;
      }

      return 0;
    });

    return wahlvorschlaegeSortiert.get(0);
  }

  @Transient
  private transient boolean zugehoerigenWahlvorschlagDirektAufGebietBestimmt = false;

  @Transient
  private transient Wahlvorschlag zugehoerigenWahlvorschlagDirektAufGebiet;

  @Nullable
  public Wahlvorschlag getZugehoerigenWahlvorschlagDirektAufGebiet(Supplier<Collection<Wahlvorschlag>> alleWahlvorschlaegeDerWahlSupplier) {
    if (!zugehoerigenWahlvorschlagDirektAufGebietBestimmt) {
      Collection<Wahlvorschlag> alleWahlvorschlaegeDerWahl = alleWahlvorschlaegeDerWahlSupplier.get();

      Set<Wahlvorschlag> direktAufGebiet = alleWahlvorschlaegeDerWahl.stream().filter(wahlvorschlag -> this.equals(wahlvorschlag.getGruppeImGebiet())).collect(Collectors.toSet());

      if (!direktAufGebiet.isEmpty()) {
        // Listen bevorzugt betrachten
        zugehoerigenWahlvorschlagDirektAufGebiet = direktAufGebiet.stream().filter(AbstractWahlvorschlag::isListe) // Listen bevorzugen
            .findFirst().orElse(direktAufGebiet.iterator().next()); // sonst irgendeinen

      }

      zugehoerigenWahlvorschlagDirektAufGebietBestimmt = true;
    }

    return zugehoerigenWahlvorschlagDirektAufGebiet;
  }
}
