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

import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toSet;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietsartBezirksart;
import de.ivu.elect.business.gebietsbaum.entity.NlGebiet;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiGebietsart;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;

// Cf. https://support.elect-it.com/wiki/display/ELECT/Kopfzahlen+in+VAPP!
public enum NlSystemGruppe {

  WAHLBERECHTIGTE(SystemGruppe.WAHLBERECHTIGTE, //
      Stimmenart.GESAMT //
  ) {

    @Override
    public boolean isBlanko() {
      return false;
    }

    @Override
    public boolean isStimmzettel() {
      return false;
    }
  },

  WAEHLER(SystemGruppe.WAEHLER, //
      Stimmenart.OHNE_WAHLSCHEIN, //
      Stimmenart.MIT_VOLLMACHT, //
      Stimmenart.MIT_WAHLSCHEIN, //
      Stimmenart.GESAMT //
  ) {

    @Override
    public boolean isBlanko() {
      return false;
    }

    @Override
    public boolean isStimmzettel() {
      return false;
    }
  },

  GUELTIGE(SystemGruppe.GUELTIGE, //
      Stimmenart.LISTENSTIMME //
  ) {

    @Override
    public boolean isBlanko() {
      return false;
    }

    @Override
    public boolean isStimmzettel() {
      return false;
    }
  },

  UNGUELTIGE(SystemGruppe.UNGUELTIGE, //
      Stimmenart.LISTENSTIMME //
  ) {

    @Override
    public boolean isBlanko() {
      return false;
    }

    @Override
    public boolean isStimmzettel() {
      return false;
    }
  },

  BLANKO(SystemGruppe.UEBRIGE, //
      // NOTE, dass (leider) NICHT Stimmenart#LISTENSTIMME verwendet werden kann, da die Kombination SystemGruppe#UEBRIGE und Stimmenart#LISTENSTIMME
      // bereits bei der Anzeige der kumulierten Stimmen, bspw. "Kumulierte Stimmen der Gruppen", zur Anzeige "Sonstiger" verbrannt wurde!
      Stimmenart.LISTENSTIMME_LEER // Leere Stimmen
  ) {

    @Override
    public boolean isBlanko() {
      return true;
    }

    @Override
    public boolean isStimmzettel() {
      return false;
    }
  },

  STIMMZETTEL_EINGENOMMEN(SystemGruppe.UEBRIGE, //
      Stimmenart.GESAMT_EINGENOMMEN // Stimmzettel eingenommen insgesamt
  ) {

    @Override
    public boolean isStimmzettel() {
      return true;
    }

    @Override
    public boolean isStimmenartRelevant(Wahl wahl, WahlartErgebnisart ergebnisart, Stimmenart stimmenart) {
      if (NlWahlart.equalsWahlebene(wahl, NlWahlart.KUERZEL_WAHLEBENE_PSB)) {
        return super.isStimmenartRelevant(wahl, ergebnisart, stimmenart);
      }

      return false;
    }
  },

  /**
   * Anzahlen der abweichenden Stimmzettel (== zuviel oder zuwenig eingenommenen Stimmzettel) mit fester Begründung.
   */
  STIMMZETTEL_EINGENOMMEN_ABWEICHEND(SystemGruppe.UEBRIGE, //
      Stimmenart.GESAMT_ABWEICHEND_ZUVIEL, // Anzahl Stimmzettel eingenommen insgesamt > Anzahl Wähler insgesamt
      Stimmenart.GESAMT_ABWEICHEND_ZUWENIG, // Anzahl Stimmzettel eingenommen insgesamt < Anzahl Wähler insgesamt

      Stimmenart.GESAMT_ABWEICHEND_GRUND_AUSGEGEBEN_NICHT_ABGEGEBEN, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_AUSGEGEBEN_ZUWENIG, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_AUSGEGEBEN_ZUVIEL, //

      Stimmenart.GESAMT_ABWEICHEND_GRUND_IN_WAHLBRIEF_ZUWENIG, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_IN_WAHLBRIEF_ZUVIEL, //

      Stimmenart.GESAMT_ABWEICHEND_GRUND_VERLOREN, //

      Stimmenart.GESAMT_ABWEICHEND_GRUND_UNBEKANNT, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_SONSTIGES //
  ) {

    @Override
    public boolean isStimmzettel() {
      return true;
    }
  },

  ;

  private static final Collection<NlSystemGruppe> VALUES_BLANKO = stream(values()).filter(NlSystemGruppe::isBlanko).collect(toSet());

  private static final Collection<NlSystemGruppe> VALUES_STIMMZETTEL = stream(values()).filter(NlSystemGruppe::isStimmzettel).collect(toSet());

  private static final Collection<Stimmenart> STIMMENARTEN_SPEZIFISCH_URNENWAHL = asList( //
      Stimmenart.OHNE_WAHLSCHEIN, //
      Stimmenart.MIT_WAHLSCHEIN, //
      Stimmenart.MIT_VOLLMACHT, //

      Stimmenart.GESAMT_ABWEICHEND_GRUND_AUSGEGEBEN_NICHT_ABGEGEBEN, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_AUSGEGEBEN_ZUWENIG, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_AUSGEGEBEN_ZUVIEL //
  );

  private static final Collection<Stimmenart> STIMMENARTEN_SPEZIFISCH_BRIEFWAHL = asList( //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_IN_WAHLBRIEF_ZUWENIG, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_IN_WAHLBRIEF_ZUVIEL, //
      Stimmenart.GESAMT_ABWEICHEND_GRUND_VERLOREN //
  );

