
package de.ivu.elect.business.stimmeingabe.control.plausi;

import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart.ERROR;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart.WARNING;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiGebietsart.URNENWAHLBEZIRK;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiGebietsart.URNENWAHLBEZIRK_SONDERSTIMMBEZIRK;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.PRUEFKENNZEICHEN_WAHLBERECHTIGTE_ERFASST;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.PRUEFKENNZEICHEN_WAHLBERECHTIGTE_GLEICH_PRUEFKENNZEICHEN_WAHLBERECHTIGTE_AM_GEBIET;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.STIMMEINGABE_ANZAHL_ENDGUELTIG_GLEICH_STIMMEINGABE_ANZAHL_VORLAEUFIG;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.STIMMEINGABE_ANZAHL_KONVERTIERBAR;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_AUS_BRIEFWAHLBEZIRK_ERFASST_IN_URNENWAHLBEZIRK_ANZAHL_KLEINER_GLEICH_WAEHLER_MIT_WAHLSCHEIN_ANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GLEICH_WAEHLER_ANZAHLEN_SUMME;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GROESSER_GLEICH_MINDESTANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GROESSER_NULL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_GROESSER_WAHLBERECHTIGTE_GESAMT_ANZAHL_ANTEIL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_GESAMT_ANZAHL_KLEINER_WAHLBERECHTIGTE_GESAMT_ANZAHL_ANTEIL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_GLEICH_WAEHLER_GESAMT_ANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_GLEICH_WAEHLER_GESAMT_ANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAEHLER_GESAMT_ANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_ANTEIL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_NULL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_ANTEIL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_ANZAHL_AM_GEBIET;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_NULL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_WAHLBERECHTIGTE_ANZAHLEN_SUMME;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_GESAMT_ANZAHL_GROESSER_NULL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_GLEICH_ANZAHL_AM_GEBIET;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_GLEICH_NULL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_GLEICH_ANZAHL_AM_GEBIET;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_GLEICH_NULL;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_ANZAHL_AM_GEBIET;
import static de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp.WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_NULL;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

import javax.enterprise.context.RequestScoped;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.AbstractStimmeingabePlausiKonfigurationDAO;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiErgebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiGebietsart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfigurationCustomData;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp;
import de.ivu.elect.business.stimmen.boundary.StimmeLabelMessages;
import de.ivu.elect.business.wahl.boundary.WahlartTemplate;
import de.ivu.elect.business.wahl.boundary.WasUmgangAWerteKonfiguration;
import de.ivu.elect.business.wahl.boundary.WasUmgangBWerteKonfiguration;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.jee.cdi.BeanManagerUtil;

@RequestScoped // ELECT-7845
public abstract class AbstractStimmeingabePlausiDefinitions implements StimmeingabePlausiDefinitions {

  private static final Logger LOG = LoggerFactory.getLogger(StimmeingabePlausiKonfiguration.class);

  private final WahlartTemplate wahlartTemplate;

  private final Wahlart wahlart;

  private Map<StimmeingabePlausiGebietsart, Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration>> stimmeingabePlausiKonfigurationDefaultsVorlaeufig;

  private Map<StimmeingabePlausiGebietsart, Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration>> stimmeingabePlausiKonfigurationDefaultsEndgueltig;

  private StimmeingabePlausiFormatter stimmeingabePlausiFormatter;

  // Nicht verwenden(!)
  private AbstractStimmeingabePlausiDefinitions() {
    this(null);
  }

  public AbstractStimmeingabePlausiDefinitions(WahlartTemplate wahlartTemplate) {
    super();

    this.wahlartTemplate = wahlartTemplate;

    this.wahlart = new Wahlart();
    this.wahlart.setName(wahlartTemplate.getWahlartName());
    this.wahlart.setKurzname(wahlartTemplate.getKurzname());
    this.wahlart.setKennzeichen(wahlartTemplate.getKennzeichen());
    this.wahlart.setAnzahlStimmzettel(wahlartTemplate.getAnzahlStimmzettel());
    this.wahlart.setCommonWahlart(wahlartTemplate.getCommonWahlart());

    this.stimmeingabePlausiKonfigurationDefaultsVorlaeufig = null;
    this.stimmeingabePlausiKonfigurationDefaultsEndgueltig = null;

    this.stimmeingabePlausiFormatter = null;
  }

