
package de.ivu.elect.presentation.stimmeingabe;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.enterprise.inject.Instance;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.deltaspike.core.api.scope.ConversationGroup;
import org.apache.deltaspike.core.api.scope.ViewAccessScoped;

import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitsbemerkungenErzeugerToleranz;
import de.ivu.elect.business.auffaelligkeit.entity.GebietsartenDerBetrachtetenGebieteInAuffaelligkeitssuche;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisSelectChangeEvent;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisSessionSelect;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietSessionSelect;
import de.ivu.elect.business.hochrechnung.boundary.HochrechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.hochrechnung.entity.HochrechneralgorithmusAusfuehrbarkeitStatus;
import de.ivu.elect.business.hochrechnung.entity.HochrechnungsalgorithmusArt;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeAuffaelligkeitSearch;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeAuffaelligkeitSearchParameters;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeAuffaelligkeit;
import de.ivu.elect.business.wahl.boundary.WahlSelectChangeEvent;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungSessionSelect;
import de.ivu.elect.business.wahl.boundary.WahlterminSelectChangeEvent;
import de.ivu.elect.jee.entity.EagerLoadingCollector;
import de.ivu.elect.jee.faces.FacesMessageUtil;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaEnumProperty;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.AbstractElectSearchView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.security.AbstractGebietSecurityFilter;


import static java.util.Arrays.asList;

/**
 *  on 29.03.2016.
 */
@OnlineHilfeId(value = "692", mehrfachVergebenOk = true)
@ViewAccessScoped
@Named(StimmeingabeAuffaelligkeitSearchView.COMPONENT_NAME)
@View(viewId = StimmeingabeAuffaelligkeitSearchView.VIEW_ID, useCase = UseCasePermission.STIMMEINGABE_AUFFAELIGKEIT_SEARCH)
public class StimmeingabeAuffaelligkeitSearchView extends AbstractElectSearchView<StimmeingabeAuffaelligkeit> implements Serializable {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "stimmeingabeAuffaelligkeitSearchView";

  public static final String VIEW_ID = "/auffaelligkeit/stimmeingabe-auffaelligkeit-search.xhtml";

  @Inject
  @ConversationGroup(StimmeingabeAuffaelligkeitCache.class)
  private transient StimmeingabeAuffaelligkeitCache stimmeingabeAuffaelligkeitCache;

  @Inject
  @ConversationGroup(StimmeingabeAuffaelligkeitSearchParameters.class)
  private transient StimmeingabeAuffaelligkeitSearchParameters searchParameters;

  @Inject
  private transient StimmeingabeAuffaelligkeitSearch stimmeingabeAuffaelligkeitSearch;

  @Inject
  private transient WahldurchfuehrungsgebietSessionSelect wahldurchfuehrungsgebietSessionSelect;

  @Inject
  private transient RequestHandlerStimmeingabeAuffaelligkeitView requestHandler;

  @Inject
  private transient WahldurchfuehrungSessionSelect wahldurchfuehrungSessionSelect;

  @Inject
  private transient ErgebnisSessionSelect ergebnisSessionSelect;

  public StimmeingabeAuffaelligkeitSearchView() {
    super();
  }

  @Nullable
  @PreConditionCheck
  public String checkCondition() {
    if (!wahldurchfuehrungSessionSelect.isSelected()) {
      addErrorMessage("wahl.selected.missing");
      return getStartViewId();
    }

    if (!wahldurchfuehrungsgebietSessionSelect.isSelected()) {
      addErrorMessage("gebiet.selected.missing");

      return getStartViewId();
    }

    if (!ergebnisSessionSelect.isSelected()) {
      addErrorMessage("ergebnis.selected.missing");

      return getStartViewId();
    }

    return null;
  }

  @Override
  public void addProperties(FormBuilder formBuilder) {
    List<HochrechnungsalgorithmusArt> hochrechnungsalgorithmen = new ArrayList<>(HochrechnungsalgorithmusArt
        .getHochrechnungsalgorithmusArtenInBevorzugteReihenfolge());

    LambdaEnumProperty<HochrechnungsalgorithmusArt> lambdaHochrechnungsalgorithmusArtEnumProperty = new LambdaEnumProperty<>(
        messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.hochrechnungsalgorithmus"), true,
        getSearchParameters()::setHochrechnungsalgorithmusArt, getSearchParameters()::getHochrechnungsalgorithmusArt,
        HochrechnungsalgorithmusArt.class, hochrechnungsalgorithmen, true);
    formBuilder.addProperty(lambdaHochrechnungsalgorithmusArtEnumProperty);

    hochrechnungsalgorithmen.forEach(algorithmus -> {
      getSearchParameters().getKonfigurationNachHochrechnungsalgorithmusArt().computeIfAbsent(algorithmus, HochrechnungsalgorithmusArt::getKonfiguration);
          HochrechnungsalgorithmusKonfiguration konfiguration = getSearchParameters().getKonfigurationNachHochrechnungsalgorithmusArt().get(algorithmus);
          BiFunction<String, Object[], String> messageGetter = getMessageGetter();
          konfiguration.addObservingProperties(formBuilder, lambdaHochrechnungsalgorithmusArtEnumProperty,
              messageGetter);
        }
    );

    formBuilder.addProperty(new LambdaEnumProperty<>(getMessage("stimmen.auffaelligkeit.gebietsarten.der.betrachteten.gebiete"), true,
        searchParameters::setGebietsartenDerBetrachtetenGebieteInAuffaelligkeitssuche, searchParameters::getGebietsartenDerBetrachtetenGebieteInAuffaelligkeitssuche,
        GebietsartenDerBetrachtetenGebieteInAuffaelligkeitssuche.class, asList(GebietsartenDerBetrachtetenGebieteInAuffaelligkeitssuche.values())
    ));

    LambdaEnumProperty<AuffaelligkeitsbemerkungenErzeugerToleranz> lambdaAuffaelligkeitsbemerkungenErzeugerToleranzEnumProperty = new LambdaEnumProperty<>(getMessage("stimmeingabe.auffaelligkeit.auffaelligkeit.bemerkungen.erzeuger.toleranz"), true,
        searchParameters::setAuffaelligkeitsbemerkungenErzeugerToleranz, searchParameters::getAuffaelligkeitsbemerkungenErzeugerToleranz,
        AuffaelligkeitsbemerkungenErzeugerToleranz.class, asList(AuffaelligkeitsbemerkungenErzeugerToleranz.values()), true);

    formBuilder.addProperty(lambdaAuffaelligkeitsbemerkungenErzeugerToleranzEnumProperty);
  }

