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

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.hochrechnung.entity.WahlteilnehmerAnzahl;


import static java.util.Arrays.asList;

public class AnzahlWahlberechtigteFuerAlleGebieteGenerator {

  private static final Long MINIMUM_ANZAHL_NEU_ERZEUGTE_WAHLBERECHTIGTE = 100L;

  private final ZufaelligeAnzahlWahlberechtigteGenerator zufaelligeAnzahlWahlberechtigteGenerator;
  private RelativeGroesseZwischenZweiGebietenBestimmer relativeGroesseZwischenZweiGebietenBestimmer = (gebiet, zuGebiet) -> 1D;

  public AnzahlWahlberechtigteFuerAlleGebieteGenerator(ZufaelligeAnzahlWahlberechtigteGenerator zufaelligeAnzahlWahlberechtigteGenerator) {
    this.zufaelligeAnzahlWahlberechtigteGenerator = zufaelligeAnzahlWahlberechtigteGenerator;
  }

  public AnzahlWahlberechtigteFuerAlleGebieteGenerator(ZufaelligeAnzahlWahlberechtigteGenerator zufaelligeAnzahlWahlberechtigteGenerator, RelativeGroesseZwischenZweiGebietenBestimmer relativeGroesseZwischenZweiGebietenBestimmer) {
    this.zufaelligeAnzahlWahlberechtigteGenerator = zufaelligeAnzahlWahlberechtigteGenerator;
    this.relativeGroesseZwischenZweiGebietenBestimmer = relativeGroesseZwischenZweiGebietenBestimmer;
  }

  /*
  * Algorithmus von ELECT-9534
  * übernehme vorhandene A-Werte für alle Gebiete (Erfassungsgebiete & Obergebiete)
  * Obergebiete mit unvollständigen A-Werte müssen rausgelassen werden, weil diese A-Werte nur kummuliert sind und nicht die erwünschten Werte entsprechen.
  * Solange es Obergebiete mit Vorhandenen A-Werte gibt, verteile diese auf ihre untergeordnete (Erfassungs- bzw. Obergebiete). (Mit Abzug von vergebenen A-Werten)
  * Erzeuge für gebliebene Urnengebiete ohne A-Werte A-Werte.
  * Rechne die A-Werte für die Obergebiete (Addition von A-Werte der untergeordneten Gebiete)
  * Verteile (übrige) A2-Werte von einem Obergebiet auf seine Untergeordnete Briefgebiete ohne A-Werte.
  * Da Obergebiete mit nur untergeordneten Briefgebieten keine A-Werte in einer reiner Briefwahl ohne A-Werte, erzeuge für diese A-Werte (zufällig)
  * Verteile (übrige) A2-Werte von einem Obergebiet auf seine Untergeordnete Briefgebiete ohne A-Werte.
  * */
  public Map<Gebiet, WahlteilnehmerAnzahl> generatePseudeZahlenFuerBriefgebieteUndZufaelligeAnzahlWahlberechtigteFuerAlleGebieteOhneAnzahlWahlberechtigte(Collection<Gebiet> alleGebiete, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();

    Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet = getUntergeordneteGebietePerGebietMap(alleGebiete);

    Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitAWerteGrosserAlsNull = getGebieteMitEchtenAWertenGrosserAlsNull(vorhandeneWahlberechtigteProGebiet, untergeordneteGebietePerGebiet);
    putAllIfAbsent(gebieteMitAWerteGrosserAlsNull, result);

    Map<Gebiet, WahlteilnehmerAnzahl> aWerteFuerUntergeordneteGebieteOhneAWerteEinesObergebietesMitAWerten = getAWerteFuerUntergeordneteGebieteOhneAWerteEinesObergebietesMitAWerten(
        result, untergeordneteGebietePerGebiet);
    putAllIfAbsentAndHasMoreThanMinimum(aWerteFuerUntergeordneteGebieteOhneAWerteEinesObergebietesMitAWerten, result);

    Map<Gebiet, WahlteilnehmerAnzahl> aWerteFuerUrnengebieteOhneAWerteWoIhreGeschwistergebieteAWerteHaben = erzeugeAWerteFuerUrnengebieteOhneAWerteWoIhreGeschwistergebieteAWerteHaben(alleGebiete, untergeordneteGebietePerGebiet, result);
    putAllIfAbsent(aWerteFuerUrnengebieteOhneAWerteWoIhreGeschwistergebieteAWerteHaben, result);

    Map<Gebiet, WahlteilnehmerAnzahl> aWerteFuerUrnengebieteOhneAWerte = erzeugeAWerteFuerUrnengebieteOhneAWerte(alleGebiete, result);
    putAllIfAbsent(aWerteFuerUrnengebieteOhneAWerte, result);

    Map<Gebiet, WahlteilnehmerAnzahl> kommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte_ohneUebergeordneteGebieteMitNurBriefgebietenUnterSich = getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte_ohneUebergeordneteGebieteMitNurBriefgebietenUnterSich(
        result, untergeordneteGebietePerGebiet);
    putAllIfAbsent(kommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte_ohneUebergeordneteGebieteMitNurBriefgebietenUnterSich, result);

    Map<Gebiet, WahlteilnehmerAnzahl> pseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte = getPseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte(
        alleGebiete, result);
    putAllIfAbsentAndHasMoreThanMinimum(pseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte, result);

    Map<Gebiet, WahlteilnehmerAnzahl> aWerteFuerUebergeordneteGebieteMitNurBriefgebietenUnterSich = erzeugeAWerteFuerUebergeordneteGebieteMitNurBriefgebietenUnterSich(
        alleGebiete, untergeordneteGebietePerGebiet, result);
    putAllIfAbsent(aWerteFuerUebergeordneteGebieteMitNurBriefgebietenUnterSich, result);

    Map<Gebiet, WahlteilnehmerAnzahl> kommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte = getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte(
        result, untergeordneteGebietePerGebiet);
    putAllIfAbsent(kommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte, result);

    Map<Gebiet, WahlteilnehmerAnzahl> pseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte_FuerUebergeordneteGebieteMitNurBriefgebietenUnterSich = getPseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte(
        alleGebiete, result);
    putAllIfAbsent(pseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte_FuerUebergeordneteGebieteMitNurBriefgebietenUnterSich, result);

    return result;
  }

