
package de.ivu.elect.presentation.stimmeingabe;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;

import javax.annotation.Nullable;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;
import javax.inject.Named;

import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietSessionSelect;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte;
import de.ivu.elect.business.konfiguration.entity.KonfigurationExtendedLoader;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingabestatuswechelPruefer;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangExtendedDAO;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangKonfiguration;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangStatus;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassungLoeschen;
import de.ivu.elect.business.stimmeingabe.boundary.WasStimmeingabeBoundaryCreate;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabePlausiBuilder;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestatus;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingabestatuswechelPruefergebnis;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangErfasser;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamictable.ColumnType;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.jee.presentation.status.StatusEntry;
import de.ivu.elect.presentation.AbstractElectStatusView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.ergebnis.ErgebnisStatusInfo;
import de.ivu.elect.presentation.ergebnis.ErgebnisartUmschaltenView;
import de.ivu.elect.presentation.ergebnis.ViewHavingErgebnisStatusInfo;
import de.ivu.elect.presentation.ergebnis.WasErgebnisStatusInfo;
import de.ivu.elect.presentation.gebietsbaum.status.ErgebniseingangSeparatorStatusEntry;
import de.ivu.elect.presentation.gebietsbaum.status.ErgebniseingangStatusEntry;
import de.ivu.elect.presentation.gebietsbaum.tree.AbstractNoStoreGebietsbaumView;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.replikation.ReplikationPresentationStatus;
import de.ivu.elect.presentation.security.AbstractGebietSecurityFilter;
import de.ivu.elect.presentation.stimmeingabe.menu.ErfassungsschritteMenuBuilder;
import de.ivu.elect.presentation.stimmeingabe.status.ErgebniseingangPerErfassungsgebietCollector;
import de.ivu.elect.presentation.stimmeingabe.status.ErgebniseingangStatusEntryFactory;
import de.ivu.elect.presentation.system.table.DefaultStatusEntryColumn;
import de.ivu.elect.presentation.wahl.WahlStatusuebersichtView;


import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static de.ivu.elect.presentation.stimmeingabe.AbstractStimmeingabeErfassenView.loescheStimmeingaben;
import static java.util.stream.Collectors.toList;

@OnlineHilfeId("495")
@RequestScoped
@Named(ErgebniseingangStatusView.COMPONENT_NAME)
@View(viewId = ErgebniseingangStatusView.VIEW_ID, useCase = UseCasePermission.STIMMEINGABE_ANZEIGEN)
public class ErgebniseingangStatusView extends AbstractElectStatusView<ErgebniseingangStatusEntry> implements ErgebnisartUmschaltenView, ViewHavingErgebnisStatusInfo {

  private static final long serialVersionUID = 1L;

  public static final String VIEW_ID = "/stimmeingabe/ergebniseingang-status.xhtml";

  public static final String COMPONENT_NAME = "ergebniseingangStatusView";

  private static final int DEFAULT_POLL_TIMEOUT_IN_SECONDS = 30;

  protected static final int POLL_DISABLED = 60 * 60 * 24; // 24 Stunden, so gut wie deaktiviert (rendered geht nicht, weil Komponente auf Seite verwendet wird)

  @Inject
  private transient WahldurchfuehrungsgebietSessionSelect wahldurchfuehrungsgebietSelect;

  @Inject
  private transient WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietDao;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Inject
  private transient ErgebniseingangExtendedDAO ergebniseingangDao;

  @Inject
  private transient ErgebniseingangStatusEntryFactory ergebniseingangStatusEntryFactory;

  @Inject
  private transient StimmeingabeErfassenViewResolver stimmeingabeErfassenViewResolver;

  @Inject
  private transient AbstractNoStoreGebietsbaumView gebietsbaumView;

  @Inject
  private transient StimmeingabeErfassungLoeschen stimmeingabeErfassungLoeschen;

  @Inject
  private transient WasStimmeingabeBoundaryCreate stimmeingabeBoundaryCreate;

  @Inject
  private transient ErgebniseingangStatus ergebniseingangStatus;

  @Inject
  protected transient ErgebniseingangStatusViewHelper ergebniseingangStatusViewHelper;

  @Inject
  private transient ErgebniseingabestatuswechelPruefer ergebniseingabestatuswechelPruefer;

  @Inject
  private transient ReplikationPresentationStatus replikationPresentationStatus;

  @Inject
  private transient StimmeingabeErfassenViewErgebniseingangsartStore stimmeingabeErfassenViewErgebniseingangsartStore;

  @View
  @Inject
  private transient WahlStatusuebersichtView wahlStatusuebersichtView;

  @Inject
  private transient ErgebniseingangKonfiguration ergebniseingangKonfiguration;

  @Inject
  private transient ErfassungsschritteMenuBuilder erfassungsschritteMenuBuilder;

  @Inject
  private transient KonfigurationExtendedLoader konfigurationExtendedLoader;

  public ErgebniseingangStatusView() {
    super();
  }

  @Override
  protected AbstractGebietSecurityFilter<ErgebniseingangStatusEntry> getGebietSecurityFilter() {
    return AbstractGebietSecurityFilter.createEntityMitGebietSecurityFilter(getEinsehenGebietSecurity());
  }