  @Override
  public boolean isApplicable(Wahl wahl) {
    return wahl != null && isApplicable(wahl.getWahlart());
  }

  public boolean isApplicable(Wahlart wahlart) {
    return getWahlartTemplateRelevant().equalsWahlart(wahlart);
  }

  public WahlartTemplate getWahlartTemplateRelevant() {
    return wahlartTemplate;
  }

  private Wahlart getWahlartRelevant() {
    return wahlart;
  }

  protected Collection<StimmeingabePlausiErgebniseingangsart> collectErgebniseingangsartenRelevant() {
    Collection<StimmeingabePlausiErgebniseingangsart> result = new ArrayList<>(EnumSet.allOf(StimmeingabePlausiErgebniseingangsart.class));
    result.removeIf(StimmeingabePlausiErgebniseingangsart::isUnbekannt);

    return result;
  }

  @Override
  public Collection<StimmeingabePlausiGebietsart> collectGebietsartenRelevant() {
    Collection<StimmeingabePlausiGebietsart> result = new ArrayList<>(EnumSet.allOf(StimmeingabePlausiGebietsart.class));
    result.removeIf(StimmeingabePlausiGebietsart::isUnbekannt);

    return result;
  }

  private Map<StimmeingabePlausiGebietsart, Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration>> getStimmeingabePlausiKonfigurationDefaultsVorlaeufig(
      StimmeingabePlausiDefinitionsContext context, Wahl wahl) {
    if (stimmeingabePlausiKonfigurationDefaultsVorlaeufig == null) {
      stimmeingabePlausiKonfigurationDefaultsVorlaeufig = new HashMap<>();

      for (StimmeingabePlausiGebietsart gebietsart : StimmeingabePlausiGebietsart.values()) {
        stimmeingabePlausiKonfigurationDefaultsVorlaeufig.put(gebietsart, new HashMap<>());
      }

      StimmeingabePlausiErgebniseingangsart ergebniseingangsart = StimmeingabePlausiErgebniseingangsart.VORLAEUFIG;

      initDefaultsAlleGebietsarten(context, wahl, ergebniseingangsart);
      initDefaultsUrnenwahlbezirk(context, wahl, ergebniseingangsart);
      initDefaultsUrnenwahlbezirkSonderstimmbezirk(context, wahl, ergebniseingangsart);
      initDefaultsBriefwahlbezirk(context, wahl, ergebniseingangsart);
      initDefaultsBriefwahlbezirkSonderstimmbezirk(context, wahl, ergebniseingangsart);
    }

    return stimmeingabePlausiKonfigurationDefaultsVorlaeufig;
  }

  private Map<StimmeingabePlausiGebietsart, Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration>> getStimmeingabePlausiKonfigurationDefaultsEndgueltig(
      StimmeingabePlausiDefinitionsContext context, Wahl wahl) {
    if (stimmeingabePlausiKonfigurationDefaultsEndgueltig == null) {
      stimmeingabePlausiKonfigurationDefaultsEndgueltig = new HashMap<>();

      for (StimmeingabePlausiGebietsart gebietsart : StimmeingabePlausiGebietsart.values()) {
        stimmeingabePlausiKonfigurationDefaultsEndgueltig.put(gebietsart, new HashMap<>());
      }

      StimmeingabePlausiErgebniseingangsart ergebniseingangsart = StimmeingabePlausiErgebniseingangsart.ENDGUELTIG;

      initDefaultsAlleGebietsarten(context, wahl, ergebniseingangsart);
      initDefaultsUrnenwahlbezirk(context, wahl, ergebniseingangsart);
      initDefaultsUrnenwahlbezirkSonderstimmbezirk(context, wahl, ergebniseingangsart);
      initDefaultsBriefwahlbezirk(context, wahl, ergebniseingangsart);
      initDefaultsBriefwahlbezirkSonderstimmbezirk(context, wahl, ergebniseingangsart);
    }

    return stimmeingabePlausiKonfigurationDefaultsEndgueltig;
  }

