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

import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.internet.control.PolitischeGruppeUndGebietUndStimmabgabeart;
import de.ivu.elect.business.wahl.entity.UmgangGueltigeWerte;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

public class ErgebnisDatenStimmzettel extends AbstractErgebnisDatenGueltige {

  public static final SystemGruppe SYSTEM_GRUPPE_UNBESTIMMTE = SystemGruppe.UNGUELTIGE;

  private static final List<Stimmenart> TARGET_STIMMENARTEN_WAEHLER = asList(Stimmenart.GESAMT);

  public ErgebnisDatenStimmzettel(StimmergebnisDaten stimmergebnisDaten) {
    super(stimmergebnisDaten);
  }

  public long getStimmzettelAktuellGueltig(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzettel(gebietUndStimmabgabeart, cacheStimmzettelAktuellGueltig, bestimmeStimmzettelAktuellGueltig);
  }

  public long getStimmzettelAktuellUngueltig(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzettel(gebietUndStimmabgabeart, cacheStimmzettelAktuellUngueltig, bestimmeStimmzettelAktuellUngueltig);
  }

  public long getStimmzettelAktuellUnbestimmt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzettel(gebietUndStimmabgabeart, cacheStimmzettelAktuellUnbestimmt, bestimmeStimmzettelAktuellUnbestimmt);
  }

  public long getStimmzettelAktuellGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneWaehlerGesamt(gebietUndStimmabgabeart, cacheStimmzettelAktuellGesamt, bestimmeStimmzettelAktuellGesamt);
  }

  // A.k.a. "Stimmzettel für genau einen Wahlvorschlag unverändert"
  public long getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getStimmzettelAktuellGueltigeOderSummiereListen(gebietUndStimmabgabeart, Stimmenart.LISTENSTIMME_EINZELNER_WAHLVORSCHAG_UNVERAENDERT,
        this::getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert);
  }

  public long getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzetteleingabe(liste, gebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagUnveraendert,
        bestimmeStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagUnveraendert);
  }

  // A.k.a. "Stimmzettel für genau einen Wahlvorschlag mit Änderungen nur in diesem"
  public long getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getStimmzettelAktuellGueltigeOderSummiereListen(gebietUndStimmabgabeart, Stimmenart.LISTENSTIMME_EINZELNER_WAHLVORSCHAG_VERAENDERT,
        this::getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert);
  }

  public long getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzetteleingabe(liste, gebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagVeraendert,
        bestimmeStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagVeraendert);
  }

  // A.k.a. "Stimmzettel für genau einen Wahlvorschlag mit panaschierten Stimmen"
  public long getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getStimmzettelAktuellGueltigeOderSummiereListen(gebietUndStimmabgabeart, Stimmenart.LISTENSTIMME_EINZELNER_WAHLVORSCHAG_PANASCHIERT,
        this::getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert);
  }

  public long getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzetteleingabe(liste, gebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagPanaschiert,
        bestimmeStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagPanaschiert);
  }

  // A.k.a. "Stimmzettel für keinen einzelnen Wahlvorschlag aber mit Änderungen nur in genau einem Wahlvorschlag"
  public long getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getStimmzettelAktuellGueltigeOderSummiereListen(gebietUndStimmabgabeart, Stimmenart.LISTENSTIMME_KEIN_EINZELNER_WAHLVORSCHAG_VERAENDERT,
        this::getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert);
  }

  public long getStimmzettelAktuellKeinEinzelnerWahlvorschlagVeraendert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzetteleingabe(liste, gebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagVeraendert,
        bestimmeStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagVeraendert);
  }

  // A.k.a. "Stimmzettel für keinen einzelnen Wahlvorschlag mit panaschierten Stimmen"
  public long getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getStimmzettelAktuellGueltigeOderSummiereListen(gebietUndStimmabgabeart, Stimmenart.LISTENSTIMME_KEIN_EINZELNER_WAHLVORSCHAG_PANASCHIERT,
        this::getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert);
  }

  public long getStimmzettelAktuellKeinEinzelnerWahlvorschlagPanaschiert(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzetteleingabe(liste, gebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagPanaschiert,
        bestimmeStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagPanaschiert);
  }

  private long getStimmzettelAktuellGueltigeOderSummiereListen(GebietUndStimmabgabeart gebietUndStimmabgabeart, Stimmenart stimmenart, BiFunction<Liste, GebietUndStimmabgabeart, Long> listeFtc) {
    Long result = stimmenKumulierer.getSummeGueltigeStimmenAufGebiet(stimmenPoolAktuell, gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet(), stimmenart);

    if (result != null && result > 0L) {
      return result;
    }

    return stimmergebnisDaten.getListenInGebietshierarchie(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream() //
        .map(liste -> listeFtc.apply(liste, gebietUndStimmabgabeart)) //
        .filter(Objects::nonNull).mapToLong(Long::longValue).sum();
  }

  public long getStimmzettelVorperiodeGueltig(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzettel(gebietUndStimmabgabeart, cacheStimmzettelVorperiodeGueltig, bestimmeStimmzettelVorperiodeGueltig);
  }

  public long getStimmzettelVorperiodeUngueltig(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneStimmzettel(gebietUndStimmabgabeart, cacheStimmzettelVorperiodeUngueltig, bestimmeStimmzettelVorperiodeUngueltig);
  }

  public long getStimmzettelVorperiodeGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneWaehlerGesamt(gebietUndStimmabgabeart, cacheStimmzettelVorperiodeGesamt, bestimmeStimmzettelVorperiodeGesamt);
  }

  private long berechneWaehlerGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart, Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cache,
      BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettel) {
    return notNull(berechnePerStimmenart(gebietUndStimmabgabeart, Stimmenart.GESAMT, cache, bestimmeStimmzettel)); // ELECT-16022
  }

  private long berechneStimmzettel(GebietUndStimmabgabeart gebietUndStimmabgabeart, Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cache,
      BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettel) {
    Long result = null;

    for (Stimmenart stimmenart : TARGET_STIMMENARTEN_GUELTIGE) {
      Long summeStimmenart = berechnePerStimmenart(gebietUndStimmabgabeart, stimmenart, cache, bestimmeStimmzettel);

      /*
       * Naive Implementierung, um Wahlart-abhängiges Durchschleifen der erfassten Stimmenart zu umgehen!
       *
       * Die Anzahl Wähler GESAMT ist IMMER für jede Stimmenart gleich, AUCH wenn diese gar nicht in der Wahl erfasst wird. Da aber ggf. mit der Anzahl Wähler gesamt gerechnet wird, da Anzahl Wähler
       * gesamt == Anzahl Stimmzettel gesamt gilt, nimm immer die KLEINSTE berechnete Anzahl Stimmzettel größer null...
       */
      if (summeStimmenart != null && summeStimmenart > 0 && (result == null || result > summeStimmenart)) {
        result = summeStimmenart;
      }
    }

    if (result == null) {
      return 0L; // ELECTKOM-808
    }

    return Math.max(0L, result);
  }

  private long berechneStimmzetteleingabe(Wahlvorschlag wahlvorschlag, GebietUndStimmabgabeart gebietUndStimmabgabeart, Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cache,
      BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabe) {
    return notNull(berechnePerWahlvorschlag(gebietUndStimmabgabeart, wahlvorschlag, cache, bestimmeStimmzetteleingabe)); // ELECT-16022
  }

  private long ermittleWaehler(StimmenPool stimmenPool, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmenart stimmenart) {
    return stimmenKumulierer.getSummeWaehlerStimmenAufGebiet(stimmenPool, wahldurchfuehrungsgebiet, stimmenart);
  }

  @Nullable
  private Long ermittleStimmzettelGueltig(StimmenPool stimmenPool, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmenart stimmenart) {
    return UmgangGueltigeWerte.berechneStimmzettelGueltig(wahl, ergebnisart, wahldurchfuehrungsgebiet, berechneSummeWaehlerGesamt(stimmenPool), berechneSummeGueltige(stimmenPool, stimmenart),
        berechneSummeUngueltige(stimmenPool, stimmenart), berechneSummeUnbestimmte(stimmenPool, stimmenart));
  }

  @Nullable
  private Long ermittleStimmzettelUngueltig(StimmenPool stimmenPool, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmenart stimmenart) {
    return UmgangGueltigeWerte.berechneStimmzettelUngueltig(wahl, ergebnisart, wahldurchfuehrungsgebiet, berechneSummeWaehlerGesamt(stimmenPool), berechneSummeGueltige(stimmenPool, stimmenart),
        berechneSummeUngueltige(stimmenPool, stimmenart), berechneSummeUnbestimmte(stimmenPool, stimmenart));
  }

  @Nullable
  private Long ermittleStimmzettelUnbestimmt(StimmenPool stimmenPool, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmenart stimmenart) {
    return UmgangGueltigeWerte.berechneStimmzettelUnbestimmt(wahl, ergebnisart, wahldurchfuehrungsgebiet, berechneSummeWaehlerGesamt(stimmenPool), berechneSummeGueltige(stimmenPool, stimmenart),
        berechneSummeUngueltige(stimmenPool, stimmenart), berechneSummeUnbestimmte(stimmenPool, stimmenart));
  }

  private Function<Gebiet, Long> berechneSummeUnbestimmte(StimmenPool stimmenPool, Stimmenart stimmenart) {
    if (stimmenart.isListenstimme()) { // ELECT-13792, LISTENSTIMME -> LISTENSTIMME_UNBESTIMMT...
      return gebiet -> stimmenKumulierer.bestimmeSummeSystemGruppenStimmenAufGebiet(stimmenPool, gebiet, SYSTEM_GRUPPE_UNBESTIMMTE, Stimmenart.LISTENSTIMME_UNBESTIMMT);
    }

    return ignored -> 0L;
  }

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelAktuellGesamt = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelAktuellGesamt = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleWaehler(stimmenPoolAktuell, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelAktuellGesamt, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_WAEHLER;
    }
  };

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagUnveraendert = new HashMap<>();

  private final BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagUnveraendert = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Wahlvorschlag wahlvorschlag, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      // ELECT-9233
      return stimmenKumulierer.getSummeListenstimmenEinzelnerWahlvorschlagUnveraendertFuerPolitischeGruppeAufGebietUndDarunter(wahlvorschlag.getGruppe(), wahldurchfuehrungsgebiet, stimmenPoolAktuell);
    }

    @Override
    public Map<Wahlvorschlag, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return berechnePerWahlvorschlag(politischeGruppeUndGebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagUnveraendert, this);
    }

    @Override
    public List<Wahlvorschlag> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return new ArrayList<>(stimmergebnisDaten.getAlleWahlvorschlaegeProGruppe().get(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe()));
    }
  };

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagVeraendert = new HashMap<>();

  private final BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagVeraendert = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Wahlvorschlag wahlvorschlag, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeListenstimmenEinzelnerWahlvorschlagVeraendertFuerPolitischeGruppeAufGebietUndDarunter(wahlvorschlag.getGruppe(), wahldurchfuehrungsgebiet, stimmenPoolAktuell);
    }

    @Override
    public Map<Wahlvorschlag, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return berechnePerWahlvorschlag(politischeGruppeUndGebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagVeraendert, this);
    }

    @Override
    public List<Wahlvorschlag> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return new ArrayList<>(stimmergebnisDaten.getAlleWahlvorschlaegeProGruppe().get(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe()));
    }
  };

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagPanaschiert = new HashMap<>();

  private final BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagPanaschiert = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Wahlvorschlag wahlvorschlag, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeListenstimmenEinzelnerWahlvorschlagPanaschiertFuerPolitischeGruppeAufGebietUndDarunter(wahlvorschlag.getGruppe(), wahldurchfuehrungsgebiet, stimmenPoolAktuell);
    }

    @Override
    public Map<Wahlvorschlag, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return berechnePerWahlvorschlag(politischeGruppeUndGebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellNurEinzelnerWahlvorschlagPanaschiert, this);
    }

    @Override
    public List<Wahlvorschlag> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return new ArrayList<>(stimmergebnisDaten.getAlleWahlvorschlaegeProGruppe().get(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe()));
    }
  };

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cacheStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagVeraendert = new HashMap<>();

  private final BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagVeraendert = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Wahlvorschlag wahlvorschlag, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeListenstimmenKeinEinzelnerWahlvorschlagVeraendertFuerPolitischeGruppeAufGebietUndDarunter(wahlvorschlag.getGruppe(), wahldurchfuehrungsgebiet,
          stimmenPoolAktuell);
    }

    @Override
    public Map<Wahlvorschlag, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return berechnePerWahlvorschlag(politischeGruppeUndGebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagVeraendert, this);
    }

    @Override
    public List<Wahlvorschlag> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return new ArrayList<>(stimmergebnisDaten.getAlleWahlvorschlaegeProGruppe().get(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe()));
    }
  };

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cacheStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagPanaschiert = new HashMap<>();

  private final BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagPanaschiert = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Wahlvorschlag wahlvorschlag, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeListenstimmenKeinEinzelnerWahlvorschlagPanaschiertFuerPolitischeGruppeAufGebietUndDarunter(wahlvorschlag.getGruppe(), wahldurchfuehrungsgebiet,
          stimmenPoolAktuell);
    }

    @Override
    public Map<Wahlvorschlag, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return berechnePerWahlvorschlag(politischeGruppeUndGebietUndStimmabgabeart, cacheStimmzetteleingabenAktuellKeinEinzelnerWahlvorschlagPanaschiert, this);
    }

    @Override
    public List<Wahlvorschlag> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return new ArrayList<>(stimmergebnisDaten.getAlleWahlvorschlaegeProGruppe().get(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe()));
    }
  };

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelAktuellGueltig = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelAktuellGueltig = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleStimmzettelGueltig(stimmenPoolAktuell, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelAktuellGueltig, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_GUELTIGE;
    }
  };

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelAktuellUngueltig = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelAktuellUngueltig = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleStimmzettelUngueltig(stimmenPoolAktuell, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelAktuellUngueltig, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_GUELTIGE;
    }
  };

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelAktuellUnbestimmt = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelAktuellUnbestimmt = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleStimmzettelUnbestimmt(stimmenPoolAktuell, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelAktuellUnbestimmt, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_GUELTIGE;
    }
  };

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelVorperiodeGesamt = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelVorperiodeGesamt = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleWaehler(stimmenPoolVorperiode, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelVorperiodeGesamt, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_WAEHLER;
    }
  };

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelVorperiodeGueltig = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelVorperiodeGueltig = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleStimmzettelGueltig(stimmenPoolVorperiode, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelVorperiodeGueltig, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_GUELTIGE;
    }
  };

  private final Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelVorperiodeUngueltig = new HashMap<>();

  private final BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeStimmzettelVorperiodeUngueltig = new BestimmeStimmen<>() {

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return ermittleStimmzettelUngueltig(stimmenPoolVorperiode, wahldurchfuehrungsgebiet, stimmenart);
    }

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheStimmzettelVorperiodeUngueltig, this);
    }

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return TARGET_STIMMENARTEN_GUELTIGE;
    }
  };
}
