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

import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;

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

import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.gebietsbaum.boundary.AbstractGebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.AbstractWahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByGebietsartAndPositionComparator;
import de.ivu.elect.business.stimmeingabe.boundary.AbstractErgebniseingangDAO;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangDAOHelper;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestatus;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsfehlerstatus;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.entity.EntityUtil;
import de.ivu.elect.jee.entity.customdata.CustomData;
import de.ivu.elect.presentation.security.AbstractGebietSecurity;

public abstract class AbstractWahldurchfuehrungsgebiet extends Gebiet implements WahldurchfuehrungsgebietBase {

  private static final long serialVersionUID = 1L;

  protected AbstractWahldurchfuehrungsgebiet() {
    super();
  }

  protected AbstractWahldurchfuehrungsgebiet(CustomData customData) {
    super(customData);
  }

  @Override
  public Wahldurchfuehrungsgebiet getWurzelgebiet() {
    return super.getWurzelgebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
  }

  private boolean hasWahl() {
    return getWahl() != null;
  }

  @Deprecated // HandleWithCare - cast sollte außerhalb der Entität erfolgen
  public Wahldurchfuehrung getWahldurchfuehrung() {
    if (!hasWahl()) {
      return null;
    }

    return getWahl().castTo(Wahldurchfuehrung.class).getEntity();
  }

  @Nullable
  @Deprecated // NICHT(!) verwenden, führt unter Umständen wieder zu: ELECT-2710 ?
  public Wahldurchfuehrungsgebiet getUebergeordnetesWahldurchfuehrungsgebiet() {
    Gebiet uebergeordnetesGebiet = getUebergeordnetesGebiet();

    return uebergeordnetesGebiet == null ? null : uebergeordnetesGebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
  }

  /**
   * @see #getGebieteSortiertNachPosition()
   */
  @Deprecated // NICHT(!) verwenden, führt unter Umständen wieder zu: ELECT-2710 ?
  public List<Wahldurchfuehrungsgebiet> getWahldurchfuehrungsgebieteSortiertNachPosition() {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();
    getUntergeordneteGebiete().forEach(g -> result.add(g.castTo(Wahldurchfuehrungsgebiet.class).getEntity()));

    result.sort(new GebietByGebietsartAndPositionComparator());

    return result;
  }

  //  public Ergebniseingang getLetzterEingang(WahlartErgebnisart wahlartErgebnisart) {
  //    if (wahlartErgebnisart != null) {
  //      if (wahlartErgebnisart.isVorlaeufig()) {
  //        return getLetzterEingangVorlaeufig();
  //      }
  //
  //      if (wahlartErgebnisart.isEndgueltig()) {
  //        return getLetzterEingangEndgueltig();
  //      }
  //    }
  //
  //    return null;
  //  }

  @Nullable
  public Ergebniseingang getLetzterErgebniseingangAktuell(AbstractErgebniseingangDAO ergebniseingangDAO, Ergebniseingangsart ergebniseingangsart) {
    return ErgebniseingangDAOHelper.from(ergebniseingangDAO)
        .findByNeuesterEingangByGebietUndErgebniseingangsart((Wahldurchfuehrungsgebiet) this, ergebniseingangsart);
  }

  @Nullable
  public Ergebniseingang getLetzterErgebniseingangAktuellInErgebnisUebernehmbar(AbstractErgebniseingangDAO ergebniseingangDAO, Ergebniseingangsart ergebniseingangsart) {
    return ErgebniseingangDAOHelper.from(ergebniseingangDAO)
        .findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingangsfehlerstatus((Wahldurchfuehrungsgebiet) this, ergebniseingangsart,
            Ergebniseingangsfehlerstatus.valuesInErgebnisUebernehmbar());
  }

  @Nullable
  public Ergebniseingang getLetzterErgebniseingangAktuellGueltig(AbstractErgebniseingangDAO ergebniseingangDAO, Ergebniseingangsart ergebniseingangsart) {
    return ErgebniseingangDAOHelper.from(ergebniseingangDAO)
        .findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingangsfehlerstatus((Wahldurchfuehrungsgebiet) this, ergebniseingangsart,
            Ergebniseingangsfehlerstatus.valuesGueltig());
  }

