
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://support.elect-it.com/wiki/display/ELECT/Kopfzahlenkonfiguration!
public enum UmgangGueltigeWerte {

  ERFASSEN_ALS_STIMMEN,
  ERFASSEN_ALS_STIMMZETTEL,

  ;

  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, ignored -> 0L);
  }

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

  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, Function<Gebiet, Long> anzahlUnbestimmteFct) {
    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;

          Long anzahlUnbestimmte = anzahlUnbestimmteFct.apply(gebiet); // ELECT-13792, bspw. "Stimmzettel mit Anlass zu Bedenken"...

          if (anzahlUnbestimmte != null) {
            result -= anzahlUnbestimmte;
          }
        }
      }
    }

    if (result == null) {
      return result;
    }

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

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

  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, Function<Gebiet, Long> anzahlUnbestimmteFct) {
    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;

          Long anzahlUnbestimmte = anzahlUnbestimmteFct.apply(gebiet); // ELECT-13792, bspw. "Stimmzettel mit Anlass zu Bedenken"...

          if (anzahlUnbestimmte != null) {
            result -= anzahlUnbestimmte;
          }
        }
      }
    }

    if (result == null) {
      return result;
    }

    return Math.max(0L, result);
  }

  @Nullable
  public static Long berechneStimmzettelUnbestimmt(Wahl wahl, WahlartErgebnisart ergebnisart, Gebiet gebiet, Function<Gebiet, Long> anzahlWaehlerGesamtFct, Function<Gebiet, Long> anzahlGueltigeFct,
      Function<Gebiet, Long> anzahlUngueltigeFct, Function<Gebiet, Long> anzahlUnbestimmteFct) {
    return anzahlUnbestimmteFct.apply(gebiet); // ELECT-13792, bisher nur "Stimmzettel mit Anlass zu Bedenken", die in KOM-RP EXPLIZIT ERFASST und nicht berechnet werden...
  }

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