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

import java.util.function.Function;
import java.util.function.Predicate;

import javax.annotation.Nullable;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;

// Cf. https://confluence.ivu.de/display/ELECT/Kopfzahlenkonfiguration!
public enum UmgangGueltigeWerte {

  ERFASSEN_ALS_STIMMEN,
  ERFASSEN_ALS_STIMMZETTEL,

  ;

  private UmgangGueltigeWerte() {
  }

  public boolean isErfassenAlsStimmen() {
    return !isErfassenAlsStimmzettel();
  }

  public boolean isErfassenAlsStimmzettel() {
    return ERFASSEN_ALS_STIMMZETTEL.equals(this);
  }

  @Nullable
  public static Long berechneStimmenGueltig(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct) {
    return berechneAnzahlGueltig(wahl, ergebnisart, gebiet, //
        UmgangGueltigeWerte::isErfassenAlsStimmen, //
        umgangUngueltige -> false, // Gültige Stimmen sind nicht ohne Weiteres, bspw. Anzahl Stimmen pro Wähler, aus Anzahl Wähler und Anzahl ungültige Stimmen berechenbar!
        anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct);
  }

  @Nullable
  public static Long berechneStimmzettelGueltig(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct) {
    return berechneAnzahlGueltig(wahl, ergebnisart, gebiet, //
        UmgangGueltigeWerte::isErfassenAlsStimmzettel, //
        UmgangGueltigeWerte::isErfassenAlsStimmzettel, //
        anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct);
  }

  private static Long berechneAnzahlGueltig(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Predicate<UmgangGueltigeWerte> anzahlGueltigeErfasstWieErwartet,
      Predicate<UmgangGueltigeWerte> anzahlGueltigeBerechenbarAusAnzahlWaehlerUndAnzahlUngueltige, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct) {
    Long result = null;

    if (anzahlGueltigeErfasstWieErwartet.test(valueOfGueltige(wahl, ergebnisart))) {
      result = anzahlGueltigeFct.apply(gebiet);
    } else if (wahl.getAnzahlStimmzettel() != null && wahl.getAnzahlStimmzettel() < 2 && anzahlGueltigeBerechenbarAusAnzahlWaehlerUndAnzahlUngueltige.test(valueOfUngueltige(wahl, ergebnisart))) {
      Long anzahlWaehlerGesamt = anzahlWaehlerGesamtFct.apply(gebiet);

      if (anzahlWaehlerGesamt != null) {
        Long anzahlUngueltige = anzahlUngueltigeFct.apply(gebiet);

        if (anzahlUngueltige != null) {
          result = anzahlWaehlerGesamt - anzahlUngueltige;
        }
      }
    }

    if (result != null) {
      return Math.max(0L, result);
    }

    return result;
  }

  @Nullable
  public static Long berechneStimmenUngueltig(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct) {
    return berechneAnzahlUngueltig(wahl, ergebnisart, gebiet, //
        UmgangGueltigeWerte::isErfassenAlsStimmen, //
        umgangGueltige -> false, // Ungültige Stimmen sind nicht ohne Weiteres, bspw. Anzahl Stimmen pro Wähler, aus Anzahl Wähler und Anzahl gültige Stimmen berechenbar!
        anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct);
  }

  @Nullable
  public static Long berechneStimmzettelUngueltig(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct) {
    return berechneAnzahlUngueltig(wahl, ergebnisart, gebiet, //
        UmgangGueltigeWerte::isErfassenAlsStimmzettel, //
        UmgangGueltigeWerte::isErfassenAlsStimmzettel, //
        anzahlWaehlerGesamtFct, anzahlGueltigeFct, anzahlUngueltigeFct);
  }

  private static Long berechneAnzahlUngueltig(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Predicate<UmgangGueltigeWerte> anzahlUngueltigeErfasstWieErwartet,
      Predicate<UmgangGueltigeWerte> anzahlUngueltigeBerechenbarAusAnzahlWaehlerUndAnzahlGueltige, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct) {
    Long result = null;

    if (anzahlUngueltigeErfasstWieErwartet.test(valueOfUngueltige(wahl, ergebnisart))) {
      result = anzahlUngueltigeFct.apply(gebiet);
    } else if (wahl.getAnzahlStimmzettel() != null && wahl.getAnzahlStimmzettel() < 2 && anzahlUngueltigeBerechenbarAusAnzahlWaehlerUndAnzahlGueltige.test(valueOfGueltige(wahl, ergebnisart))) {
      Long anzahlWaehlerGesamt = anzahlWaehlerGesamtFct.apply(gebiet);

      if (anzahlWaehlerGesamt != null) {
        Long anzahlGueltige = anzahlGueltigeFct.apply(gebiet);

        if (anzahlGueltige != null) {
          result = anzahlWaehlerGesamt - anzahlGueltige;
        }
      }
    }

    if (result != null) {
      return Math.max(0L, result);
    }

    return result;
  }

  public static UmgangGueltigeWerte valueOfGueltige(Wahl wahl, WahlartErgebnisart ergebnisart) {
    Wahlart wahlart = wahl.getWahlart();

    if (ergebnisart.isEndgueltig()) {
      return wahlart.getUmgangGueltigeEndgueltig();
    }

    if (ergebnisart.isVorlaeufig()) {
      return wahlart.getUmgangGueltigeVorlaeufig();
    }

    return ERFASSEN_ALS_STIMMEN;
  }

  public static UmgangGueltigeWerte valueOfUngueltige(Wahl wahl, WahlartErgebnisart ergebnisart) {
    Wahlart wahlart = wahl.getWahlart();

    if (ergebnisart.isEndgueltig()) {
      return wahlart.getUmgangUngueltigeEndgueltig();
    }

    if (ergebnisart.isVorlaeufig()) {
      return wahlart.getUmgangUngueltigeVorlaeufig();
    }

    return ERFASSEN_ALS_STIMMEN;
  }
}