  public void preRender() {
    if (getSelectedGebiet() == null || getSelectedErgebnis() == null) {
      return;
    }

    for (Ergebniseingabestatus ergebniseingabestatusZuSetzen : Ergebniseingabestatus.values()) {
      ErgebniseingabestatuswechelPruefergebnis result = ergebniseingabestatuswechelPruefer.isStatusuebergangMoeglich(getSelectedErgebnis(), getSelectedGebiet(), ergebniseingabestatusZuSetzen,
          hasPermission(UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_FREIGEGEBEN_ZURUECKNEHMEN));

      if (!result.isUebergangMoeglich() && result.isMessageVorhanden()) {
        addWarningMessage(result.getMsgKey(), result.getParams());
      }
    }
  }

  @Nullable
  @PreConditionCheck
  public String checkCondition() {
    if (!isGebietSelected()) {
      return getStartViewId();
    }

    // Auf unterster Ebene in die Erfassung wechseln
    if (stimmeingabeErfassenViewResolver.isErfassungsgebietSelected() || stimmeingabeErfassenViewResolver.isAusbleibendesErfassungsgebietSelected()) {
      return stimmeingabeErfassenViewResolver.getViewId();
    }

    return null;
  }

  protected boolean isGebietSelected() {
    return wahldurchfuehrungsgebietSelect.isSelected();
  }

  protected Wahldurchfuehrungsgebiet getSelectedGebiet() {
    return wahldurchfuehrungsgebietSelect.getEntity();
  }

  public String getGebietName() {
    return formatGebietName(getSelectedGebiet());
  }

  public boolean isBtnEingangsartWechselnDisabled() {
    return !isErgebnisSelected();
  }

  public boolean isBtnEingangsartWechselnRendered() {
    return hasPermission(UseCasePermission.STIMMEINGABE_ERGEBNISEINGANGSART_WECHSELN) && isWahlartLaesstEingangsartWechselZu() && isFortschrittErfassungRendered();
  }

  private boolean isWahlartLaesstEingangsartWechselZu() {
    if (getSelectedWahldurchfuehrung() == null) {
      return false;
    }

    return stimmeingabeErfassenViewErgebniseingangsartStore.isErgebniseingangsartVerschiedenVonWahl(getSelectedWahldurchfuehrung()) // zurück immer
        || getSelectedWahldurchfuehrung().getWahlart().getErgebnisartUmschaltbar().isUmschaltbarErgebniseingangStatus(getErgebnisart());
  }

  public String getLabelEingangsartWechseln() {
    Ergebniseingangsart ergebniseingangsart = getErgebniseingangsart();

    if (ergebniseingangsart.getArtNachWechsel().isVorlaeufig()) {
      return getMessage("ergebniseingang.status.ergebnisart.wechseln.vorlaeufig");
    }

    return getMessage("ergebniseingang.status.ergebnisart.wechseln.endgueltig");
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.STIMMEINGABE_ERGEBNISEINGANGSART_WECHSELN)
  public String eingangsartWechseln() {
    if (isBtnEingangsartWechselnRendered() && !isBtnEingangsartWechselnDisabled()) {
      getStimmeingabeErfassenViewErgebniseingangsartStore().ergebniseEingangsartWechseln(getSelectedWahldurchfuehrung());

      ergebniseingangsartVerschiedenVonWahl = null;

      // nervt? addInfoMessage("ergebniseingang.status.ergebnisart.gewechselt", getMessage(getErgebniseingangsart()));
    }

    return VIEW_ID;
  }

  protected boolean isErgebnisSelected() {
    return getSelectedErgebnis() != null;
  }

  protected Ergebnis getSelectedErgebnis() {
    return getStimmeingabeErfassenViewErgebniseingangsartStore().getSelectedErgebnis();
  }

  @Override
  // @Redirect // Redirect nicht notwendig => onGebietSelectChangeEvent(), sonst Exception
  @UseCaseSecure(UseCasePermission.STIMMEINGABE_ANZEIGEN)
  @Nullable
  public String redirectTo(ErgebniseingangStatusEntry gebietsbaumStatusEntry) {
    Gebiet gebiet = gebietsbaumStatusEntry.getGebiet();

    if (gebiet != null) {
      wahldurchfuehrungsgebietSelect.setEntity(gebiet);
    }

    // Redirect nicht notwendig => onGebietSelectChangeEvent(), sonst Exception
    return null; // stimmeingabeErfassenViewResolver.getViewId();
  }

  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    reset();

    Gebiet neueEntity = event.getNeueEntity();