  protected void initDefaultsAlleGebietsarten(StimmeingabePlausiDefinitionsContext context, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart) {
    for (StimmeingabePlausiGebietsart gebietsart : StimmeingabePlausiGebietsart.values()) {
      initDefault(ergebniseingangsart, gebietsart, STIMMEINGABE_ANZAHL_KONVERTIERBAR, ERROR);

      if (ergebniseingangsart.isEndgueltig()) {
        initDefault(ergebniseingangsart, gebietsart, STIMMEINGABE_ANZAHL_ENDGUELTIG_GLEICH_STIMMEINGABE_ANZAHL_VORLAEUFIG, WARNING);
      }

      initDefault(ergebniseingangsart, gebietsart, PRUEFKENNZEICHEN_WAHLBERECHTIGTE_ERFASST, ERROR);
      initDefault(ergebniseingangsart, gebietsart, PRUEFKENNZEICHEN_WAHLBERECHTIGTE_GLEICH_PRUEFKENNZEICHEN_WAHLBERECHTIGTE_AM_GEBIET, ERROR);

      initDefault(ergebniseingangsart, gebietsart, WAEHLER_GESAMT_ANZAHL_GLEICH_WAEHLER_ANZAHLEN_SUMME, ERROR);
      initDefault(ergebniseingangsart, gebietsart, WAEHLER_GESAMT_ANZAHL_GROESSER_NULL, ERROR);
      initDefault(ergebniseingangsart, gebietsart, WAEHLER_GESAMT_ANZAHL_GROESSER_GLEICH_MINDESTANZAHL, WARNING, new StimmeingabeAnzahlComparedToAnzahlPlausiCustomData(50L)); // ELECTKOM-271
    }
  }