  @Nullable
  public Ergebniseingang getLetzterErgebniseingangInErgebniseingabestufe(AbstractErgebniseingangDAO ergebniseingangDAO, Ergebniseingangsart ergebniseingangsart,
                                                                         Ergebniseingabestufe ergebniseingabestufe) {
    return ErgebniseingangDAOHelper.from(ergebniseingangDAO)
        .findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufe((Wahldurchfuehrungsgebiet) this, ergebniseingangsart,
            ergebniseingabestufe);
  }

  @Nullable
  public Ergebniseingang getLetzterErgebniseingangInErgebniseingabestufeGueltig(AbstractErgebniseingangDAO ergebniseingangDAO, Ergebniseingangsart ergebniseingangsart,
                                                                                Ergebniseingabestufe ergebniseingabestufe) {
    return ErgebniseingangDAOHelper.from(ergebniseingangDAO)
        .findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufeAndErgebniseingangsfehlerstatus((Wahldurchfuehrungsgebiet) this,
            ergebniseingangsart, ergebniseingabestufe, Ergebniseingangsfehlerstatus.valuesGueltig());
  }

  // ELECT-2240
  @Nullable
  public Wahldurchfuehrungsgebiet getErstesGebietInHierarchieDasEingabeNichtZulaesst(WahlartErgebnisart ergebnisart, Ergebniseingangsherkunft ergebniseingangsherkunft,
                                                                                     BooleanSupplier eingangsherkunftIgnorieren) {
    if (getErgebniseingabestatus(ergebnisart).isEingabeMoeglich(ergebniseingangsherkunft, eingangsherkunftIgnorieren)) {
      if (hasUebergeordnetesGebiet()) {
        return getUebergeordnetesWahldurchfuehrungsgebiet().getErstesGebietInHierarchieDasEingabeNichtZulaesst(ergebnisart, ergebniseingangsherkunft,
            eingangsherkunftIgnorieren);
      }

      return null;
    }

    return (Wahldurchfuehrungsgebiet) this;
  }

  public boolean isEingabeMoeglich(WahlartErgebnisart ergebnisart, Ergebniseingangsherkunft ergebniseingangsherkunft, BooleanSupplier eingangsherkunftIgnorieren) {
    return getErstesGebietInHierarchieDasEingabeNichtZulaesst(ergebnisart, ergebniseingangsherkunft, eingangsherkunftIgnorieren) == null;
  }