  private void putAllIfAbsentAndHasMoreThanMinimum(Map<Gebiet, WahlteilnehmerAnzahl> source, Map<Gebiet, WahlteilnehmerAnzahl> target) {
    source.forEach((gebiet, wahlberechtigte) -> {
      if (hasMoreThanMinimum(wahlberechtigte)) {
        target.putIfAbsent(gebiet, wahlberechtigte);
      }
    });
  }

  private boolean hasMoreThanMinimum(WahlteilnehmerAnzahl wahlberechtigte) {
    return wahlberechtigte.getGesamtNotNull() >= MINIMUM_ANZAHL_NEU_ERZEUGTE_WAHLBERECHTIGTE;
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getPseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigte(Collection<Gebiet> alleGebiete, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    return new BriefwahlgebietPseudoAnzahlWahlberechtigteGenerator()
        .generatePseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigteAnzahl(alleGebiete, gebieteMitWahlberechtigte, getRelativeGroesseZwischenZweiGebietenBestimmer());
  }

  private RelativeGroesseZwischenZweiGebietenBestimmer getRelativeGroesseZwischenZweiGebietenBestimmer() {
    return relativeGroesseZwischenZweiGebietenBestimmer;
  }

  protected static WahlteilnehmerAnzahl getUebrigeZuVerteilindeWahlberechtigteMitWahlschein(WahlteilnehmerAnzahl a, WahlteilnehmerAnzahl b) {
    WahlteilnehmerAnzahl result = subtractWahlberechtigteAnzahl(a,
        b);
    Set<Stimmenart> stimmenarten = result.getStimmenarten();
    stimmenarten.forEach(stimmenart -> {
      if (stimmenart != Stimmenart.MIT_WAHLSCHEIN) {
        result.setAnzahl(stimmenart, 0L);
      }
    });
    result.setAnzahl(Stimmenart.GESAMT, result.getAnzahl(Stimmenart.MIT_WAHLSCHEIN));
    return result;
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> erzeugeAWerteFuerUebergeordneteGebieteMitNurBriefgebietenUnterSich(Collection<Gebiet> alleGebiete, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    Set<Gebiet> gebieteMitNurBriefgebietenUnterSich = getUebergeordneteGebieteMitNurBriefgebietenUnterSich(untergeordneteGebietePerGebiet);
    gebieteMitNurBriefgebietenUnterSich.forEach(uebergeordnetesGebiet -> {
      WahlteilnehmerAnzahl wahlberechtigte = erzeugeAWerteFuerEinUebergeordnetesGebietMitNurBriefgebietenUnterSich(uebergeordnetesGebiet,
          untergeordneteGebietePerGebiet.get(uebergeordnetesGebiet), gebieteMitWahlberechtigte);
      result.put(uebergeordnetesGebiet, wahlberechtigte);
    });
    return result;
  }

  private WahlteilnehmerAnzahl erzeugeAWerteFuerEinUebergeordnetesGebietMitNurBriefgebietenUnterSich(Gebiet uebergeordnetesGebiet, Set<Gebiet> untergeordneteGebiete, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    Set<Gebiet> untergeordneteGebieteMitAWerte = untergeordneteGebiete.stream().filter(
        untergeordnetesGebiet -> gebieteMitWahlberechtigte.get(untergeordnetesGebiet) != null && gebieteMitWahlberechtigte.get(untergeordnetesGebiet)
            .hasAnyAnzahlWahlberechtigte())
        .collect(Collectors.toSet());

    WahlteilnehmerAnzahl summeWahlberechtigteVonUntergeordnetenGebietenMitAWerten = getSummeWahlberechtigteVonGebieten(untergeordneteGebieteMitAWerte, gebieteMitWahlberechtigte);

    long anzahlUntergeordneteGebieteOhneAWerte = untergeordneteGebiete.size() - untergeordneteGebieteMitAWerte.size();
    if (anzahlUntergeordneteGebieteOhneAWerte == 0) {
      return summeWahlberechtigteVonUntergeordnetenGebietenMitAWerten;
    }

    WahlteilnehmerAnzahl anzahlWahlberechtigteFuerUntergeordneteGebieteOhneAWerte = generateAnzahlWahlberechtigteFuerEinUebergeordnetesGebietMitNurBriefwahlgebieteAlsUntergeordneteGebiete(
        anzahlUntergeordneteGebieteOhneAWerte);

    return getSummeWahlberechtigte(summeWahlberechtigteVonUntergeordnetenGebietenMitAWerten, anzahlWahlberechtigteFuerUntergeordneteGebieteOhneAWerte);
  }

  private Set<Gebiet> getUebergeordneteGebieteMitNurBriefgebietenUnterSich(Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    Set<Gebiet> result = new LinkedHashSet<>();
    untergeordneteGebietePerGebiet.forEach((uebergeordnetesGebiet, untergeordneteGebiete) -> {
      if (isUebergeordneteGebieteMitNurBriefgebietenUnterSich(untergeordneteGebiete)) {
        result.add(uebergeordnetesGebiet);
      }
    });
    return result;
  }

  private boolean isUebergeordneteGebieteMitNurBriefgebietenUnterSich(Set<Gebiet> untergeordneteGebiete) {
    return untergeordneteGebiete != null && !untergeordneteGebiete.isEmpty() && untergeordneteGebiete.stream().allMatch(Gebiet::isBriefwahlgebiet);
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte(Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    return getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte(gebieteMitWahlberechtigte, untergeordneteGebietePerGebiet, true);
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte_ohneUebergeordneteGebieteMitNurBriefgebietenUnterSich(Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    return getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte(
        gebieteMitWahlberechtigte, untergeordneteGebietePerGebiet, false);
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getKommulierteAnzahlWahlberechtigteFuerUebergeordneteGebieteOhneAWerte(Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet, boolean isUebergeordneteGebieteMitNurBriefgebietenUnterSichZuBetrachten) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    gebieteMitWahlberechtigte.forEach(((gebiet, wahlberechtigte) -> {
      Gebiet uebergeordnetesGebiet = gebiet.getUebergeordnetesGebiet();
      if (uebergeordnetesGebiet != null && !gebieteMitWahlberechtigte.containsKey(uebergeordnetesGebiet)) {
        if (isUebergeordneteGebieteMitNurBriefgebietenUnterSichZuBetrachten || !isUebergeordneteGebieteMitNurBriefgebietenUnterSich(untergeordneteGebietePerGebiet.get(uebergeordnetesGebiet))) {
          addAnzahlWahlberechtigteFuerAlleVorfahren(gebiet, wahlberechtigte, result);
        }
      }
    }));
    return result;
  }
  private Map<Gebiet, WahlteilnehmerAnzahl> erzeugeAWerteFuerUrnengebieteOhneAWerteWoIhreGeschwistergebieteAWerteHaben(Collection<Gebiet> alleGebiete, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    alleGebiete.forEach(gebiet -> {
      if (isUrnengebietOhneAWerte(gebieteMitWahlberechtigte, gebiet)) {
        Set<Gebiet> geschwisterurnengebieteMitAWerten = getGeschwisterurnengebieteMitAWerten(untergeordneteGebietePerGebiet, gebiet, gebieteMitWahlberechtigte);
        if (!geschwisterurnengebieteMitAWerten.isEmpty()) {
          result.put(gebiet, berechneDurchschnittlicheAWerteVonGebieten(geschwisterurnengebieteMitAWerten, gebieteMitWahlberechtigte));
        }
      }
    });
    return result;
  }

  private WahlteilnehmerAnzahl berechneDurchschnittlicheAWerteVonGebieten(Set<Gebiet> gebieteMitAWerten, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    List<WahlteilnehmerAnzahl> aWerte = gebieteMitAWerten.stream().map(gebieteMitWahlberechtigte::get).collect(Collectors.toList());
    int anzahlElemente = aWerte.size();
    if (anzahlElemente <= 0) {
      return null;
    }
    WahlteilnehmerAnzahl summeAWerte = getSummeWahlberechtigteAnzahl(aWerte);
    WahlteilnehmerAnzahl result = new WahlteilnehmerAnzahl();
    summeAWerte.getStimmenarten().forEach(stimmenart -> {
      Long anzahlWahlberechtigte = summeAWerte.getAnzahl(stimmenart);
      if (anzahlWahlberechtigte != null) {
        result.setAnzahl(stimmenart, anzahlWahlberechtigte / anzahlElemente);
      }
    });
    return result;
  }

  private Set<Gebiet> getGeschwisterurnengebieteMitAWerten(Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet, Gebiet gebiet, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    Gebiet uebergeordnetesGebiet = gebiet.getUebergeordnetesGebiet();
    if (uebergeordnetesGebiet == null) {
      return new HashSet<>();
    }
    Set<Gebiet> geschwistergebiete = untergeordneteGebietePerGebiet.get(uebergeordnetesGebiet);
    if (geschwistergebiete == null) {
      return new HashSet<>();
    }
    return geschwistergebiete.stream()
        .filter(g -> !g.isBriefwahlgebiet() && hasAnyAnzahlWahlberechtigte(gebieteMitWahlberechtigte.get(g)))
        .collect(Collectors.toSet());
  }

  private boolean isUrnengebietOhneAWerte(Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte, Gebiet gebiet) {
    return gebiet.isErfassungsgebiet() && !gebiet.isBriefwahlgebiet() && !gebieteMitWahlberechtigte.containsKey(gebiet);
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> erzeugeAWerteFuerUrnengebieteOhneAWerte(Collection<Gebiet> alleGebiete, Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    alleGebiete.forEach(gebiet -> {
      if (isUrnengebietOhneAWerte(gebieteMitWahlberechtigte, gebiet)) {
        result.put(gebiet, generateAnzahlWahlberechtigteFuerUrnenAehnlichesGebiet());
      }
    });
    return result;
  }

  private void putAllIfAbsent(Map<Gebiet, WahlteilnehmerAnzahl> source, Map<Gebiet, WahlteilnehmerAnzahl> target) {
    source.forEach((target::putIfAbsent));
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getAWerteFuerUntergeordneteGebieteOhneAWerteEinesObergebietesMitAWerten(Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {

    Map<Gebiet, WahlteilnehmerAnzahl> updatedGebieteMitWahlberechtigte = new LinkedHashMap<>(gebieteMitWahlberechtigte);

    Queue<Gebiet> uebergeordneteGebieteMitAWertenZuVerteilen = gebieteMitWahlberechtigte.keySet().stream()
        .filter(gebiet -> isGebietMitVerbleibendenAWertenZuVerteilen(gebiet, updatedGebieteMitWahlberechtigte, untergeordneteGebietePerGebiet))
        .collect(Collectors.toCollection(LinkedList::new));

    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    
    while (!uebergeordneteGebieteMitAWertenZuVerteilen.isEmpty()) {
      Gebiet uebergeordnetesGebiet = uebergeordneteGebieteMitAWertenZuVerteilen.remove();
      WahlteilnehmerAnzahl wahlberechtigte = updatedGebieteMitWahlberechtigte.get(uebergeordnetesGebiet);
      Map<Gebiet, WahlteilnehmerAnzahl> aWerteFuerUntergeordneteGebieteOhneAWerte = getAWerteFuerUntergeordneteGebieteOhneAWerte(
          updatedGebieteMitWahlberechtigte, uebergeordnetesGebiet, wahlberechtigte, untergeordneteGebietePerGebiet.get(uebergeordnetesGebiet));
      result.putAll(aWerteFuerUntergeordneteGebieteOhneAWerte);
      updatedGebieteMitWahlberechtigte.putAll(aWerteFuerUntergeordneteGebieteOhneAWerte);
      
      // add uebergeordnete Gebiete mit A-Werte zu verteilen:
      aWerteFuerUntergeordneteGebieteOhneAWerte.keySet().forEach(gebiet -> {
        if (isGebietMitVerbleibendenAWertenZuVerteilen(gebiet, updatedGebieteMitWahlberechtigte, untergeordneteGebietePerGebiet)) {
          uebergeordneteGebieteMitAWertenZuVerteilen.add(gebiet);
        }
      });
    }

    return result;
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getAWerteFuerUntergeordneteGebieteOhneAWerte(Map<Gebiet, WahlteilnehmerAnzahl> gebieteMitWahlberechtigte, Gebiet uebergeordnetesGebiet, WahlteilnehmerAnzahl wahlberechtigteDesUebergeordnetenGebiets, Set<Gebiet> direktUntergeordneteGebiete) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    if (direktUntergeordneteGebiete == null || direktUntergeordneteGebiete.isEmpty() || !wahlberechtigteDesUebergeordnetenGebiets.hasAnyAnzahlWahlberechtigte()) {
      return result;
    }

    Set<Gebiet> untergeordneteGebieteMitAWerte = direktUntergeordneteGebiete.stream().filter(
        untergeordnetesGebiet -> gebieteMitWahlberechtigte.get(untergeordnetesGebiet) != null && gebieteMitWahlberechtigte.get(untergeordnetesGebiet)
            .hasAnyAnzahlWahlberechtigte())
        .collect(Collectors.toSet());

    WahlteilnehmerAnzahl summeWahlberechtigteVonUntergeordnetenGebieten = getSummeWahlberechtigteVonGebieten(untergeordneteGebieteMitAWerte, gebieteMitWahlberechtigte);
    WahlteilnehmerAnzahl uebrigeZuVerteilindeWahlberechtigte = subtractWahlberechtigteAnzahl(wahlberechtigteDesUebergeordnetenGebiets,
        summeWahlberechtigteVonUntergeordnetenGebieten);

    if (!uebrigeZuVerteilindeWahlberechtigte.hasAnyAnzahlWahlberechtigte()) {
      return result;
    }

    List<Gebiet> untergeordneteUrnenOderOberGebieteOhneAWerte = direktUntergeordneteGebiete.stream().filter(
        untergeordnetesGebiet -> !untergeordneteGebieteMitAWerte.contains(untergeordnetesGebiet) && !untergeordnetesGebiet.isBriefwahlgebiet())
        .collect(Collectors.toList());

    return verteileWahlberechtigteAufGebiete(uebrigeZuVerteilindeWahlberechtigte, untergeordneteUrnenOderOberGebieteOhneAWerte);
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> verteileWahlberechtigteAufGebiete(WahlteilnehmerAnzahl zuVerteilindeWahlberechtigte, List<Gebiet> gebiete) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();

    long anzahlGebiete = gebiete.size();

    gebiete.forEach(gebiet -> result.put(gebiet, divideAnzahlWahlberechtigteAufAnzahlGebieteUndLoeseTeilungsrestProblem(zuVerteilindeWahlberechtigte, anzahlGebiete)));
    return result;
  }

  private WahlteilnehmerAnzahl divideAnzahlWahlberechtigteAufAnzahlGebieteUndLoeseTeilungsrestProblem(WahlteilnehmerAnzahl zuVerteilindeWahlberechtigte, long anzahlGebiete) {
    WahlteilnehmerAnzahl result = new WahlteilnehmerAnzahl();

    for (Stimmenart stimmenart: asList(Stimmenart.OHNE_WAHLSCHEIN, Stimmenart.MIT_WAHLSCHEIN, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS, Stimmenart.GESAMT)) {
      long anzahlZuVerteilen = zuVerteilindeWahlberechtigte.getAnzahlNotNull(stimmenart);
      long teilungsrest = anzahlZuVerteilen % anzahlGebiete;
      long resultNachStimmenArt = anzahlZuVerteilen / anzahlGebiete + teilungsrest;

      zuVerteilindeWahlberechtigte.setAnzahl(stimmenart, anzahlZuVerteilen - teilungsrest);

      result.setAnzahl(stimmenart, resultNachStimmenArt);
    }

    return result;
  }

  private static WahlteilnehmerAnzahl subtractWahlberechtigteAnzahl(WahlteilnehmerAnzahl a, WahlteilnehmerAnzahl b) {
    // returns a - b
    if (a == null) {
      a = new WahlteilnehmerAnzahl();
    }
    if (b == null) {
      b = new WahlteilnehmerAnzahl();
    }

    WahlteilnehmerAnzahl result = new WahlteilnehmerAnzahl();
    for (Stimmenart stimmenart: asList(Stimmenart.OHNE_WAHLSCHEIN, Stimmenart.MIT_WAHLSCHEIN, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS, Stimmenart.GESAMT)) {
      long resultNachStimmenArt = a.getAnzahlNotNull(stimmenart) - b.getAnzahlNotNull(stimmenart);
      result.setAnzahl(stimmenart, Math.max(0, resultNachStimmenArt));
    }
    return result;
  }

  protected static WahlteilnehmerAnzahl getSummeWahlberechtigteVonGebieten(Set<Gebiet> betroffeneGebiete, Map<Gebiet, WahlteilnehmerAnzahl> aWertePerGebiet) {
    List<WahlteilnehmerAnzahl> wahlberechtigteVonBetroffenenGebieten = betroffeneGebiete.stream().map(aWertePerGebiet::get).filter(Objects::nonNull).collect(Collectors.toList());
    return getSummeWahlberechtigteAnzahl(wahlberechtigteVonBetroffenenGebieten);
  }

  private static WahlteilnehmerAnzahl getSummeWahlberechtigteAnzahl(List<WahlteilnehmerAnzahl> wahlberechtigte) {
    wahlberechtigte = wahlberechtigte.stream().filter(Objects::nonNull).collect(Collectors.toList());
    WahlteilnehmerAnzahl summe = new WahlteilnehmerAnzahl();
    for (Stimmenart stimmenart: asList(Stimmenart.OHNE_WAHLSCHEIN, Stimmenart.MIT_WAHLSCHEIN, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS, Stimmenart.GESAMT)) {
      long summeNachStimmenArt = getSummeWahlberechtigteNachStimmenart(stimmenart, wahlberechtigte);
      summe.setAnzahl(stimmenart, summeNachStimmenArt);
    }
    return summe;
  }

  private static long getSummeWahlberechtigteNachStimmenart(Stimmenart stimmenart, List<WahlteilnehmerAnzahl> wahlberechtigte) {
    return wahlberechtigte.stream().mapToLong(x -> x.getAnzahlNotNull(stimmenart)).sum();
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> getGebieteMitEchtenAWertenGrosserAlsNull(Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new LinkedHashMap<>();
    Set<Gebiet> gebieteMitEchtenAWerten = getGebieteMitEchtenAWerten(vorhandeneWahlberechtigteProGebiet, untergeordneteGebietePerGebiet);
    gebieteMitEchtenAWerten.forEach(gebiet -> result.put(gebiet, vorhandeneWahlberechtigteProGebiet.get(gebiet)));
    return result;
  }

  /**
   * Diese Methode ist nur zum Testen von getGebieteMitEchtenAWerten(Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) da.
   */
  protected Set<Gebiet> getGebieteMitEchtenAWerten(Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, List<Gebiet> alleGebiete) {
    Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet = getUntergeordneteGebietePerGebietMap(alleGebiete);
    return getGebieteMitEchtenAWerten(vorhandeneWahlberechtigteProGebiet, untergeordneteGebietePerGebiet);
  }

  private Set<Gebiet> getGebieteMitEchtenAWerten(Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    Set<Gebiet> result = new HashSet<>();

    Set<Gebiet> candidates = getGebieteMitAWerteGrosserAlsNull(vorhandeneWahlberechtigteProGebiet);

    // A-Werte von Erfassungsgebieten sind immer echt
    Set<Gebiet> erfassungsgebiete = extractErfassungsgebiete(candidates, untergeordneteGebietePerGebiet);
    result.addAll(erfassungsgebiete);
    candidates.removeAll(erfassungsgebiete);

    // Übergeordnete Gebiete, deren untergeordnete Gebiete alle A-Werte haben, können aufgenommen werden, weil ihre A-Werte echt und nicht kummuliert sind.
    // Solange es sich was ändert, muss die Berechnung erneut gemacht werden.
    Set<Gebiet> newGebieteMitEchtenAWerten;
    do {
      newGebieteMitEchtenAWerten = candidates.stream()
          .filter(gebiet -> isGebietMitEchtenAWerten(gebiet, result, vorhandeneWahlberechtigteProGebiet, untergeordneteGebietePerGebiet))
          .collect(Collectors.toSet());
      result.addAll(newGebieteMitEchtenAWerten);
      candidates.removeAll(newGebieteMitEchtenAWerten);

    } while (!newGebieteMitEchtenAWerten.isEmpty());

    return result;
  }

  private Set<Gebiet> getGebieteMitAWerteGrosserAlsNull(Map<Gebiet, WahlteilnehmerAnzahl> aWertePerGebiet) {
    return aWertePerGebiet.keySet().stream()
        .filter(gebiet -> hasAnyAnzahlWahlberechtigte(aWertePerGebiet.get(gebiet)))
        .collect(Collectors.toSet());
  }

  private boolean hasAnyAnzahlWahlberechtigte(WahlteilnehmerAnzahl anzahlWahlberechtigte) {
    return anzahlWahlberechtigte != null && anzahlWahlberechtigte.hasAnyAnzahlWahlberechtigte();
  }

  private boolean isGebietMitEchtenAWerten(Gebiet gebiet, Set<Gebiet> gebieteMitEchtenAWerten, Map<Gebiet, WahlteilnehmerAnzahl> aWertePerGebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    if (isErfassungsgebiet(gebiet, untergeordneteGebietePerGebiet)) {
      return true;
    }

    Set<Gebiet> untergeordneteGebiete = untergeordneteGebietePerGebiet.get(gebiet);
    if (gebieteMitEchtenAWerten.containsAll(untergeordneteGebiete)) {
      return true; // Fuer den Fall, dass A-Werte an Briefgebieten erfasst werden.
    }


    Set<Gebiet> untergeordneteGebieteOhneisBriefwahlgebiete = untergeordneteGebiete.stream().filter(g -> !g.isBriefwahlgebiet()).collect(Collectors.toSet());
    if (!untergeordneteGebieteOhneisBriefwahlgebiete.isEmpty() && gebieteMitEchtenAWerten.containsAll(untergeordneteGebieteOhneisBriefwahlgebiete)) {
      return true; // Fuer den Fall, dass A-Werte an Briefgebieten nicht erfasst werden.
    }

    if (isGebietMitVerbleibendenAWertenZuVerteilen(gebiet, aWertePerGebiet, untergeordneteGebietePerGebiet)) {
      return true;
    }

    return false;
  }

  private boolean isGebietMitVerbleibendenAWertenZuVerteilen(Gebiet gebiet, Map<Gebiet, WahlteilnehmerAnzahl> aWertePerGebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    WahlteilnehmerAnzahl aWerteVomGebiet = aWertePerGebiet.get(gebiet);
    if (!hasAnyAnzahlWahlberechtigte(aWerteVomGebiet)) {
      return false;
    }

    Set<Gebiet> untergeordneteGebiete = untergeordneteGebietePerGebiet.get(gebiet);
    if (isEmpty(untergeordneteGebiete)) {
      return false;
    }

    WahlteilnehmerAnzahl summeWahlberechtigteVonUntergeordnetenGebieten = getSummeWahlberechtigteVonGebieten(untergeordneteGebiete, aWertePerGebiet);
    WahlteilnehmerAnzahl uebrigeZuVerteilindeWahlberechtigte = subtractWahlberechtigteAnzahl(aWerteVomGebiet, summeWahlberechtigteVonUntergeordnetenGebieten);

    return uebrigeZuVerteilindeWahlberechtigte.hasAnyAnzahlWahlberechtigte();
  }

  private boolean isEmpty(Collection<?> objects) {
    return objects == null || objects.isEmpty();
  }

  private Set<Gebiet> extractErfassungsgebiete(Set<Gebiet> gebiete, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    return gebiete.stream()
        .filter(gebiet -> isErfassungsgebiet(gebiet, untergeordneteGebietePerGebiet))
        .collect(Collectors.toSet());
  }

  private boolean isErfassungsgebiet(Gebiet gebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    return !hasUntergeordneteGebiete(gebiet, untergeordneteGebietePerGebiet);
  }

  private boolean hasUntergeordneteGebiete(Gebiet gebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebietePerGebiet) {
    Set<Gebiet> untergeordneteGebiete = untergeordneteGebietePerGebiet.get(gebiet);
    return untergeordneteGebiete != null && !untergeordneteGebiete.isEmpty();
  }

  private Map<Gebiet, Set<Gebiet>> getUntergeordneteGebietePerGebietMap(Collection<Gebiet> alleGebiete) {
    Map<Gebiet, Set<Gebiet>> result = new LinkedHashMap<>();
    for (Gebiet gebiet: alleGebiete) {
      Gebiet uebergeordnetesGebiet = gebiet.getUebergeordnetesGebiet();
      if (uebergeordnetesGebiet != null) {
        result.putIfAbsent(uebergeordnetesGebiet, new LinkedHashSet<>());
        result.get(uebergeordnetesGebiet).add(gebiet);
      }
    }
    return result;
  }

  private WahlteilnehmerAnzahl generateAnzahlWahlberechtigteFuerEinUebergeordnetesGebietMitNurBriefwahlgebieteAlsUntergeordneteGebiete(Long anzahlUntergeordneteBriefgebieteVomGebiet) {
    return zufaelligeAnzahlWahlberechtigteGenerator.getZufaelligeAnzahlWahlberechtigteFuerEinUebergeordnetesGebietMitNurBriefwahlgebieteAlsUntergeordneteGebiete(anzahlUntergeordneteBriefgebieteVomGebiet);
  }

  private void addAnzahlWahlberechtigteFuerAlleVorfahren(Gebiet untergeordnetesGebiet, WahlteilnehmerAnzahl wahlberechtigteFuerErfassungsgebiet, Map<Gebiet, WahlteilnehmerAnzahl> result) {
    Gebiet uebergeordnetesGebiet = untergeordnetesGebiet.getUebergeordnetesGebiet();
    while (uebergeordnetesGebiet != null) {
      WahlteilnehmerAnzahl summeWahlberechtigte = getSummeWahlberechtigte(wahlberechtigteFuerErfassungsgebiet, result.get(uebergeordnetesGebiet));
      result.put(uebergeordnetesGebiet, summeWahlberechtigte);
      uebergeordnetesGebiet = uebergeordnetesGebiet.getUebergeordnetesGebiet();
    }
  }

  private WahlteilnehmerAnzahl getSummeWahlberechtigte(WahlteilnehmerAnzahl wahlberechtigte1, WahlteilnehmerAnzahl wahlberechtigte2) {
    return getSummeWahlberechtigteAnzahl(asList(wahlberechtigte1, wahlberechtigte2));
  }

  private WahlteilnehmerAnzahl generateAnzahlWahlberechtigteFuerUrnenAehnlichesGebiet() {
    return zufaelligeAnzahlWahlberechtigteGenerator.getZufaelligeAnzahlWahlberechtigteFuerUrnengebiet();
  }

}