  private BiFunction<String, Object[], String> getMessageGetter() {
    return (key, param) -> messages.getMessageForMessageKey(String.valueOf(key), param);
  }

  @Override
  protected String getViewId() {
    return VIEW_ID;
  }

  @Override
  public StimmeingabeAuffaelligkeitSearchParameters getSearchParameters() {
    searchParameters.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietSessionSelect.getEntity());
    searchParameters.setErgebnis(ergebnisSessionSelect.getEntity());
    return searchParameters;
  }

  @Override
  public void presetSearchParameters() {
    // nichts zu tun
  }

  public EagerLoadingCollector<StimmeingabeAuffaelligkeit> getEagerLoadingCollector() {
    return EagerLoadingCollector.<StimmeingabeAuffaelligkeit> create();
  }

  @Override
  protected List<StimmeingabeAuffaelligkeit> performSearch() {
    List<StimmeingabeAuffaelligkeit> searchResult = stimmeingabeAuffaelligkeitSearch.getSearchResult(searchParameters, stimmeingabeAuffaelligkeitCache);
    checkIfAlgorithmusAusfuehrbar(searchResult);
    return searchResult;
  }

  private void checkIfAlgorithmusAusfuehrbar(List<StimmeingabeAuffaelligkeit> searchResult) {
    HochrechneralgorithmusAusfuehrbarkeitStatus algorithmusAusfuehrbarkeitStatus = getAlgorithmusAusfuehrbarkeitStatus(searchResult);
    if (!algorithmusAusfuehrbarkeitStatus.isAlgorithmusAusfuehrbar()) {
      addErrorMessage(FacesMessageUtil.getEnumMessageKey(algorithmusAusfuehrbarkeitStatus));
    }
  }

  private HochrechneralgorithmusAusfuehrbarkeitStatus getAlgorithmusAusfuehrbarkeitStatus(List<StimmeingabeAuffaelligkeit> searchResult) {
    StimmeingabeAuffaelligkeit stimmeingabeAuffaelligkeit = searchResult.stream().filter(Objects::nonNull).findAny().orElse(null);
    if (stimmeingabeAuffaelligkeit == null) {
      return HochrechneralgorithmusAusfuehrbarkeitStatus.BERECHNUNG_NOCH_NICHT_AUSGEFUEHRT;
    }
    return stimmeingabeAuffaelligkeit.getGesamtStimmenhochrechnerAusgabedaten().getAusfuehrbarkeitStatus();
  }

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

  @Override
  public List<StimmeingabeAuffaelligkeit> getSearchResult() {
    requestHandler.nothing(); // ELECT-248

    List<StimmeingabeAuffaelligkeit> searchResult = super.getSearchResult();
    checkIfAlgorithmusAusfuehrbar(searchResult);
    return searchResult;
  }

  @Override
  @Redirect
  public String search() {
    stimmeingabeAuffaelligkeitCache.reset();
    return super.search();
  }

  @Override
  @Redirect
  public String reset() {
    String result = super.reset();
    stimmeingabeAuffaelligkeitCache.reset();
    return result;
  }
  // ELECT-2231    ELECT-2250
  /*
   * searchParameters sind von der zur Bearbeitung ausgewählten Wahl abhängig.
   */
  public void onWahlSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) WahlSelectChangeEvent event) {
    reset();
  }

  /*
   * searchParameters sind vom zur Bearbeitung ausgewählten Wahltermin abhängig.
   */
  public void onWahlterminSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) WahlterminSelectChangeEvent event) {
    reset();
  }

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

  public void onErgebnisSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) ErgebnisSelectChangeEvent event) {
    reset();
  }

  @RequestScoped
  //  Das Suchergebnis soll nur einmal pro Request geladen werden, daher gibt es dieses "Konstrukt" aus "SearchView" und "RequestHandlerAbstract"
  public static class RequestHandlerStimmeingabeAuffaelligkeitView {

    @View
    @Inject
    private transient Instance<StimmeingabeAuffaelligkeitSearchView> searchViews;

    @PostConstruct
    public void init() {
      // ELECT-3543 Doppelte Suchen vermeiden - Suchergebnis für Request nicht beim POST-löschen
      if (!FacesContext.getCurrentInstance().isPostback()) {
        for (StimmeingabeAuffaelligkeitSearchView searchView : searchViews) {
          searchView.resetSearchResult();
        }
      }
    }

    public void nothing() {
      // Do nothing.
    }
  }
}
