
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 java.util.stream.Collectors;

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 org.primefaces.model.charts.bar.BarChartModel;

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.gruppe.boundary.GruppeExtendedDAO;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeExtendedDAO;
import de.ivu.elect.business.gruppe.entity.AbstractGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietExtendedDAO;
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.StimmeingabeAuffaelligkeitSearchParameters;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeHochgerechnetSearch;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeHochgerechnetSearchParameters;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeHochgerechnet;
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.business.wahl.entity.Wahl;
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(StimmeingabeHochgerechnetSearchView.COMPONENT_NAME)
@View(viewId = StimmeingabeHochgerechnetSearchView.VIEW_ID, useCase = UseCasePermission.STIMMEINGABE_HOCHGERECHNET_SEARCH)
public class StimmeingabeHochgerechnetSearchView extends AbstractElectSearchView<StimmeingabeHochgerechnet> implements Serializable {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "stimmeingabeHochgerechnetSearchView";

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

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

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

  @Inject
  private transient StimmeingabeHochgerechnetSearch search;

  @Inject
  private transient WahldurchfuehrungsgebietSessionSelect wahldurchfuehrungsgebietSessionSelect;

  @Inject
  private transient RequestHandlerStimmeingabeHochgerechnetView requestHandler;

  @Inject
  private transient WahldurchfuehrungSessionSelect wahldurchfuehrungSessionSelect;

  @Inject
  private transient ErgebnisSessionSelect ergebnisSessionSelect;

  @Inject
  private transient GruppeExtendedDAO gruppeDAO;

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  private transient PolitischeGruppeExtendedDAO politischeGruppeDao;

  @Inject
  private transient GruppeImGebietExtendedDAO gruppeImGebietDao;

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

  public StimmeingabeHochgerechnetSearchView() {
    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) {
    formBuilder.addProperty(getSearchParameters().getStimmenartProperty());
    formBuilder.addProperty(getSearchParameters().getGruppenSearchProperty(getPolitischeGruppenMitSystemgruppen()));

    formBuilder.addSeparator("stimmeingabe.hochgerechnet.hochrechnungsalgorithmus.konfiguration.separator");

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

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

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

  private LambdaEnumProperty<HochrechnungsalgorithmusArt> getLambdaEnumPropertyFuerHochrechnungsalgorithmusArt() {
    return new LambdaEnumProperty<>(getMessage("stimmeingabe.auffaelligkeit.hochrechnungsalgorithmus"), true,
        null, getSearchParameters().getStimmeingabeAuffaelligkeitSearchParameters()::getHochrechnungsalgorithmusArt,
        HochrechnungsalgorithmusArt.class, asList(HochrechnungsalgorithmusArt.values())
    );
  }

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

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

  @Override
  public void presetSearchParameters() {
  }

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

  @Override
  protected List<StimmeingabeHochgerechnet> performSearch() {
    List<StimmeingabeHochgerechnet> searchResult = search.getSearchResult(getSearchParameters(), getEagerLoadingCollector(), stimmeingabeAuffaelligkeitCache);
    checkIfHochrechnerAlgorithmusAusfuehrbar(searchResult);
    return searchResult;
  }

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

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

    List<StimmeingabeHochgerechnet> searchResult = super.getSearchResult();
    checkIfHochrechnerAlgorithmusAusfuehrbar(searchResult);
    return searchResult;
  }

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

  private HochrechneralgorithmusAusfuehrbarkeitStatus getAlgorithmusAusfuehrbarkeitStatus(List<StimmeingabeHochgerechnet> searchResult) {
    StimmeingabeHochgerechnet stimmeingabeHochgerechnet = searchResult.stream().filter(Objects::nonNull).findAny().orElse(null);
    if (stimmeingabeHochgerechnet == null) {
      return HochrechneralgorithmusAusfuehrbarkeitStatus.BERECHNUNG_NOCH_NICHT_AUSGEFUEHRT;
    }
    return stimmeingabeHochgerechnet.getHochrechneralgorithmusAusfuehrbarkeitStatus();
    }

  @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) {
    getSearchParameters().setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietSessionSelect.getEntity());
    resetSearchResult();
  }

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

  // WASDE-586
  private List<Gruppe> getPolitischeGruppenMitSystemgruppen() {
    List<Gruppe> result = new ArrayList<>();

    List<Gruppe> alleGruppen = gruppeDAO.findByWahl(getWahl());

    // gültige
    result.add(AbstractGruppe.findeSystemGruppe(alleGruppen, SystemGruppe.GUELTIGE, systemGruppeEigenschaften));
    // ungültige
    result.add(AbstractGruppe.findeSystemGruppe(alleGruppen, SystemGruppe.UNGUELTIGE, systemGruppeEigenschaften));
    // wahlberechtigte
    result.add(AbstractGruppe.findeSystemGruppe(alleGruppen, SystemGruppe.WAHLBERECHTIGTE, systemGruppeEigenschaften));
    // waehler
    result.add(AbstractGruppe.findeSystemGruppe(alleGruppen, SystemGruppe.WAEHLER, systemGruppeEigenschaften));

    result.addAll(getPolitischeGruppenOhneEinzelbewerber());
    return result;
  }

  private List<PolitischeGruppe> getPolitischeGruppenOhneEinzelbewerber() {
    List<PolitischeGruppe> politischeGruppenAufGebietSortiertNachPosition = PolitischeGruppe
        .getPolitischeGruppenAufGebietSortiertNachPosition(getSelectedGebiet(), politischeGruppeDao, gruppeImGebietDao);
    return politischeGruppenAufGebietSortiertNachPosition.stream().filter(pg -> !pg.isEinzelbewerber()).collect(Collectors.toList());
  }

  private Wahl getWahl() {
    return getSelectedGebiet().getWahl();
  }

  public List<BarChartModel> getBarChartModels() {

    List<StimmeingabeHochgerechnet> stimmeingabenHochgerechnet = getSearchResult();

    StimmeingabeHochgerechnetBarChartBuilder stimmeingabeHochgerechnetBarChartBuilder = new StimmeingabeHochgerechnetBarChartBuilder();

    return stimmeingabeHochgerechnetBarChartBuilder.getBarChartModels(stimmeingabenHochgerechnet, getMessages());
  }

  @Override
  public void onSearchResultLoaded() {
    super.onSearchResultLoaded();
    this.ajaxHelper.update(this.getBarChartModelsId());
  }

  public String getBarChartModelsId() {
    return "StimmeingabeHochgerechnetBarChartModels";
  }

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

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

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

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