    if (neueEntity != null) { // ELECTDD-404 Abmeldung
      // Wenn Erfassungsgebiet => alles neu laden
      String targetViewId = stimmeingabeErfassenViewResolver.getViewId();

      if (!VIEW_ID.equals(targetViewId)) {
        gebietsbaumView.reset();

        FacesContextUtil.redirectToView(targetViewId);
      }
    }
  }

  @Override
  protected List<ErgebniseingangStatusEntry> bestimmeStatusEntriesInternal() {
    List<ErgebniseingangStatusEntry> result = new ArrayList<>();

    addStatusEntriesFuerGebiet(result, getSelectedGebiet(), getLetzterErgebniseingangPerErfassungsgebietEinsehbar(), getErgebniseingangsart());

    entferneLetztenSeparator(result);

    return result;
  }

  void entferneLetztenSeparator(List<ErgebniseingangStatusEntry> result) {
    int size = result.size();

    if (size > 0 && result.get(size - 1).isSeparator()) {
      result.remove(size - 1);
    }
  }

  private void addStatusEntriesFuerGebiet(List<ErgebniseingangStatusEntry> statusEntries, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      Map<Wahldurchfuehrungsgebiet, Ergebniseingang> letzterErgebniseingangPerErfassungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    if (wahldurchfuehrungsgebiet == null) {
      return;
    }

    if (wahldurchfuehrungsgebiet.isErfassungsgebietLautGebietsart()) {
      statusEntries.add(ergebniseingangStatusEntryFactory.create(wahldurchfuehrungsgebiet,
          letzterErgebniseingangPerErfassungsgebiet.get(wahldurchfuehrungsgebiet),
          ergebniseingangsart));
    } else {
      List<Wahldurchfuehrungsgebiet> untergeordneteGebiete = getUntergeordneteGebiete(wahldurchfuehrungsgebiet);

      untergeordneteGebiete.sort(GebietByBreadthFirstComparator.INSTANCE);

      for (Wahldurchfuehrungsgebiet untergebiet : untergeordneteGebiete) {
        addStatusEntriesFuerGebiet(statusEntries, untergebiet, letzterErgebniseingangPerErfassungsgebiet, ergebniseingangsart);
      }

      if (!statusEntries.isEmpty() && !statusEntries.get(statusEntries.size() - 1).isSeparator()) {
        statusEntries.add(new ErgebniseingangSeparatorStatusEntry());
      }
    }
  }

  private ArrayList<Wahldurchfuehrungsgebiet> getUntergeordneteGebiete(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new ArrayList<>(wahldurchfuehrungsgebietDao.findByUebergeordnetesGebiet(wahldurchfuehrungsgebiet));
  }

  public boolean isBtnEingaengeLoeschenRendered() {
    return hasPermission(UseCasePermission.STIMMEINGABE_DELETE_FUER_AUSGEWAEHLTE_GEBIETE) && isRechtZumBearbeitenVorhanden() && !isBtnEingaengeLoeschenDisabled() /* ELECT-5904 */
        && !isErgebniseingangsartVerschiedenVonWahl();
  }

  public boolean isBtnEingaengeLoeschenDisabled() {
    return !isErgebnisSelected() || getLetzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar(getEingangsherkunftIgnorierenFuerLoeschen()).isEmpty() || !isRechtZumBearbeitenVorhanden();
  }

  private BooleanSupplier getEingangsherkunftIgnorierenFuerLoeschen() {
    return () -> getPrincipal().hasUseCasePermission(UseCasePermission.STIMMEINGABE_CREATE_EINGANGSHERKUNFT_IGNORIEREN);
  }

  private boolean isRechtZumBearbeitenVorhanden() {
    return isRechtZumBearbeitenVorhanden(getSelectedGebiet());
  }

  private transient Boolean isSlave;

  private boolean isRechtZumBearbeitenVorhanden(Gebiet gebiet) {
    return getBearbeitenGebietSecurity().isRechtVorhanden(gebiet) && !isSlave();
  }

  private boolean isRechtZumEinsehenVorhanden(Gebiet gebiet) {
    return getEinsehenGebietSecurity().isRechtVorhanden(gebiet);
  }

  // WASDE-1153
  protected boolean isSlave() {
    if (isSlave == null) {
      isSlave = replikationPresentationStatus.isSlave();
    }

    return isSlave;
  }

  private boolean isGebietEingabeMoeglich(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, BooleanSupplier eingangsherkunftIgnorieren) {
    return wahldurchfuehrungsgebiet.isEingabeMoeglich(getErgebnisart(), AbstractStimmeingabeErfassenView.ERGEBNISEINGANGSHERKUNFT_MANUELLE_ERFASSUNG, eingangsherkunftIgnorieren);
  }

  private boolean isGebietEingabeNichtMoeglich(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, BooleanSupplier eingangsherkunftIgnorieren) {
    return !isGebietEingabeMoeglich(wahldurchfuehrungsgebiet, eingangsherkunftIgnorieren);
  }

  private Ergebnisart getErgebnisart() {
    return getSelectedErgebnis().getErgebnisart();
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.STIMMEINGABE_DELETE_FUER_AUSGEWAEHLTE_GEBIETE)
  public String doEingaengeLoeschen() {
    if (isErgebnisSelected()) {
      Collection<Wahldurchfuehrungsgebiet> untergeordneteErfassungsgebieteBearbeitbar = getLetzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar(
          getEingangsherkunftIgnorierenFuerLoeschen()).keySet();

      if (!untergeordneteErfassungsgebieteBearbeitbar.isEmpty()) {
        loescheStimmeingaben(untergeordneteErfassungsgebieteBearbeitbar, getSelectedErgebnis(), stimmeingabeErfassungLoeschen, getErfasser());

        addInfoMessage("ergebniseingang.status.action.eingaenge.loeschen.success.message");

        reset();
      }
    }

    return VIEW_ID;
  }

  // WICHTIG: Bei Änderungen auch die Logik in AbstractStimmeingabeErfassenView anpassen!!!
  public boolean isBtnFehlerBestaetigenRendered() {
    return hasPermission(UseCasePermission.STIMMEINGABE_FEHLER_BESTAETIGEN_FUER_AUSGEWAEHLTE_GEBIETE) && isRechtZumBearbeitenVorhanden() && !isBtnFehlerBestaetigenDisabled() /* ELECT-5904 */
        && !isErgebniseingangsartVerschiedenVonWahl();
  }

  // WICHTIG: Bei Änderungen auch die Logik in AbstractStimmeingabeErfassenView anpassen!!!
  public boolean isBtnFehlerBestaetigenDisabled() {
    return getLetzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar(getEingangsherkunftIgnorierenFuerKannFehlerBestaetigen()).values().stream().noneMatch(this::existsKannFehler)
        || !isRechtZumBearbeitenVorhanden();
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.STIMMEINGABE_FEHLER_BESTAETIGEN_FUER_AUSGEWAEHLTE_GEBIETE)
  public String doFehlerBestaetigen() {
    Map<Wahldurchfuehrungsgebiet, Ergebniseingang> letzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar = getLetzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar(
        getEingangsherkunftIgnorierenFuerKannFehlerBestaetigen());

    if (!letzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar.isEmpty()) {
      Ergebniseingangsart ergebniseingangsart = getSelectedWahldurchfuehrung().getErgebniseingangsart();

      letzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar.forEach((erfassungsgebiet, letzterErgebniseingang) -> {
        if (existsKannFehler(letzterErgebniseingang)) {
          Ergebniseingang aktuellerErgebniseingang = createErgebniseingang(erfassungsgebiet, ergebniseingangsart, letzterErgebniseingang.getStimmeingaben());

          stimmeingabeBoundaryCreate.saveUndKannFehlerFuerBenutzerBestaetigen(aktuellerErgebniseingang, letzterErgebniseingang,
              StimmeingabePlausiBuilder.NO_OP_BUILDER /* ELECT-7009 Konkreter Plausibuilder hier nicht vorhanden */);
        }
      });

      reset();

      addInfoMessage("ergebniseingang.status.action.fehler.bestaetigen.success.message");
    }

    return VIEW_ID;
  }

  private BooleanSupplier getEingangsherkunftIgnorierenFuerKannFehlerBestaetigen() {
    return () -> true;
  }

  private Ergebniseingang createErgebniseingang(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart, Collection<Stimmeingabe> stimmeingaben) {
    Ergebniseingang ergebniseingang = new Ergebniseingang();
    ergebniseingang.setErfasser(getErfasser().getKennung());
    ergebniseingang.setErgebniseingangsherkunft(AbstractStimmeingabeErfassenView.ERGEBNISEINGANGSHERKUNFT_MANUELLE_ERFASSUNG);
    ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebiet);
    ergebniseingang.setErgebniseingangsart(ergebniseingangsart);

    stimmeingaben.forEach(stimmeingabe -> ergebniseingang.addStimmeingabe(createStimmeingabe(ergebniseingang, stimmeingabe)));

    return ergebniseingang;
  }

  private ErgebniseingangErfasser getErfasser() {
    return ErgebniseingangErfasser.benutzer(getPrincipal());
  }

  private Stimmeingabe createStimmeingabe(Ergebniseingang ergebniseingang, Stimmeingabe template) {
    Stimmeingabe stimmeingabe = new Stimmeingabe();

    if (template.isInstanceOf(ZwischensummeEingabe.class)) {
      stimmeingabe = createZwischensummeEingabe(castTo(template, ZwischensummeEingabe.class));
    }

    stimmeingabe.setErgebniseingang(ergebniseingang);
    stimmeingabe.setStimmenart(template.getStimmenart());
    stimmeingabe.setAnzahlEingabe(template.getAnzahlEingabe());

    stimmeingabe.setGruppeImGebiet(template.getGruppeImGebiet());
    stimmeingabe.setListenkandidatur(template.getListenkandidatur());
    stimmeingabe.setReferendumOption(template.getReferendumOption());

    return stimmeingabe;
  }

  private ZwischensummeEingabe createZwischensummeEingabe(ZwischensummeEingabe template) {
    ZwischensummeEingabe zwischensummeEingabe = new ZwischensummeEingabe();
    zwischensummeEingabe.setSummenName(template.getSummenName());
    zwischensummeEingabe.setSummenIndex(template.getSummenIndex());

    return zwischensummeEingabe;
  }

  private boolean existsKannFehler(Ergebniseingang ergebniseingang) {
    return ergebniseingang.isUngueltigKannFehler();
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> getLetzterErgebniseingangPerErfassungsgebietEinsehbar() {
    return getErgebniseingangPerErfassungsgebietCollector().findLetzterErgebniseingangAktuellPerErfassungsgebiet(getEinsehenGebietSecurity(), getSelectedWahldurchfuehrung(), getErgebniseingangsart(),
        false);
  }

  private Wahldurchfuehrung getSelectedWahldurchfuehrung() {
    if (getSelectedGebiet() == null) {
      return null;
    }

    return castTo(getSelectedGebiet().getWahl(), Wahldurchfuehrung.class);
  }

  /**
   * @return für alle Erfassungsgebiete (und für nicht ergebnisrelevante Gebiete) unterhalb des selektierten Gebiets der jeweils letzte Ergebniseingang
   */
  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> getLetzterErgebniseingangPerUntergeordnetesErfassungsgebietBearbeitbar(BooleanSupplier eingangsherkunftIgnorieren) {
    Map<Wahldurchfuehrungsgebiet, Ergebniseingang> result = new HashMap<>(getErgebniseingangPerErfassungsgebietCollector()
        .findLetzterErgebniseingangAktuellPerUntergeordnetesErfassungsgebiet(getBearbeitenGebietSecurity(), getSelectedGebiet(), getErgebniseingangsart(), true));

    // WASDE-591
    List<Wahldurchfuehrungsgebiet> gebieteEingabeGesperrt = result.keySet().stream().filter(g -> isGebietEingabeNichtMoeglich(g, eingangsherkunftIgnorieren)).collect(toList());

    gebieteEingabeGesperrt.forEach(result::remove);

    return result;
  }

  protected ErgebniseingangPerErfassungsgebietCollector getErgebniseingangPerErfassungsgebietCollector() {
    return new ErgebniseingangPerErfassungsgebietCollector(wahldurchfuehrungsgebietDao, ergebniseingangDao);
  }

  public boolean isBtnGebietStatusEingabeMoeglichRendered() {
    return isRechtVorhanden(Ergebniseingabestatus.EINGABE_MOEGLICH) && isRechtZumBearbeitenVorhanden() && !isBtnGebietStatusEingabeMoeglichDisabled() /* ELECT-5904 */;
  }

  public boolean isBtnGebietStatusEntsperrenRendered() {
    return isRechtVorhanden(Ergebniseingabestatus.EINGABE_MOEGLICH) && isRechtZumBearbeitenVorhanden() && !isBtnGebietStatusEingabeMoeglichDisabled() /* ELECT-5904 */ && isGebietGeperrt();
  }

  public boolean isBtnGebietAusbleibendBeendenRendered() {
    return isRechtVorhanden(Ergebniseingabestatus.EINGABE_MOEGLICH) && isRechtZumBearbeitenVorhanden() && !isBtnGebietStatusEingabeMoeglichDisabled() /* ELECT-5904 */ && isGebietAusbleibend()
        && isOberGebietEingabeMoeglich() && !isBtnGebietAusbleibendBeendenDisabled();
  }

  public boolean isOberGebietEingabeMoeglich() {
    Wahldurchfuehrungsgebiet selectedGebiet = getSelectedGebiet();

    if (selectedGebiet == null) {
      return false;
    }

    if (selectedGebiet.getUebergeordnetesGebiet() == null) {
      return true; // kein Gebiet => möglich
    }

    Wahldurchfuehrungsgebiet uebergeordnetesGebiet = selectedGebiet.getUebergeordnetesGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();

    return uebergeordnetesGebiet.getErstesGebietInHierarchieDasEingabeNichtZulaesst(getErgebnisart(), AbstractStimmeingabeErfassenView.ERGEBNISEINGANGSHERKUNFT_MANUELLE_ERFASSUNG,
        () -> hasPermission(UseCasePermission.STIMMEINGABE_CREATE_EINGANGSHERKUNFT_IGNORIEREN)) == null;
  }

  private boolean isGebietGeperrt() {
    Wahldurchfuehrungsgebiet selectedGebiet = getSelectedGebiet();

    return selectedGebiet != null && selectedGebiet.getErgebniseingabestatus(getErgebniseingangsart().korrespondierendeErgebnisart()).isGesperrt();
  }

  private boolean isGebietAusbleibend() {
    Wahldurchfuehrungsgebiet selectedGebiet = getSelectedGebiet();

    return selectedGebiet != null && selectedGebiet.getErgebniseingabestatus(getErgebniseingangsart().korrespondierendeErgebnisart()).isAusbleibend();
  }

  public boolean isBtnGebietStatusGeperrtRendered() {
    return isRechtVorhanden(Ergebniseingabestatus.GESPERRT) && isRechtZumBearbeitenVorhanden() && !isBtnGebietStatusGeperrtDisabled() /* ELECT-5904 */;
  }

  public boolean isBtnGebietStatusAbgeschlossenRendered() {
    return isRechtVorhanden(Ergebniseingabestatus.ABGESCHLOSSEN) && isRechtZumBearbeitenVorhanden() && !isBtnGebietStatusAbgeschlossenDisabled() /* ELECT-5904 */;
  }

  public boolean isBtnGebietStatusFreigegebenRendered() {
    return isRechtVorhanden(Ergebniseingabestatus.FREIGEGEBEN) && isRechtZumBearbeitenVorhanden() && !isBtnGebietStatusFreigegebenDisabled();
  }

  public boolean isBtnGebietStatusEingabeMoeglichDisabled() {
    return !isStatusUebergangMoeglich(Ergebniseingabestatus.EINGABE_MOEGLICH) || !isRechtZumBearbeitenVorhanden() || isErgebniseingangsartVerschiedenVonWahl();
  }

  public boolean isBtnGebietStatusEntsperrenDisabled() {
    return !isStatusUebergangMoeglich(Ergebniseingabestatus.EINGABE_MOEGLICH) || !isRechtZumBearbeitenVorhanden() || isErgebniseingangsartVerschiedenVonWahl();
  }

  public boolean isBtnGebietAusbleibendBeendenDisabled() {
    return !isStatusUebergangMoeglich(Ergebniseingabestatus.EINGABE_MOEGLICH) || !isRechtZumBearbeitenVorhanden() || isErgebniseingangsartVerschiedenVonWahl();
  }

  public boolean isBtnGebietStatusGeperrtDisabled() {
    return !isStatusUebergangMoeglich(Ergebniseingabestatus.GESPERRT) || !isRechtZumBearbeitenVorhanden();
  }

  public boolean isBtnGebietStatusAbgeschlossenDisabled() {
    return !isStatusUebergangMoeglich(Ergebniseingabestatus.ABGESCHLOSSEN) || !isRechtZumBearbeitenVorhanden();
  }

  public boolean isBtnGebietStatusFreigegebenDisabled() {
    return !isStatusUebergangMoeglich(Ergebniseingabestatus.FREIGEGEBEN) || !isRechtZumBearbeitenVorhanden();
  }

  private boolean isStatusUebergangMoeglich(Ergebniseingabestatus ergebniseingabestatusZuSetzen) {
    if (getSelectedGebiet() == null || getSelectedGebiet() == null || isErgebniseingangsartVerschiedenVonWahl()) {
      return false;
    }

    return ergebniseingabestatuswechelPruefer.isStatusuebergangMoeglich(getSelectedErgebnis(), getSelectedGebiet(), ergebniseingabestatusZuSetzen,
        hasPermission(UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_FREIGEGEBEN_ZURUECKNEHMEN)).isUebergangMoeglich();
  }

  @Redirect
  public String setzeGebietStatusEingabeMoeglich() {
    return setzeGebietStatus(Ergebniseingabestatus.EINGABE_MOEGLICH);
  }

  @Redirect
  public String setzeGebietStatusEntsperrt() {
    return setzeGebietStatus(Ergebniseingabestatus.EINGABE_MOEGLICH);
  }

  @Redirect
  public String setzeGebietStatusAusbleibendBeenden() {
    return setzeGebietStatus(Ergebniseingabestatus.EINGABE_MOEGLICH);
  }

  @Redirect
  public String setzeGebietStatusGeperrt() {
    return setzeGebietStatus(Ergebniseingabestatus.GESPERRT);
  }

  @Nullable
  @Redirect
  public String setzeGebietStatusAbgeschlossen() {
    return setzeGebietStatus(Ergebniseingabestatus.ABGESCHLOSSEN);
  }

  @Redirect
  public String setzeGebietStatusFreigegeben() {
    return setzeGebietStatus(Ergebniseingabestatus.FREIGEGEBEN);
  }

  private String setzeGebietStatus(Ergebniseingabestatus ergebniseingabestatusZuSetzen) {
    Wahldurchfuehrungsgebiet gebiet = getSelectedGebiet();

    Ergebniseingabestatus ergebniseingabestatus = ergebniseingangStatus.setzeGebietErgebniseingabestatus(gebiet, ergebniseingabestatusZuSetzen, getSelectedErgebnis());

    if (ergebniseingabestatusZuSetzen.equals(ergebniseingabestatus)) {
      gebietExtendedDAO.refresh(getSelectedGebiet()); // Ansonsten ist der neue Ergebniseingabestatus nachfolgend noch nicht sichtbar!

      return statusWechselErfolgreich(ergebniseingabestatusZuSetzen, gebiet);
    }

    addWarningMessage("ergebniseingang.status.alter.gebiet.status", getMessage(ergebniseingabestatus));

    return VIEW_ID;
  }

  protected String statusWechselErfolgreich(Ergebniseingabestatus ergebniseingabestatusGesetzt, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    addInfoMessage("ergebniseingang.status.neuer.gebiet.status", getMessage(ergebniseingabestatusGesetzt));

    return VIEW_ID;
  }

  private boolean isRechtVorhanden(Ergebniseingabestatus ergebniseingabestatus) {
    return hasPermission(ergebniseingabestatus.getNotwendigesRecht());
  }

  private transient List<Gebiet> untergeordneteGebieteMitRechtZumEinsehen = null;

  public List<Gebiet> getUntergeordneteGebieteMitRechtZumEinsehen() {
    if (untergeordneteGebieteMitRechtZumEinsehen == null) {
      untergeordneteGebieteMitRechtZumEinsehen = getSelectedGebiet().getUntergeordneteGebieteSortiertNachPosition(gebietExtendedDAO).stream().filter(this::isRechtZumEinsehenVorhanden)
          .collect(toList());
    }

    return untergeordneteGebieteMitRechtZumEinsehen;
  }

  public String getIconStatusGesperrt() {
    return Ergebniseingabestatus.GESPERRT.getIcon();
  }

  public String getIconStatusEntsperren() {
    return "fa fa-unlock";
  }

  public String getIconStatusEingabeMoeglich() {
    return Ergebniseingabestatus.EINGABE_MOEGLICH.getIcon();
  }

  public String getIconStatusAbgeschlossen() {
    return Ergebniseingabestatus.ABGESCHLOSSEN.getIcon();
  }

  public String getIconStatusFreigegeben() {
    return Ergebniseingabestatus.FREIGEGEBEN.getIcon();
  }

  public String getLabelStatusEingabeMoeglich() {
    return getLabel(Ergebniseingabestatus.EINGABE_MOEGLICH);
  }

  public String getLabelStatusGesperrt() {
    return getLabel(Ergebniseingabestatus.GESPERRT);
  }

  public String getLabelStatusEntsperren() {
    return getMessage("gebiet.entsperren.label");
  }

  public String getLabelStatusAusbleibendBeenden() {
    return getMessage("gebiet.ausbleibend.beenden.label");
  }

  public String getLabelStatusAbgeschlossen() {
    return getLabel(Ergebniseingabestatus.ABGESCHLOSSEN);
  }

  public String getMsgStatusAbgeschlossen() {
    return getMessage("ergebniseingang.status.action.gebiet.message.ABGESCHLOSSEN");
  }

  public String getLabelStatusFreigegeben() {
    return getLabel(Ergebniseingabestatus.FREIGEGEBEN);
  }

  public String getMsgStatusFreigegeben() {
    return getMessage("ergebniseingang.status.action.gebiet.message.FREIGEGEBEN");
  }

  private String getLabel(Ergebniseingabestatus ergebniseingabestatusZuSetzen) {
    if (getSelectedGebiet() == null || getSelectedErgebnis() == null) {
      return "";
    }

    Ergebniseingabestatus alterStatus = getSelectedGebiet().getErgebniseingabestatus(getSelectedErgebnis());

    final String msgKey;

    if (ergebniseingabestatusZuSetzen.isEigenerMessageKeyFuerUebergang()) {
      msgKey = "ergebniseingang.status.action.gebiet.status." + ergebniseingabestatusZuSetzen.name();
    } else {
      if (alterStatus.ordinal() < ergebniseingabestatusZuSetzen.ordinal()) {
        msgKey = "ergebniseingang.status.action.gebiet.status.vor";
      } else {
        msgKey = "ergebniseingang.status.action.gebiet.status.zurueck";
      }
    }

    return getMessage(msgKey, getMessage(ergebniseingabestatusZuSetzen));
  }

  public boolean isWeitereButtonsVorhanden() {
    return false;
  }

  public int getPollTimeoutInSeconds() {
    if (isFortschrittErfassungRendered()) {

      Integer refresh = konfigurationExtendedLoader.resolveIntegerValue(ElectKonfigurationWerte.STIMMEINGABE_AUTOMATISCHER_REFRESH_IN_SEKUNDEN);
      if (refresh == null || refresh == 0) {
        return DEFAULT_POLL_TIMEOUT_IN_SECONDS;
      }
      return refresh;

    }

    return POLL_DISABLED;
  }

  @Override
  public List<DefaultStatusEntryColumn< ? extends StatusEntry>> getAdditionalColumnsForExport() {
    List<DefaultStatusEntryColumn< ? extends StatusEntry>> additionalColumnsForExport = new ArrayList<>(super.getAdditionalColumnsForExport());

    additionalColumnsForExport.add(new DefaultStatusEntryColumn<>("gebietErfassungsstatus", messages, "status.entry.gebiet.erfassungstatus", ColumnType.ENUM, s -> {
      if (s instanceof ErgebniseingangStatusEntry) {
        return ((ErgebniseingangStatusEntry) s).getGebietErfassungsstatus();
      }
      return null;
    }));

    return additionalColumnsForExport;
  }

  private transient Boolean ergebniseingangsartVerschiedenVonWahl = null;

  protected boolean isErgebniseingangsartVerschiedenVonWahl() {
    if (ergebniseingangsartVerschiedenVonWahl == null) {
      ergebniseingangsartVerschiedenVonWahl = getStimmeingabeErfassenViewErgebniseingangsartStore().isErgebniseingangsartVerschiedenVonWahl(getSelectedWahldurchfuehrung());
    }

    return ergebniseingangsartVerschiedenVonWahl;
  }

  public boolean isErfassungschritteRendered() {
    return getErfassungsschritteMenu().isRendered() && !isErgebniseingangsartVerschiedenVonWahl() && !isGebietGesperrtOderAusbleibend();
  }

  public ErfassungsschritteMenuBuilder getErfassungsschritteMenu() {
    return erfassungsschritteMenuBuilder;
  }

  public boolean isFortschrittErfassungRendered() {
    if (getSelectedGebiet() == null) {
      return false;
    }

    return !getUntergeordneteGebiete(getSelectedGebiet()).isEmpty();
  }

  private boolean isGebietGesperrtOderAusbleibend() {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = getSelectedGebiet();

    if (wahldurchfuehrungsgebiet != null) {
      Ergebniseingabestatus ergebniseingabestatus = wahldurchfuehrungsgebiet.getErgebniseingabestatus(getErgebniseingangsart().korrespondierendeErgebnisart());

      return ergebniseingabestatus.isGesperrt() || ergebniseingabestatus.isAusbleibend();
    }

    return false;
  }

  public Ergebniseingangsart getErgebniseingangsart() {
    return getStimmeingabeErfassenViewErgebniseingangsartStore().getErgebniseingangsart(getSelectedWahldurchfuehrung());
  }

  @Override
  public ErgebnisStatusInfo getErgebnisStatusInfo() {
    if (!isGebietSelected() || !isErgebnisSelected()) {
      return ErgebnisStatusInfo.EMPTY;
    }

    Ergebniseingabestatus ergebniseingabestatus = null;

    if (!isErgebniseingangsartVerschiedenVonWahl()) {
      ergebniseingabestatus = getSelectedGebiet().getErgebniseingabestatus(getSelectedErgebnis());
    }

    return new WasErgebnisStatusInfo(getMessages(), getSelectedWahldurchfuehrung(), ergebniseingangKonfiguration, getErgebniseingangsart(), ergebniseingabestatus);
  }

  private StimmeingabeErfassenViewErgebniseingangsartStore getStimmeingabeErfassenViewErgebniseingangsartStore() {
    return stimmeingabeErfassenViewErgebniseingangsartStore;
  }

  @Override
  public boolean isBtnWechselnAufVorlaeufigeErfassungRendered() {
    return wahlStatusuebersichtView.isBtnWechselnAufVorlaeufigeErfassungRendered() && isErgebnisartUmschaltenMoeglich();
  }

  @Override
  public boolean isBtnWechselnAufVorlaeufigeErfassungDisabled() {
    return wahlStatusuebersichtView.isBtnWechselnAufVorlaeufigeErfassungDisabled();
  }

  @Override
  @Redirect
  public String wechselnAufVorlaeufigeErfassung() {
    return wechselnAuf(wahlStatusuebersichtView.wechselnAufVorlaeufigeErfassung());
  }

  @Override
  public String getBtnWechselnAufVorlaeufigeErfassungLabel() {
    return wahlStatusuebersichtView.getBtnWechselnAufVorlaeufigeErfassungLabel();
  }

  @Override
  public boolean isBtnWechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaengeRendered() {
    return wahlStatusuebersichtView.isBtnWechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaengeRendered() && isErgebnisartUmschaltenMoeglich();
  }

  @Override
  public boolean isBtnWechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaengeDisabled() {
    return wahlStatusuebersichtView.isBtnWechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaengeDisabled();
  }

  @Override
  @Redirect
  public String wechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaenge() {
    return wechselnAuf(wahlStatusuebersichtView.wechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaenge());
  }

  @Override
  public String getBtnWechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaengeLabel() {
    return wahlStatusuebersichtView.getBtnWechselnAufEndgueltigeErfassungOhneUebernahmeVorlaeufigerErgebniseingaengeLabel();
  }

  @Override
  public boolean isBtnWechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaengeRendered() {
    return wahlStatusuebersichtView.isBtnWechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaengeRendered() && isErgebnisartUmschaltenMoeglich();
  }

  @Override
  public boolean isBtnWechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaengeDisabled() {
    return wahlStatusuebersichtView.isBtnWechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaengeDisabled();
  }

  @Override
  @Redirect
  public String wechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaenge() {
    return wechselnAuf(wahlStatusuebersichtView.wechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaenge());
  }

  @Override
  public String getBtnWechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaengeLabel() {
    return wahlStatusuebersichtView.getBtnWechselnAufEndgueltigeErfassungMitUebernahmeVorlaeufigerErgebniseingaengeLabel();
  }

  private String wechselnAuf(String outcome) {
    ergebniseingangStatusViewHelper.reset();

    stimmeingabeErfassenViewErgebniseingangsartStore.reset();

    return outcome;
  }

  @Override
  public String getZielErgebnisartName() {
    return wahlStatusuebersichtView.getZielErgebnisartName();
  }

  @Override
  public boolean isBtnErebnisartUmschaltenRendered() {
    return wahlStatusuebersichtView.isBtnErebnisartUmschaltenRendered() && isErgebnisartUmschaltenMoeglich();
  }

  @Override
  public String getBtnErgebnisartUmschaltenLabel() {
    return wahlStatusuebersichtView.getBtnErgebnisartUmschaltenLabel();
  }

  private boolean isErgebnisartUmschaltenMoeglich() {
    if (stimmeingabeErfassenViewErgebniseingangsartStore.isErgebniseingangsartVerschiedenVonWahl(getSelectedWahldurchfuehrung())) {
      return false; // nur wenn Art der Wahl entspricht
    }

    Wahldurchfuehrungsgebiet selectedGebiet = getSelectedGebiet();

    if (selectedGebiet == null || selectedGebiet.hasUebergeordnetesGebiet()) {
      return false; // nur auf oberstem Gebiet
    }

    Ergebnis selectedErgebnis = getSelectedErgebnis();

    if (selectedErgebnis == null) {
      return false;
    }

    if (selectedErgebnis.getErgebnisart().isEndgueltig()) {
      return true; // von Endgültig jederzeit zurück nach vorläufig
    }

    return selectedGebiet.getErgebniseingabestatus(selectedErgebnis).isFreigegeben(); // von Vorläufig nach endgültig nur, wenn gebiet freigegeben
  }

}
