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

import java.util.Collection;
import java.util.List;
import java.util.Map;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.hochrechnung.boundary.Hochrechnungsalgorithm;
import de.ivu.elect.business.hochrechnung.boundary.HochrechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.hochrechnung.control.HochrechnenAnhandVorlaufigesErgebnis;
import de.ivu.elect.business.hochrechnung.control.HochrechnenMitDistanceWeightedKNearestNeighbors;


import static java.util.Arrays.asList;

public enum HochrechnungsalgorithmusArt {
  ZUFAELLIG{
    @Override
    public HochrechneralgorithmusAusfuehrbarkeitStatus getAlgorithmusAusfuehrbarkeitStatus(StimmenHochrechnerEingabedaten<Long> eingabedaten, Collection<? extends Gebiet> gebieteErzeugung) {
      return HochrechneralgorithmusAusfuehrbarkeitStatus.AUSFUEHRBAR;
    }

    @Override
    public ZufaelligerStimmenHochrechnerKonfiguration getKonfiguration() {
      return new ZufaelligerStimmenHochrechnerKonfiguration();
    }

    @Override
    public Hochrechnungsalgorithm getHochrechnungsalgorithm(HochrechnungsalgorithmusKonfiguration konfiguration) {
      return konfiguration instanceof  ZufaelligerStimmenHochrechnerKonfiguration?
          new ZufaelligerStimmenHochrechner((ZufaelligerStimmenHochrechnerKonfiguration) konfiguration) : null;
    }

  },

  UEBERNAHME_VOM_VORLAEUFIGEN_ERGEBNIS {
    @Override
    public HochrechneralgorithmusAusfuehrbarkeitStatus getAlgorithmusAusfuehrbarkeitStatus(StimmenHochrechnerEingabedaten<Long> eingabedaten, Collection<? extends Gebiet> gebieteErzeugung) {
      return HochrechneralgorithmusAusfuehrbarkeitStatus.AUSFUEHRBAR;
    }

    @Override
    public HochrechnungsalgorithmusKonfiguration getKonfiguration() {
      return new HochrechnenAnhandVorlaufigesErgebnisKonfiguration();
    }

    @Override
    public Hochrechnungsalgorithm getHochrechnungsalgorithm(HochrechnungsalgorithmusKonfiguration konfiguration) {
      return konfiguration instanceof  HochrechnenAnhandVorlaufigesErgebnisKonfiguration?
          new HochrechnenAnhandVorlaufigesErgebnis((HochrechnenAnhandVorlaufigesErgebnisKonfiguration) konfiguration) : null;
    }
  },

  HOCHRECHNEN_MIT_DISTANCE_WEIGHTED_KNEAREST_NEIGHBORS{
    @Override
    public HochrechneralgorithmusAusfuehrbarkeitStatus getAlgorithmusAusfuehrbarkeitStatus(StimmenHochrechnerEingabedaten<Long> eingabedaten, Collection<? extends Gebiet> gebieteErzeugung) {
      if (eingabedaten == null) {
        return HochrechneralgorithmusAusfuehrbarkeitStatus.NICHT_AUSFUEHRBAR_WEGEN_STIMMENHOCHRECHNEREINGABEDATEN;
      }

      if (!isErgebnisVorhandenAktuellOderVorperiode(eingabedaten)) {
        return HochrechneralgorithmusAusfuehrbarkeitStatus.NICHT_AUSFUEHRBAR_WEIL_WEDER_ERGEBNISEINGAENGE_DER_AKTUELLEN_WAHL_NOCH_DER_VORPERIODE_VORHANDEN_SIND;
      }

      return HochrechneralgorithmusAusfuehrbarkeitStatus.AUSFUEHRBAR;
    }

    private boolean isErgebnisVorhandenAktuellOderVorperiode(StimmenHochrechnerEingabedaten<Long> eingabedaten) {
      return (isErgebnisVorhanden(eingabedaten.getVorperiode()) || isErgebnisVorhanden(eingabedaten.getAktuelleWahl()));
    }

    private boolean isAWerteFuerUrnengebieteInAktuelleWahlOderVorperiodeVorhanden(Map<Gebiet, GebietDaten<Long>> vorperiode, Map<Gebiet, GebietDaten<Long>> aktuelleWahl, Collection<? extends Gebiet> betroffeneGebiete) {
      return betroffeneGebiete.stream().allMatch(gebiet -> isAWerteFuerUrnengebiete(aktuelleWahl.get(gebiet)) || isAWerteFuerUrnengebiete(vorperiode.get(gebiet)));
    }

    private boolean isAWerteFuerUrnengebiete(GebietDaten<Long> gebietDaten) {
      return gebietDaten!= null && (!gebietDaten.getGebiet().isUrnenwahlgebiet() || gebietDaten.getAnzahlWahlberechtigteImGebiet().getGesamtNotNull() > 0);
    }

    private boolean isErgebnisVorhanden(Map<Gebiet, GebietDaten<Long>> ergebnis) {
      return ergebnis.values().stream().anyMatch(gebietDaten -> gebietDaten != null && gebietDaten.isErgebnisVorhanden());
    }

    @Override
    public HochrechnenMitDistanceWeightedKNearestNeighborsKonfiguration getKonfiguration() {
      return new HochrechnenMitDistanceWeightedKNearestNeighborsKonfiguration();
    }

    @Override
    public Hochrechnungsalgorithm getHochrechnungsalgorithm(HochrechnungsalgorithmusKonfiguration konfiguration) {
      return konfiguration instanceof  HochrechnenMitDistanceWeightedKNearestNeighborsKonfiguration?
          new HochrechnenMitDistanceWeightedKNearestNeighbors((HochrechnenMitDistanceWeightedKNearestNeighborsKonfiguration) konfiguration) : null;
    }
  };

  public static List<HochrechnungsalgorithmusArt> getHochrechnungsalgorithmusArtenInBevorzugteReihenfolge() {
    return asList (
        HochrechnungsalgorithmusArt.HOCHRECHNEN_MIT_DISTANCE_WEIGHTED_KNEAREST_NEIGHBORS,
        HochrechnungsalgorithmusArt.ZUFAELLIG
    );
  }

  public abstract HochrechneralgorithmusAusfuehrbarkeitStatus getAlgorithmusAusfuehrbarkeitStatus(StimmenHochrechnerEingabedaten<Long> eingabedaten, Collection<? extends Gebiet> gebieteErzeugung);
  public abstract HochrechnungsalgorithmusKonfiguration getKonfiguration ();
  public abstract Hochrechnungsalgorithm getHochrechnungsalgorithm (HochrechnungsalgorithmusKonfiguration konfiguration);
}
