
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 de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
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 {

  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 getStimmzettelAktuellGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return berechneWaehlerGesamt(gebietUndStimmabgabeart, cacheWaehlerGesamtAktuell, bestimmeWaehlerGesamtAktuell);
  }

  // A.k.a. "Stimmzettel für genau einen Wahlvorschlag unverändert"
  public Long getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return stimmergebnisDaten.getListenInGebietshierarchie(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream() //
        .map(liste -> getStimmzettelAktuellNurEinzelnerWahlvorschlagUnveraendert(liste, gebietUndStimmabgabeart)) //
        .filter(Objects::nonNull).mapToLong(Long::longValue).sum();
  }

  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 stimmergebnisDaten.getListenInGebietshierarchie(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream() //
        .map(liste -> getStimmzettelAktuellNurEinzelnerWahlvorschlagVeraendert(liste, gebietUndStimmabgabeart)) //
        .filter(Objects::nonNull).mapToLong(Long::longValue).sum();
  }

  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 stimmergebnisDaten.getListenInGebietshierarchie(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream() //
        .map(liste -> getStimmzettelAktuellNurEinzelnerWahlvorschlagPanaschiert(liste, gebietUndStimmabgabeart)) //
        .filter(Objects::nonNull).mapToLong(Long::longValue).sum();
  }

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

  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, cacheWaehlerGesamtVorperiode, bestimmeWaehlerGesamtVorperiode);
  }

  private Long berechneWaehlerGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart, Map<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cache,
      BestimmeStimmen<Stimmenart, GebietUndStimmabgabeart> bestimmeWaehler) {
    return berechnePerStimmenart(gebietUndStimmabgabeart, Stimmenart.GESAMT, cache, bestimmeWaehler);
  }

  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 Math.max(0L, result);
    }

    return 0L; // ELECTKOM-808
  }

  private Long berechneStimmzetteleingabe(Wahlvorschlag wahlvorschlag, GebietUndStimmabgabeart gebietUndStimmabgabeart, Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Wahlvorschlag, Long>> cache,
      BestimmeStimmen<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart> bestimmeStimmzetteleingabe) {
    return berechnePerWahlvorschlag(gebietUndStimmabgabeart, wahlvorschlag, cache, bestimmeStimmzetteleingabe);
  }

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

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

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

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

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

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

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheWaehlerGesamtAktuell, 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<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart>() {

    @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<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart>() {

    @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<Wahlvorschlag, PolitischeGruppeUndGebietUndStimmabgabeart>() {

    @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<GebietUndStimmabgabeart, Map<Stimmenart, Long>> cacheStimmzettelAktuellGueltig = new HashMap<>();

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

    @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<Stimmenart, GebietUndStimmabgabeart>() {

    @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>> cacheWaehlerGesamtVorperiode = new HashMap<>();

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

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

    @Override
    public Map<Stimmenart, Long> getErgebnisUntergebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return berechnePerStimmenart(gebietUndStimmabgabeart, cacheWaehlerGesamtVorperiode, 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<Stimmenart, GebietUndStimmabgabeart>() {

    @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<Stimmenart, GebietUndStimmabgabeart>() {

    @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;
    }
  };
}