  private final SystemGruppe systemGruppe;

  private final List<Stimmenart> stimmenarten;

  NlSystemGruppe(SystemGruppe systemGruppe, Stimmenart one, Stimmenart... more) {
    this.systemGruppe = systemGruppe;

    this.stimmenarten = new ArrayList<>();
    this.stimmenarten.add(one);
    this.stimmenarten.addAll(asList(more));
  }

  public SystemGruppe getSystemGruppe() {
    return systemGruppe;
  }

  public boolean isStimmenartRelevantAufGebiet(Gebiet gebiet, WahlartErgebnisart ergebnisart, Stimmenart stimmenart) {
    if (gebiet != null) {
      Optional<StimmeingabePlausiGebietsart> gebietsart = Optional.empty();

      // ELECT-11714
      Collection<Stimmenart> stimmenartenSpezifischNurBriefwahlGemeinde = asList( // Keine NUR "Gemeinde-Briefwahl", a.k.a. "normale Briefwahl", spezifischen Stimmenarten...
      );

      Collection<Stimmenart> stimmenartenSpezifischNurBriefwahlAusland = asList( //
          Stimmenart.GESAMT_ABWEICHEND_GRUND_VERLOREN // ELECT-11571
      );

      if (gebiet.isBriefwahlgebiet()) { // Immer explizit markiert!
        NlGebiet nlBriefwahlgebiet = new NlGebiet(gebiet);

        if (stimmenartenSpezifischNurBriefwahlGemeinde.contains(stimmenart)) {
          return nlBriefwahlgebiet.isBriefwahlgebietGemeinde();
        }

        if (stimmenartenSpezifischNurBriefwahlAusland.contains(stimmenart)) {
          return nlBriefwahlgebiet.isBriefwahlgebietAusland();
        }

        gebietsart = Optional.of(StimmeingabePlausiGebietsart.BRIEFWAHLBEZIRK);
      } else {
        GebietsartBezirksart gebietsartBezirksart = gebiet.getGebietsart().getBezirksart();

        /*
         * ELECT-10109, hier nur Urnenwahlgebiete auf unterster Ebene (== Stimmbezirke in PSB) auswerten, da auf übergeordneten "Urnenwahlgebieten" (== Gemeinde in HSB oder CSB) ggf. auch Stimmenarten
         * aus nicht mehr im Gebietsbaum hinterlegten Briefwahlgebieten angezeigt werden müssen!
         */
        if (gebietsartBezirksart.isStimmbezirk()) {
          gebietsart = Optional.of(StimmeingabePlausiGebietsart.URNENWAHLBEZIRK);
        }
      }

      Wahl wahl = gebiet.getWahl();

      if (gebietsart.isPresent()) {
        return isStimmenartRelevantBeiStimmeingabe(wahl, ergebnisart, gebietsart, stimmenart);
      } else {
        if (isStimmenartRelevant(wahl, ergebnisart, stimmenart)) {
          if (stimmenartenSpezifischNurBriefwahlGemeinde.contains(stimmenart)) {
            return NlWahlart.isBriefwahlGemeindeUnterstuetzt(gebiet);
          }

          if (stimmenartenSpezifischNurBriefwahlAusland.contains(stimmenart)) {
            return NlWahlart.isBriefwahlAuslandUnterstuetzt(gebiet);
          }

          if (STIMMENARTEN_SPEZIFISCH_BRIEFWAHL.contains(stimmenart)) { // ELECT-10109
            return NlWahlart.isBriefwahlGemeindeUnterstuetzt(gebiet) || NlWahlart.isBriefwahlAuslandUnterstuetzt(gebiet);
          }

          return true;
        }
      }
    }

    return false;
  }

  public boolean isStimmenartRelevantBeiStimmeingabe(Wahl wahl, WahlartErgebnisart ergebnisart, Optional<StimmeingabePlausiGebietsart> gebietsart, Stimmenart stimmenart) {
    if (isStimmenartRelevant(wahl, ergebnisart, stimmenart)) {
      boolean briefwahlbezirk = gebietsart.map(StimmeingabePlausiGebietsart::isBriefwahlbezirk).orElse(false);

      if (briefwahlbezirk) {
        return !STIMMENARTEN_SPEZIFISCH_URNENWAHL.contains(stimmenart);
      }

      return !STIMMENARTEN_SPEZIFISCH_BRIEFWAHL.contains(stimmenart);
    }

    return false;
  }

  protected boolean isStimmenartRelevant(Wahl wahl, WahlartErgebnisart ergebnisart, Stimmenart stimmenart) {
    return getStimmenarten().contains(stimmenart);
  }

  public List<Stimmenart> getStimmenarten() {
    return stimmenarten;
  }

  public boolean isBlanko() {
    return false;
  }

  public abstract boolean isStimmzettel();

  public boolean equalsSystemGruppe(EntityMitGruppe< ? > entityMitGruppe, SystemGruppeEigenschaften systemGruppeEigenschaften) {
    return entityMitGruppe != null && equalsSystemGruppe(entityMitGruppe.getGruppe(), systemGruppeEigenschaften);
  }

  public boolean equalsSystemGruppe(Gruppe gruppe, SystemGruppeEigenschaften systemGruppeEigenschaften) {
    return gruppe != null && getSystemGruppe().equalsGruppe(gruppe, systemGruppeEigenschaften);
  }

  public static Collection<NlSystemGruppe> valuesBlanko() {
    return VALUES_BLANKO;
  }

  public static Collection<NlSystemGruppe> valuesStimmzettel() {
    return VALUES_STIMMZETTEL;
  }
}
