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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
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.internet.entity.PraesentationErgebnisArt;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;


import static java.util.stream.Collectors.toList;

public class PraesentationExportWahldaten extends ErgebnisDaten {

  private static final long serialVersionUID = 1L;

  private final List<Ergebniseingang> ergebniseingaenge;

  private final List<Wahldurchfuehrungsgebiet> seitLetzemExportGeaenderteErfassungsgebiete;

  // siehe auch ELECTMUC-138
  private final Set<Wahldurchfuehrungsgebiet> gebieteDieNichtImExportAngezeigtWerdenSollen;

  // siehe auch ELECTMUC-308
  private final PraesentationErgebnisArt praesentationErgebnisArt;

  private final Date zeitpunktAuslesenDaten = new Date();

  public PraesentationExportWahldaten(ErgebnisDaten ergebnisDaten, List<Ergebniseingang> ergebniseingaenge,
      List<Wahldurchfuehrungsgebiet> seitLetzemExportGeaenderteErfassungsgebiete,
      Set<Wahldurchfuehrungsgebiet> gebieteDieNichtImExportAngezeigtWerdenSollen,
      PraesentationErgebnisArt praesentationErgebnisArt) {
    super(ergebnisDaten);
    this.ergebniseingaenge = ergebniseingaenge;
    this.seitLetzemExportGeaenderteErfassungsgebiete = seitLetzemExportGeaenderteErfassungsgebiete;
    this.gebieteDieNichtImExportAngezeigtWerdenSollen = gebieteDieNichtImExportAngezeigtWerdenSollen;
    this.praesentationErgebnisArt = praesentationErgebnisArt;
  }

  public List<Ergebniseingang> getErgebniseingaenge() {
    return ergebniseingaenge;
  }

  public Long getAnzahlErfassungsgebieteInsgesamt() {
    return (long) getErfassungsgebiete().size();
  }
   public Long getAnzahlErfassungsgebieteInsgesamt(Stimmabgabeart stimmabgabeart) {
     return (long) getErfassungsgebieteInsgesamt(stimmabgabeart).size();
   }

  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteInsgesamt(Stimmabgabeart stimmabgabeart) {
    return getErfassungsgebiete().stream().filter(g->stimmabgabeart.getStimmabgabeartFuerAlleGebiet().isBetrachteErfassungsgebiet(g, ()->{
      throw new RuntimeException("Unsupported");
    })).collect(toList());
  }

  public Long getAnzahlErfassungsgebieteInsgesamtUnterhalbVon(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    if (wahldurchfuehrungsgebiet.getUebergeordnetesGebiet() == null) {
      return getAnzahlErfassungsgebieteInsgesamt(stimmabgabeart);
    }

    return (long) getErfassungsgebieteUnterhalbVon(wahldurchfuehrungsgebiet, stimmabgabeart/*ELECT-12758*/).size();
  }


  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteUnterhalbVon(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getErfassungsgebieteUnterhalbVon(wahldurchfuehrungsgebiet).stream().filter(g->stimmabgabeart.getStimmabgabeartFuerAlleGebiet().isBetrachteErfassungsgebiet(g, ()->{
      throw new RuntimeException("Unsupported");
    })).collect(toList());
  }

  public Long getAnzahlErfassungsgebieteAusgezaehlt() {
    return (long) getErgebniseingaenge().size();
  }

