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

import static java.util.Arrays.asList;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

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.control.GebietUndStimmabgabeart;

public abstract class AbstractErgebnisDatenWahlberechtigte extends AbstractErgebnisDaten {

  private final StimmenKumulierer stimmenKumulierer;

  private final StimmenPool stimmenPool;

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

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

    @Override
    public Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return AbstractErgebnisDatenWahlberechtigte.this.ermittleStimmen(stimmenart, wahldurchfuehrungsgebiet);
    }

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

    @Override
    public List<Stimmenart> getTargetAufGebiet(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return asList(Stimmenart.GESAMT, Stimmenart.MIT_WAHLSCHEIN, Stimmenart.OHNE_WAHLSCHEIN, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
    }
  };

  public AbstractErgebnisDatenWahlberechtigte(Function<Wahldurchfuehrungsgebiet, List<Wahldurchfuehrungsgebiet>> untergeordneteGebiete, Supplier<Set<Gebiet>> erfassteGebiete,
      StimmenKumulierer stimmenKumulierer, StimmenPool stimmenPool) {
    super(untergeordneteGebiete, erfassteGebiete);

    this.stimmenKumulierer = stimmenKumulierer;
    this.stimmenPool = stimmenPool;
  }

  protected StimmenKumulierer getStimmenKumulierer() {
    return stimmenKumulierer;
  }

  protected StimmenPool getStimmenPool() {
    return stimmenPool;
  }

  public boolean isErmittleStimmenNurAnhandErfassungsgebiete() {
    return true;
  }

  public abstract Long ermittleStimmen(Stimmenart stimmenart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet);

  public long getSummeWahlberechtigteOhneWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteOhneWahlschein(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getSummeWahlberechtigteOhneWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWahlberechtigteOhneWahlschein(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWahlberechtigteOhneWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getSummeWahlberechtigte(gebietUndStimmabgabeart, Stimmenart.OHNE_WAHLSCHEIN);
  }

  public long getSummeWahlberechtigteMitWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteMitWahlschein(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getSummeWahlberechtigteMitWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWahlberechtigteMitWahlschein(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWahlberechtigteMitWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getSummeWahlberechtigte(gebietUndStimmabgabeart, Stimmenart.MIT_WAHLSCHEIN);
  }

  public long getSummeWahlberechtigteNichtImWaehlerverzeichnis(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteNichtImWaehlerverzeichnis(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getSummeWahlberechtigteNichtImWaehlerverzeichnis(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWahlberechtigteNichtImWaehlerverzeichnis(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWahlberechtigteNichtImWaehlerverzeichnis(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getSummeWahlberechtigte(gebietUndStimmabgabeart, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
  }

  public long getSummeWahlberechtigteGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getSummeWahlberechtigteGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWahlberechtigteGesamt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWahlberechtigteGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getSummeWahlberechtigte(gebietUndStimmabgabeart, Stimmenart.GESAMT);
  }

  private long getSummeWahlberechtigte(GebietUndStimmabgabeart gebietUndStimmabgabeart, Stimmenart stimmenart) {
    return notNull(getSummeWahlberechtigte(gebietUndStimmabgabeart).get(stimmenart));
  }

  private Map<Stimmenart, Long> getSummeWahlberechtigte(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    if (isErmittleStimmenNurAnhandErfassungsgebiete()) {
      return bestimmeStimmenOderSummiereAufUntergebieten(gebietUndStimmabgabeart, wahlberechtigte, wahlberechtigteBestimmeStimmen);
    }

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    return wahlberechtigte.computeIfAbsent(gebietUndStimmabgabeart, ignored -> {
      Map<Stimmenart, Long> result = new HashMap<>();

      for (Stimmenart stimmenart : wahlberechtigteBestimmeStimmen.getTargetAufGebiet(gebietUndStimmabgabeart)) {
        result.put(stimmenart, wahldurchfuehrungsgebiet == null ? 0L : ermittleStimmen(stimmenart, wahldurchfuehrungsgebiet));
      }

      return result;
    });
  }
}