  @Nullable
  public Wahldurchfuehrungsgebiet getErstesUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity) {
    if (isErfassungsgebiet() && gebietSecurity.isRechtVorhanden(this)) {
      return (Wahldurchfuehrungsgebiet) this;
    }

    return getGebieteSortiertNachPosition().stream()
        .map(untergebiet -> ((Wahldurchfuehrungsgebiet) untergebiet).getErstesUntergeordnetesErfassungsgebiet(gebietSecurity)).filter(Objects::nonNull)
        .findFirst().orElse(null);
  }

  private Long notNull(@Nullable Long zahl) {
    if (zahl == null) {
      return 0L;
    }

    return zahl;
  }

  public Ergebniseingabestatus getErgebniseingabestatus(Ergebnis ergebnis) {
    return getErgebniseingabestatus(ergebnis.getErgebnisart());
  }

  public Ergebniseingabestatus getErgebniseingabestatus(WahlartErgebnisart ergebnisart) {
    if (ergebnisart.isEndgueltig()) {
      return getEingabestatusEndgueltig();
    } else if (ergebnisart.isVorlaeufig()) {
      return getEingabestatusVorlaeufig();
    } else {
      throw new IllegalArgumentException("Unbekannte Ergebnisart:  " + ergebnisart);
    }
  }

  public void setErgebniseingabestatus(Ergebniseingabestatus ergebniseingabestatus, WahlartErgebnisart ergebnisart) {
    if (ergebnisart.isEndgueltig()) {
      setEingabestatusEndgueltig(ergebniseingabestatus);
    } else if (ergebnisart.isVorlaeufig()) {
      setEingabestatusVorlaeufig(ergebniseingabestatus);
    } else {
      throw new IllegalArgumentException("Unbekannte Ergebnisart:  " + ergebnisart);
    }
  }

  public Ergebniseingabestatus getErgebniseingabestatusZuWahlErgebnisart() {
    return getErgebniseingabestatus(getWahldurchfuehrung().getErgebniseingangsart().korrespondierendeErgebnisart());
  }

  public boolean hasPruefkennzeichen() {
    return isNotBlank(getPruefkennzeichen());
  }

  public String getPruefkennzeichen() {
    // ELECTBB-119, erste naive Implementierung mit Rückgriff auf ein existierendes (bei BB nicht anderweitig verwendetes) Feld.
    return getExterneId();
  }

  public boolean isAnzahlenWahlberechtigteErfassbar() {
    return isUrnenwahlgebiet();
  }

  public boolean hasAnyAnzahlWahlberechtigteGroesserNull() {
    return hasAnyAnzahlWahlberechtigte() && getAnzahlWahlberechtigterGesamtA1A2A3() > 0L;
  }

  public boolean hasAnyAnzahlWahlberechtigte() {
    return getAnzahlWahlberechtigterGesamtA1A2A3() != null;
  }

  public boolean hasAnzahlWahlberechtigteOhneWahlschein() {
    return getAnzahlWahlberechtigteOhneWahlschein() != null;
  }

  public Long getAnzahlWahlberechtigteOhneWahlscheinNotNull() {
    return notNull(getAnzahlWahlberechtigteOhneWahlschein());
  }

  public boolean hasAnzahlWahlberechtigteMitWahlschein() {
    return getAnzahlWahlberechtigteMitWahlschein() != null;
  }

  public Long getAnzahlWahlberechtigteMitWahlscheinNotNull() {
    return notNull(getAnzahlWahlberechtigteMitWahlschein());
  }

  public boolean hasAnzahlWahlberechtigteNichtImWaehlerverzeichnis() {
    return getAnzahlWahlberechtigteNichtImWaehlerverzeichnis() != null;
  }

  public Long getAnzahlWahlberechtigteNichtImWaehlerverzeichnisNotNull() {
    return notNull(getAnzahlWahlberechtigteNichtImWaehlerverzeichnis());
  }

  public Long getAnzahlWahlberechtigterA1A2NotNull() {
    return notNull(getAnzahlWahlberechtigterA1A2());
  }

  @Nullable
  public Long getAnzahlWahlberechtigterA1A2() {
    Long result = null;

    if (hasAnzahlWahlberechtigteOhneWahlschein() || hasAnzahlWahlberechtigteMitWahlschein()) {
      result = 0L;

      if (hasAnzahlWahlberechtigteOhneWahlschein()) {
        result += getAnzahlWahlberechtigteOhneWahlschein();
      }

      if (hasAnzahlWahlberechtigteMitWahlschein()) {
        result += getAnzahlWahlberechtigteMitWahlschein();
      }
    }

    return result;
  }

  public Long getAnzahlWahlberechtigterGesamtA1A2A3NotNull() {
    return notNull(getAnzahlWahlberechtigterGesamtA1A2A3());
  }

  @Nullable
  public Long getAnzahlWahlberechtigterGesamtA1A2A3() {
    Long result = null;

    if (hasAnzahlWahlberechtigteOhneWahlschein() || hasAnzahlWahlberechtigteMitWahlschein() || hasAnzahlWahlberechtigteNichtImWaehlerverzeichnis()) {
      result = 0L;

      if (hasAnzahlWahlberechtigteOhneWahlschein()) {
        result += getAnzahlWahlberechtigteOhneWahlschein();
      }

      if (hasAnzahlWahlberechtigteMitWahlschein()) {
        result += getAnzahlWahlberechtigteMitWahlschein();
      }

      if (hasAnzahlWahlberechtigteNichtImWaehlerverzeichnis()) {
        result += getAnzahlWahlberechtigteNichtImWaehlerverzeichnis();
      }
    }

    return result;
  }

  @Transient
  private transient Long anzahlWahlberechtigteOhneWahlscheinErfasstFuerCache;

  @javax.annotation.Nullable
  public java.lang.Long getAnzahlWahlberechtigteOhneWahlschein(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    if (anzahlWahlberechtigteOhneWahlscheinErfasstFuerCache == null) {
      anzahlWahlberechtigteOhneWahlscheinErfasstFuerCache = summiere(this, erfasstFuer, WahldurchfuehrungsgebietBase::getAnzahlWahlberechtigteOhneWahlschein);
    }
    return anzahlWahlberechtigteOhneWahlscheinErfasstFuerCache;
  }

  @Transient
  private transient Long anzahlWahlberechtigteMitWahlscheinErfasstFuerCache;

  @javax.annotation.Nullable
  public java.lang.Long getAnzahlWahlberechtigteMitWahlschein(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    if (anzahlWahlberechtigteMitWahlscheinErfasstFuerCache == null) {
      anzahlWahlberechtigteMitWahlscheinErfasstFuerCache = summiere(this, erfasstFuer, WahldurchfuehrungsgebietBase::getAnzahlWahlberechtigteMitWahlschein);
    }
    return anzahlWahlberechtigteMitWahlscheinErfasstFuerCache;
  }

  @Transient
  private transient Long anzahlWahlberechtigteNichtImWaehlerverzeichnisErfasstFuerCache;

  @javax.annotation.Nullable
  public java.lang.Long getAnzahlWahlberechtigteNichtImWaehlerverzeichnis(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    if (anzahlWahlberechtigteNichtImWaehlerverzeichnisErfasstFuerCache == null) {
      anzahlWahlberechtigteNichtImWaehlerverzeichnisErfasstFuerCache = summiere(this, erfasstFuer,
          WahldurchfuehrungsgebietBase::getAnzahlWahlberechtigteNichtImWaehlerverzeichnis);
    }
    return anzahlWahlberechtigteNichtImWaehlerverzeichnisErfasstFuerCache;
  }

  @Transient
  private transient Long anzahlWahlberechtigterGesamtA1A2A3NotNullErfasstFuerCache;

  public Long getAnzahlWahlberechtigterGesamtA1A2A3NotNull(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    if (anzahlWahlberechtigterGesamtA1A2A3NotNullErfasstFuerCache == null) {
      anzahlWahlberechtigterGesamtA1A2A3NotNullErfasstFuerCache = summiere(this, erfasstFuer,
          AbstractWahldurchfuehrungsgebiet::getAnzahlWahlberechtigterGesamtA1A2A3NotNull);
    }
    return anzahlWahlberechtigterGesamtA1A2A3NotNullErfasstFuerCache;
  }

  @Transient
  private transient Long anzahlWahlberechtigterGesamtA1A2A3ErfasstFuerCache;

  @Nullable
  public Long getAnzahlWahlberechtigterGesamtA1A2A3(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    if (anzahlWahlberechtigterGesamtA1A2A3ErfasstFuerCache == null) {
      anzahlWahlberechtigterGesamtA1A2A3ErfasstFuerCache = summiere(this, erfasstFuer, AbstractWahldurchfuehrungsgebiet::getAnzahlWahlberechtigterGesamtA1A2A3);
    }
    return anzahlWahlberechtigterGesamtA1A2A3ErfasstFuerCache;
  }

  @Transient
  private transient Long anzahlWahlberechtigterA1A2ErfasstFuerCache;

  public Long getAnzahlWahlberechtigterA1A2(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    if (anzahlWahlberechtigterA1A2ErfasstFuerCache == null) {
      anzahlWahlberechtigterA1A2ErfasstFuerCache = summiere(this, erfasstFuer, AbstractWahldurchfuehrungsgebiet::getAnzahlWahlberechtigterA1A2);
    }
    return anzahlWahlberechtigterA1A2ErfasstFuerCache;
  }

  public boolean hasAnyAnzahlWahlberechtigteGroesserNull(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return hasAnyAnzahlWahlberechtigte(erfasstFuer) && getAnzahlWahlberechtigterGesamtA1A2A3(erfasstFuer) > 0L;
  }

  public boolean hasAnyAnzahlWahlberechtigte(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return getAnzahlWahlberechtigterGesamtA1A2A3(erfasstFuer) != null;
  }

  public boolean hasAnzahlWahlberechtigteOhneWahlschein(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return getAnzahlWahlberechtigteOhneWahlschein(erfasstFuer) != null;
  }

  public Long getAnzahlWahlberechtigteOhneWahlscheinNotNull(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return notNull(getAnzahlWahlberechtigteOhneWahlschein(erfasstFuer));
  }

  public boolean hasAnzahlWahlberechtigteMitWahlschein(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return getAnzahlWahlberechtigteMitWahlschein(erfasstFuer) != null;
  }

  public Long getAnzahlWahlberechtigteMitWahlscheinNotNull(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return notNull(getAnzahlWahlberechtigteMitWahlschein(erfasstFuer));
  }

  public boolean hasAnzahlWahlberechtigteNichtImWaehlerverzeichnis(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return getAnzahlWahlberechtigteNichtImWaehlerverzeichnis(erfasstFuer) != null;
  }

  public Long getAnzahlWahlberechtigteNichtImWaehlerverzeichnisNotNull(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return notNull(getAnzahlWahlberechtigteNichtImWaehlerverzeichnis(erfasstFuer));
  }

  public Long getAnzahlWahlberechtigterA1A2NotNull(Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    return notNull(getAnzahlWahlberechtigterA1A2(erfasstFuer));
  }

  @Override
  public Wahldurchfuehrungsgebiet getErstesUebergeordnetesDirektwahlOderListenwahlOderWahlgebiet() {
    return (Wahldurchfuehrungsgebiet) super.getErstesUebergeordnetesDirektwahlOderListenwahlOderWahlgebiet();
  }

  @Transient
  private transient Boolean briefwahlInUrneErfassenInGebietOderDarunter = null;

  public void setBriefwahlInUrneErfassenInGebietOderDarunter(Boolean briefwahlInUrneErfassenInGebietOderDarunter) {
    this.briefwahlInUrneErfassenInGebietOderDarunter = briefwahlInUrneErfassenInGebietOderDarunter;
  }

  public boolean isBriefwahlInUrneErfassenInGebietOderDarunter() {
    if (briefwahlInUrneErfassenInGebietOderDarunter == null) {
      briefwahlInUrneErfassenInGebietOderDarunter = bestimmeBriefwahlInUrneErfassenInGebietOderDarunter();
    }

    return briefwahlInUrneErfassenInGebietOderDarunter;
  }

  protected boolean bestimmeBriefwahlInUrneErfassenInGebietOderDarunter() {
    if (Boolean.TRUE.equals(getBriefwahlInUrneErfassen())) {
      return true;
    }

    if (isErfassungsgebiet()) {
      return false; // Optimierung: Weiter runter geht es nicht => Untergebiete müssen nicht geladen werden
    }

    return getUntergeordneteGebiete().stream()
        .anyMatch(gebiet -> gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity().isBriefwahlInUrneErfassenInGebietOderDarunter());
  }

  @Transient
  private transient List<Wahldurchfuehrungsgebiet> erfassungsgebieteUnterhalb = null;

  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteUnterhalb(AbstractGebietDAO abstractGebietDAO) {
    if (erfassungsgebieteUnterhalb == null) {
      erfassungsgebieteUnterhalb = new ArrayList<>();

      if (isErfassungsgebiet()) {
        erfassungsgebieteUnterhalb.add((Wahldurchfuehrungsgebiet) this);
      }

      for (Gebiet gebiet : getUntergeordneteGebieteSortiertNachPosition(abstractGebietDAO)) {
        erfassungsgebieteUnterhalb.addAll(gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity().getErfassungsgebieteUnterhalb(abstractGebietDAO));
      }
    }

    return erfassungsgebieteUnterhalb;
  }

  @Deprecated // Variante mit DAO nutzen
  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteUnterhalb() {
    if (erfassungsgebieteUnterhalb == null) {
      erfassungsgebieteUnterhalb = new ArrayList<>();

      if (isErfassungsgebiet()) {
        erfassungsgebieteUnterhalb.add((Wahldurchfuehrungsgebiet) this);
      }

      for (Gebiet gebiet : getUntergeordneteGebieteSortiertNachPosition()) {
        erfassungsgebieteUnterhalb.addAll(gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity().getErfassungsgebieteUnterhalb());
      }
    }

    return erfassungsgebieteUnterhalb;
  }

  @Override
  public boolean isErfassungsgebiet() {
    // WASDE-616 ausbleibende Gebiete ignorieren
    return super.isErfassungsgebiet() && getErgebniseingabestatusZuWahlErgebnisart().isGebietZaehltWeiterAlsErfassungsgebiet();
  }

  public boolean isFreigegebenOderUebergeordnetesGebietFreigegeben(Ergebnis ergebnis) {
    return matchesAnyErgebniseingabestatusInUebergeordneterGebietshierarchie(ergebnis.getErgebnisart(), Ergebniseingabestatus::isFreigegeben);
  }

  public boolean matchesAnyErgebniseingabestatusInUebergeordneterGebietshierarchie(WahlartErgebnisart ergebnisart, Predicate<Ergebniseingabestatus> matcher) {
    return getInGebietshierarchieUebergeordneteGebieteMitGebietSelbst().stream() //
        .map(gebiet -> EntityUtil.castTo(gebiet, Wahldurchfuehrungsgebiet.class).getErgebniseingabestatus(ergebnisart)) //
        .anyMatch(matcher);
  }

  public boolean isWahlbeteiligungErfassungsgebiet() {
    return isAnzahlenWahlberechtigteErfassbar();
  }

  @Transient
  private transient Map<String, Object> additionalData;

  public void addAdditionalData(String key, Object data) {
    if (additionalData == null) {
      additionalData = new HashMap<>();
    }

    additionalData.put(key, data);
  }

  public Object getAdditionalData(String key) {
    if (additionalData == null) {
      return null;
    }

    return additionalData.get(key);
  }

  public void resetAWerte() {
    setAnzahlWahlberechtigteOhneWahlschein(null);
    setAnzahlWahlberechtigteMitWahlschein(null);
    setAnzahlWahlberechtigteNichtImWaehlerverzeichnis(null);
  }

  public Wahlart bestimmeWahlartBeiAufteilung() {
    return Optional.ofNullable(bestimmeWahlartBeiAufteilungOhneFallback()).orElse(getWahl().getWahlart());
  }

  @Nullable
  public Wahlart bestimmeWahlartBeiAufteilungOhneFallback() {
    if (getWahlartBeiAufteilung() != null) {
      return getWahlartBeiAufteilung();
    }

    if (getGebietsarteigenschaft() != null && getGebietsarteigenschaft().getWahlartBeiAufteilung() != null) {
      return getGebietsarteigenschaft().getWahlartBeiAufteilung();
    }

    return null;
  }

  public List<Wahldurchfuehrungsgebiet> getGebieteSortiertNachPosition(AbstractWahldurchfuehrungsgebietDAO gebietDAO) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>(gebietDAO.findByUebergeordnetesGebiet(this));
    result.sort(new GebietByGebietsartAndPositionComparator());

    return result;
  }

  private static Long summiere(AbstractWahldurchfuehrungsgebiet thiss, Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer, Function<AbstractWahldurchfuehrungsgebiet, Long> getter) {
    Long result = getter.apply(thiss);
    for (Wahldurchfuehrungsgebiet other : erfasstFuer.apply(thiss)) {
      Long resultOther = getter.apply(other);
      if (resultOther != null) {
        if (result == null) {
          result = resultOther;
        } else {
          result += resultOther;
        }
      }
    }
    return result;
  }

}