  public Long getAnzahlErfassungsgebieteAusgezaehltUnterhalbVon(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return (long) getErfassungsgebieteUnterhalbVonAusgezaehlt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart)).size();
  }

  public Long getAnzahlErfassungsgebieteAusbleibendUnterhalbVon(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return (long) getAusbleibendUnterhalbVon(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart)).size();
  }

  public Long getAnzahlErfassungsgebieteAusgezaehltDirektstimmen() {
    return getAnzahlGebieteProStimmenartAusgezaehlt(getWurzelgebiet()).getOrDefault(Stimmenart.DIREKTSTIMME, 0L);
  }

  public Long getAnzahlErfassungsgebieteAusgezaehltListenstimme() {
    return getAnzahlGebieteProStimmenartAusgezaehlt(getWurzelgebiet()).getOrDefault(Stimmenart.LISTENSTIMME, 0L);
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> letztenGueltigenErgebniseingangFuerGebiet = null;

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> getLetztenGueltigenErgebniseingangFuerGebiet() {
    if (letztenGueltigenErgebniseingangFuerGebiet == null) {
      mappeLetztenGueltigenErgebniseingangFuerGebiet();
    }
    return letztenGueltigenErgebniseingangFuerGebiet;
  }

  public Ergebniseingang getLetztenGueltigenErgebniseingangFuerGebiet(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getLetztenGueltigenErgebniseingangFuerGebiet().get(wahldurchfuehrungsgebiet);
  }

  private void mappeLetztenGueltigenErgebniseingangFuerGebiet() {
    letztenGueltigenErgebniseingangFuerGebiet = new HashMap<>();
    for (Ergebniseingang ergebniseingang : ergebniseingaenge) {
      letztenGueltigenErgebniseingangFuerGebiet.put(ergebniseingang.getWahldurchfuehrungsgebiet(), ergebniseingang);
    }
  }

  private transient Map<Wahldurchfuehrungsgebiet, Collection<Ergebniseingang>> letzeGueltigeErgebniseingaengeUnterhalbVon = new HashMap<>();

  public Collection<Ergebniseingang> getLetzeGueltigeErgebniseingaengeUnterhalbVon(
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    Collection<Ergebniseingang> result = letzeGueltigeErgebniseingaengeUnterhalbVon.get(wahldurchfuehrungsgebiet);
    if (result == null) {
      result = new ArrayList<>();
      letzeGueltigeErgebniseingaengeUnterhalbVon.put(wahldurchfuehrungsgebiet, result);

      Ergebniseingang letztenGueltigenErgebniseingangFuerGebietParameter = getLetztenGueltigenErgebniseingangFuerGebiet(wahldurchfuehrungsgebiet);
      if (letztenGueltigenErgebniseingangFuerGebietParameter != null) {
        result.add(letztenGueltigenErgebniseingangFuerGebietParameter);
      }

      for (Wahldurchfuehrungsgebiet untergebiet : getUntergeordneteGebiete(wahldurchfuehrungsgebiet)) {
        result.addAll(getLetzeGueltigeErgebniseingaengeUnterhalbVon(untergebiet));
      }
    }

    return result;
  }

  public List<Wahldurchfuehrungsgebiet> getSeitLetzemExportGeaenderteErfassungsgebiete() {
    return seitLetzemExportGeaenderteErfassungsgebiete;
  }

  private final Map<GebietUndStimmabgabeart, List<Wahldurchfuehrungsgebiet>> erfassungsgebieteUnterhalbVonAusgezaehlt = new HashMap<>();

  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteUnterhalbVonAusgezaehlt(
      GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    List<Wahldurchfuehrungsgebiet> result = erfassungsgebieteUnterhalbVonAusgezaehlt.get(gebietUndStimmabgabeart);
    if (result == null) {
      result = new ArrayList<>();
      erfassungsgebieteUnterhalbVonAusgezaehlt.put(gebietUndStimmabgabeart, result);
      for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        if (isAusgezaehlt(wahldurchfuehrungsgebiet)) {
          result.add(wahldurchfuehrungsgebiet);
        }
      }
    }
    return result;
  }

  private final Map<GebietUndStimmabgabeart, List<Wahldurchfuehrungsgebiet>> ausbleibendUnterhalbVon = new HashMap<>();

  public List<Wahldurchfuehrungsgebiet> getAusbleibendUnterhalbVon(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    List<Wahldurchfuehrungsgebiet> result = ausbleibendUnterhalbVon.get(gebietUndStimmabgabeart);
    if (result == null) {
      result = new ArrayList<>();
      ausbleibendUnterhalbVon.put(gebietUndStimmabgabeart, result);
      for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : getErfassungsgebieteLautGebietsartUnterhalbVon(gebietUndStimmabgabeart)) {
        if (isAusbleibend(wahldurchfuehrungsgebiet)) {
          result.add(wahldurchfuehrungsgebiet);
        }
      }
    }
    return result;
  }

  private boolean isAusbleibend(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return wahldurchfuehrungsgebiet.getErfasstIn() != null || wahldurchfuehrungsgebiet.getErgebniseingabestatus(getErgebnis()).isAusbleibend();
  }

  private transient List<Wahldurchfuehrungsgebiet> erfassungsgebieteMitErgebniseingang;

  public List<Wahldurchfuehrungsgebiet> getErfassungsgebieteMitErgebniseingang() {
    if (erfassungsgebieteMitErgebniseingang == null) {
      erfassungsgebieteMitErgebniseingang = new ArrayList<>();
      for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : getGebiete()) {
        if (wahldurchfuehrungsgebiet.isErfassungsgebiet() && hatGueltigenErgebniseingang(wahldurchfuehrungsgebiet)) {
          erfassungsgebieteMitErgebniseingang.add(wahldurchfuehrungsgebiet);
        }
      }
    }
    return erfassungsgebieteMitErgebniseingang;
  }

  private Map<Wahldurchfuehrungsgebiet, Boolean> gebietFreigegenenCache = new HashMap<>();

  public Set<Wahldurchfuehrungsgebiet> getGebieteDieNichtImExportAngezeigtWerdenSollen() {
    return gebieteDieNichtImExportAngezeigtWerdenSollen;
  }

  public PraesentationErgebnisArt getPraesentationErgebnisArt() {
    return praesentationErgebnisArt;
  }

  private boolean hatGueltigenErgebniseingang(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErgebniseingaenge().stream().anyMatch(ergebniseingang -> ergebniseingang.getWahldurchfuehrungsgebiet().equals(wahldurchfuehrungsgebiet));
  }

  public boolean isGebietFreigegeben(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    if (wahldurchfuehrungsgebiet == null) {
      return false;
    }
    return gebietFreigegenenCache.computeIfAbsent(wahldurchfuehrungsgebiet, this::berechneGebietFreigegeben);
  }

  private boolean berechneGebietFreigegeben(Wahldurchfuehrungsgebiet w) {
    if (w.getErgebniseingabestatus(getErgebnis()).isFreigegeben()) {
      return true;
    }
    Gebiet uebergeordnetesGebiet = w.getUebergeordnetesGebiet();
    if (uebergeordnetesGebiet==null) {
      return false;
    }
    return berechneGebietFreigegeben(uebergeordnetesGebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity());
  }

  public Date getZeitpunktAuslesenDaten() {
    return zeitpunktAuslesenDaten;
  }
}