  protected void initDefaultsUrnenwahlbezirk(StimmeingabePlausiDefinitionsContext context, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart) {
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, ERROR);

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, ERROR);

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, ERROR);

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_ANZAHL_AM_GEBIET, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_WAHLBERECHTIGTE_ANZAHLEN_SUMME, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAHLBERECHTIGTE_GESAMT_ANZAHL_GROESSER_NULL, ERROR);

    /*
     * NOTE ELECTMUC-2230, dass StimmeingabePlausiTyp#WAEHLER_OHNE_WAHLSCHEIN_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL und
     * StimmeingabePlausiTyp#WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL sich gegenseitig ausschließen sollten!
     */
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_OHNE_WAHLSCHEIN_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_GLEICH_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_OHNE_WAHLSCHEIN_BERECHNET_ANZAHL_KLEINER_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_ANTEIL, WARNING,
        new WaehlerOhneWahlscheinAnzahlBerechnetComparedToStimmeingabeAnzahlAnteilPlausiCustomData(0.98D));

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_GLEICH_WAEHLER_GESAMT_ANZAHL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAEHLER_GESAMT_ANZAHL, WARNING); // ELECT-9171
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_MIT_WAHLSCHEIN_ANZAHL_KLEINER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_ANTEIL, WARNING,
        new StimmeingabeAnzahlComparedToStimmeingabeAnzahlAnteilPlausiCustomData(0.95D));

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_AUS_BRIEFWAHLBEZIRK_ERFASST_IN_URNENWAHLBEZIRK_ANZAHL_KLEINER_GLEICH_WAEHLER_MIT_WAHLSCHEIN_ANZAHL, ERROR);

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_GESAMT_ANZAHL_GROESSER_WAHLBERECHTIGTE_GESAMT_ANZAHL_ANTEIL, WARNING,
        new StimmeingabeAnzahlComparedToStimmeingabeAnzahlAnteilPlausiCustomData(0.20D));
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK, WAEHLER_GESAMT_ANZAHL_KLEINER_WAHLBERECHTIGTE_GESAMT_ANZAHL_ANTEIL, WARNING,
        new StimmeingabeAnzahlComparedToStimmeingabeAnzahlAnteilPlausiCustomData(0.95D));
  }

  protected void initDefaultsUrnenwahlbezirkSonderstimmbezirk(StimmeingabePlausiDefinitionsContext context, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart) {
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK_SONDERSTIMMBEZIRK, WAHLBERECHTIGTE_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_NULL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK_SONDERSTIMMBEZIRK, WAHLBERECHTIGTE_MIT_WAHLSCHEIN_ANZAHL_GLEICH_NULL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK_SONDERSTIMMBEZIRK, WAHLBERECHTIGTE_NICHT_IM_WAEHLERVERZEICHNIS_ANZAHL_GLEICH_NULL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK_SONDERSTIMMBEZIRK, WAHLBERECHTIGTE_GESAMT_ANZAHL_GLEICH_NULL, ERROR);

    initDefault(ergebniseingangsart, URNENWAHLBEZIRK_SONDERSTIMMBEZIRK, WAEHLER_OHNE_WAHLSCHEIN_ANZAHL_GLEICH_NULL, ERROR);
    initDefault(ergebniseingangsart, URNENWAHLBEZIRK_SONDERSTIMMBEZIRK, WAEHLER_MIT_WAHLSCHEIN_ANZAHL_GLEICH_WAEHLER_GESAMT_ANZAHL, ERROR);
  }

  protected void initDefaultsBriefwahlbezirk(StimmeingabePlausiDefinitionsContext context, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart) {
    // Nur allgemeine Plausis...
  }

  protected void initDefaultsBriefwahlbezirkSonderstimmbezirk(StimmeingabePlausiDefinitionsContext context, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart) {
    // Nur allgemeine Plausis...
  }

  protected void initDefault(StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart, StimmeingabePlausiTyp stimmeingabePlausiTyp,
      StimmeingabePlausiFehlerart fehlerart) {
    initDefault(ergebniseingangsart, gebietsart, stimmeingabePlausiTyp, fehlerart, stimmeingabePlausiTyp.newCustomDataInstance());
  }

  protected void initDefault(StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart, StimmeingabePlausiTyp stimmeingabePlausiTyp,
      StimmeingabePlausiFehlerart fehlerart, StimmeingabePlausiKonfigurationCustomData customData) {
    if (ergebniseingangsart.isVorlaeufig()) {
      set(stimmeingabePlausiKonfigurationDefaultsVorlaeufig, ergebniseingangsart, gebietsart, stimmeingabePlausiTyp, fehlerart, customData);
    }

    if (ergebniseingangsart.isEndgueltig()) {
      set(stimmeingabePlausiKonfigurationDefaultsEndgueltig, ergebniseingangsart, gebietsart, stimmeingabePlausiTyp, fehlerart, customData);
    }
  }

  private void set(Map<StimmeingabePlausiGebietsart, Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration>> stimmeingabePlausiKonfiguration,
      StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart, StimmeingabePlausiTyp stimmeingabePlausiTyp, StimmeingabePlausiFehlerart fehlerart,
      StimmeingabePlausiKonfigurationCustomData customData) {
    if (!gebietsart.isUnbekannt()) {
      stimmeingabePlausiKonfiguration.get(gebietsart).put(stimmeingabePlausiTyp,
          createStimmeingabePlausiKonfiguration(getWahlartRelevant(), ergebniseingangsart, gebietsart, stimmeingabePlausiTyp, fehlerart, customData));
    }
  }

  protected Optional<StimmeingabePlausiKonfiguration> findStimmeingabePlausiKonfiguration(StimmeingabePlausiDefinitionsContext context, Wahl wahl, WahlartErgebnisart ergebnisart,
      StimmeingabePlausiGebietsart gebietsart, StimmeingabePlausiTyp stimmeingabePlausiTyp) {
    Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationPerStimmeingabePlausiTyp = collectAllStimmeingabePlausiKonfigurationPerStimmeingabePlausiTyp(context,
        wahl, StimmeingabePlausiErgebniseingangsart.of(ergebnisart), gebietsart);

    if (stimmeingabePlausiKonfigurationPerStimmeingabePlausiTyp.containsKey(stimmeingabePlausiTyp)) {
      StimmeingabePlausiKonfiguration result = stimmeingabePlausiKonfigurationPerStimmeingabePlausiTyp.get(stimmeingabePlausiTyp);

      if (result != null) {
        return Optional.of(result);
      }
    }

    return Optional.empty();
  }

  @Override
  public Collection<StimmeingabePlausiKonfiguration> collectStimmeingabePlausiKonfigurationDefiniert(StimmeingabePlausiDefinitionsContext context,
      StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration, Wahl wahl) {
    Collection<StimmeingabePlausiKonfiguration> result = new ArrayList<>();

    for (StimmeingabePlausiErgebniseingangsart ergebniseingangsart : collectErgebniseingangsartenRelevant()) {
      for (StimmeingabePlausiGebietsart gebietsart : collectGebietsartenRelevant()) {
        result.addAll(collectStimmeingabePlausiKonfigurationDefiniert(context, stimmeingabeKopfzahlKonfiguration, wahl, ergebniseingangsart, gebietsart));
      }
    }

    return result;
  }

  @Override
  public Collection<StimmeingabePlausiKonfiguration> collectStimmeingabePlausiKonfigurationDefiniert(StimmeingabePlausiDefinitionsContext context,
      StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart) {
    Predicate< ? super StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationDefiniertFilter = stimmeingabePlausiKonfiguration -> true; // ELECT-8075

    return collectStimmeingabePlausiKonfiguration(context, wahl, ergebniseingangsart, gebietsart, stimmeingabePlausiKonfigurationDefiniertFilter);
  }

  @Override
  public Collection<StimmeingabePlausiKonfiguration> collectStimmeingabePlausiKonfigurationRelevant(StimmeingabePlausiDefinitionsContext context,
      StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration, Wahl wahl) {
    Collection<StimmeingabePlausiKonfiguration> result = new ArrayList<>();

    for (StimmeingabePlausiErgebniseingangsart ergebniseingangsart : collectErgebniseingangsartenRelevant()) {
      for (StimmeingabePlausiGebietsart gebietsart : collectGebietsartenRelevant()) {
        result.addAll(collectStimmeingabePlausiKonfigurationRelevant(context, stimmeingabeKopfzahlKonfiguration, wahl, ergebniseingangsart, gebietsart));
      }
    }

    return result;
  }

  @Override
  public Collection<StimmeingabePlausiKonfiguration> collectStimmeingabePlausiKonfigurationRelevant(StimmeingabePlausiDefinitionsContext context,
      StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration, Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart) {
    Predicate< ? super StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationRelevantFilter = stimmeingabePlausiKonfiguration -> isStimmeingabePlausiKonfigurationRelevant(context, wahl,
        ergebniseingangsart, Optional.of(gebietsart), stimmeingabePlausiKonfiguration.getTyp(), stimmeingabeKopfzahlKonfiguration.getUmgangAWerteKonfiguration(),
        stimmeingabeKopfzahlKonfiguration.getUmgangBWerteKonfiguration());

    return collectStimmeingabePlausiKonfiguration(context, wahl, ergebniseingangsart, gebietsart, stimmeingabePlausiKonfigurationRelevantFilter);
  }

  protected boolean isStimmeingabePlausiKonfigurationRelevant(StimmeingabePlausiDefinitionsContext context, Wahl wahl, WahlartErgebnisart ergebnisart,
      Optional<StimmeingabePlausiGebietsart> gebietsart, StimmeingabePlausiTyp stimmeingabePlausiTyp, WasUmgangAWerteKonfiguration umgangAWerteKonfiguration,
      WasUmgangBWerteKonfiguration umgangBWerteKonfiguration) {
    return stimmeingabePlausiTyp.isRelevant(wahl, ergebnisart, gebietsart, umgangAWerteKonfiguration, umgangBWerteKonfiguration);
  }

  private Collection<StimmeingabePlausiKonfiguration> collectStimmeingabePlausiKonfiguration(StimmeingabePlausiDefinitionsContext context, Wahl wahl,
      StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart, Predicate< ? super StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationFilter) {
    if (collectGebietsartenRelevant().contains(gebietsart)) {
      return collectAllStimmeingabePlausiKonfigurationPerStimmeingabePlausiTyp(context, wahl, ergebniseingangsart, gebietsart).values().stream() //
          .filter(stimmeingabePlausiKonfigurationFilter) //
          .collect(toList());
    }

    return emptyList();
  }

  private Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration> collectAllStimmeingabePlausiKonfigurationPerStimmeingabePlausiTyp(StimmeingabePlausiDefinitionsContext context, Wahl wahl,
      StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart) {
    Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration> result = new HashMap<>();

    result.putAll(mapAllStimmeingabePlausiKonfigurationDefaults(context, wahl, ergebniseingangsart, gebietsart));

    AbstractStimmeingabePlausiKonfigurationDAO stimmeingabePlausiKonfigurationDAO = context.getStimmeingabePlausiKonfigurationDAO();

    for (Entry<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationInDbPerTyp : mapAllStimmeingabePlausiKonfigurationenInDb(context, wahl, ergebniseingangsart,
        gebietsart).entrySet()) {
      StimmeingabePlausiTyp stimmeingabePlausiTyp = stimmeingabePlausiKonfigurationInDbPerTyp.getKey();

      StimmeingabePlausiKonfiguration stimmeingabePlausiKonfigurationDefault = result.get(stimmeingabePlausiTyp);

      if (stimmeingabePlausiKonfigurationDefault != null) { // Datenbank-Plausis ohne Default sind nicht mehr relevant und werden ignoriert
        StimmeingabePlausiKonfiguration stimmeingabePlausiKonfigurationInDb = stimmeingabePlausiKonfigurationInDbPerTyp.getValue();

        // damit die Datenbank-Plausis unten nicht mit den Default-Werten gefüllt werden
        stimmeingabePlausiKonfigurationDAO.detach(stimmeingabePlausiKonfigurationInDb);

        stimmeingabePlausiKonfigurationInDb.setStimmeingabePlausiKonfigurationDefault(stimmeingabePlausiKonfigurationDefault);

        result.put(stimmeingabePlausiTyp, stimmeingabePlausiKonfigurationInDb);
      }
    }

    for (StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration : result.values()) {
      fillInStimmeingabePlausiKonfigurationValuesLocalizated(wahl.getWahlart(), stimmeingabePlausiKonfiguration); // unten ist hier
      stimmeingabePlausiKonfiguration.setWahl(wahl);
    }

    return result;
  }

  protected Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration> mapAllStimmeingabePlausiKonfigurationDefaults(StimmeingabePlausiDefinitionsContext context, Wahl wahl,
      StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart) {
    if (ergebniseingangsart.isVorlaeufig()) {
      return getStimmeingabePlausiKonfigurationDefaultsVorlaeufig(context, wahl).get(gebietsart);
    }

    if (ergebniseingangsart.isEndgueltig()) {
      return getStimmeingabePlausiKonfigurationDefaultsEndgueltig(context, wahl).get(gebietsart);
    }

    return emptyMap();
  }

  protected Map<StimmeingabePlausiTyp, StimmeingabePlausiKonfiguration> mapAllStimmeingabePlausiKonfigurationenInDb(StimmeingabePlausiDefinitionsContext context, Wahl wahl,
      StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart) {
    return context.getStimmeingabePlausiKonfigurationDAO().findByWahlAndErgebniseingangsartAndGebietsart(wahl, ergebniseingangsart, gebietsart).stream() //
        .collect(toMap(StimmeingabePlausiKonfiguration::getTyp, Function.identity()));
  }

  public StimmeingabePlausiKonfiguration findStimmeingabePlausiKonfigurationOrLegacyDefaults(StimmeingabePlausiDefinitionsContext context,
      StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration, Ergebniseingang ergebniseingang, StimmeingabePlausiTyp stimmeingabePlausiTyp, StimmeingabePlausiFehlerart fehlerart,
      StimmeingabePlausiKonfigurationCustomData customData) {
    StimmeingabePlausiErgebniseingangsart ergebniseingangsart;

    if (ergebniseingang == null) {
      ergebniseingangsart = StimmeingabePlausiErgebniseingangsart.UNBEKANNT;
    } else {
      ergebniseingangsart = StimmeingabePlausiErgebniseingangsart.of(ergebniseingang.getErgebniseingangsart());
    }

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = ergebniseingang.getWahldurchfuehrungsgebiet();
    Wahl wahl = wahldurchfuehrungsgebiet.getWahl();

    Optional<StimmeingabePlausiGebietsart> gebietsartIfPresent = stimmeingabeKopfzahlKonfiguration.findStimmeingabePlausiGebietsart(wahldurchfuehrungsgebiet);

    return gebietsartIfPresent //
        .flatMap(gebietsart -> findStimmeingabePlausiKonfiguration(context, wahl, ergebniseingangsart, gebietsart, stimmeingabePlausiTyp)) //
        .orElseGet(() -> {
          StimmeingabePlausiGebietsart gebietsart = gebietsartIfPresent.orElse(StimmeingabePlausiGebietsart.UNBEKANNT);

          LOG.warn("KEINE StimmeingabeKopfzahlKonfiguration gefunden [" //
              + "wahl = " + wahl.getName() + ", " //
              + "wahlart = " + wahl.getWahlart().getWahlartNameNormalized() + ", " //
              + "gebiet = " + wahldurchfuehrungsgebiet.getNummer() + " [urnenwahlgebiet = " + wahldurchfuehrungsgebiet.isUrnenwahlgebiet() + ", briefwahlgebiet = "
              + wahldurchfuehrungsgebiet.isBriefwahlgebiet() + ", sonderstimmbezirk = " + wahldurchfuehrungsgebiet.getSonderstimmbezirk() + "], " //
              + "ergebniseingangsart = " + ergebniseingangsart + ", " //
              + "gebietsart = " + gebietsart + ", " //
              + "stimmeingabePlausiTyp = " + stimmeingabePlausiTyp + ", " //
              + "fehlerart = " + fehlerart + ", " //
              + "customData = " + customData.getClass().getSimpleName() //
              + "]");

          return createStimmeingabePlausiKonfiguration(wahl, ergebniseingangsart, gebietsart, stimmeingabePlausiTyp, fehlerart, customData);
        });
  }

  private StimmeingabePlausiKonfiguration createStimmeingabePlausiKonfiguration(Wahl wahl, StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart,
      StimmeingabePlausiTyp stimmeingabePlausiTyp, StimmeingabePlausiFehlerart fehlerart, StimmeingabePlausiKonfigurationCustomData customData) {
    StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration = createStimmeingabePlausiKonfiguration(wahl.getWahlart(), ergebniseingangsart, gebietsart, stimmeingabePlausiTyp, fehlerart,
        customData);
    stimmeingabePlausiKonfiguration.setWahl(wahl);

    return stimmeingabePlausiKonfiguration;
  }

  private StimmeingabePlausiKonfiguration createStimmeingabePlausiKonfiguration(Wahlart wahlart, StimmeingabePlausiErgebniseingangsart ergebniseingangsart, StimmeingabePlausiGebietsart gebietsart,
      StimmeingabePlausiTyp stimmeingabePlausiTyp, StimmeingabePlausiFehlerart fehlerart, StimmeingabePlausiKonfigurationCustomData customData) {

    StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration = new StimmeingabePlausiKonfiguration(customData) {

      private static final long serialVersionUID = 1L;

      @Override
      public Long getId() {
        return -1 * System.nanoTime(); // Dummy-ID, bspw. als Row-Key zur Anzeige in DataTable.
      }

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

    stimmeingabePlausiKonfiguration.setErgebniseingangsart(ergebniseingangsart);
    stimmeingabePlausiKonfiguration.setGebietsart(gebietsart);
    stimmeingabePlausiKonfiguration.setTyp(stimmeingabePlausiTyp);

    stimmeingabePlausiKonfiguration.setFehlerart(fehlerart);

    fillInStimmeingabePlausiKonfigurationValuesLocalizated(wahlart, stimmeingabePlausiKonfiguration);

    return stimmeingabePlausiKonfiguration;
  }

  @Override
  public void fillInStimmeingabePlausiKonfigurationValuesLocalizated(Wahlart wahlart, StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration) {
    StimmeingabePlausiErgebniseingangsart ergebniseingangsart = stimmeingabePlausiKonfiguration.getErgebniseingangsart();
    StimmeingabePlausiTyp stimmeingabePlausiTyp = stimmeingabePlausiKonfiguration.getTyp();
    StimmeingabePlausiFehlerart fehlerart = stimmeingabePlausiKonfiguration.getFehlerart();

    if (isBlank(stimmeingabePlausiKonfiguration.getBeschreibung())) {
      stimmeingabePlausiKonfiguration.setBeschreibung(getStimmeingabePlausiFormatter().formatBeschreibungFachlich(wahlart, ergebniseingangsart, stimmeingabePlausiTyp, fehlerart));
    }

    if (isBlank(stimmeingabePlausiKonfiguration.getFehlerschluessel())) {
      stimmeingabePlausiKonfiguration.setFehlerschluessel(getStimmeingabePlausiFormatter().formatFehlerschluessel(wahlart, ergebniseingangsart, stimmeingabePlausiTyp, fehlerart));
    }

    if (isBlank(stimmeingabePlausiKonfiguration.getFehlermeldungFormat())) {
      stimmeingabePlausiKonfiguration.setFehlermeldungFormat(getStimmeingabePlausiFormatter().createFehlermeldungFormat(wahlart, ergebniseingangsart, stimmeingabePlausiTyp, fehlerart));
    }
  }

  private StimmeingabePlausiFormatter getStimmeingabePlausiFormatter() {
    if (stimmeingabePlausiFormatter == null) {
      stimmeingabePlausiFormatter = new StimmeingabePlausiFormatter(getMessages());
    }

    return stimmeingabePlausiFormatter;
  }

  protected StimmeLabelMessages getMessages() {
    return BeanManagerUtil.getBeanByClass(StimmeLabelMessages.class);
  }
}
