
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.BiFunction;

import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;
import javax.inject.Named;

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

import de.ivu.elect.business.ergebnis.boundary.ErgebnisSessionSelect;
import de.ivu.elect.business.gebietsbaum.boundary.GebietNichtAusgewaehltException;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietSessionSelect;
import de.ivu.elect.business.gebietsbaum.entity.KeineWahlberechtigtenImportiertException;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.hochrechnung.boundary.HochrechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.hochrechnung.entity.HochrechnungsalgorithmusArt;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.control.ElectCommonPrincipal;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeFiktivewerteBatchCreate;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangErfasser;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangGenerierenProgress;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
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.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaEnumProperty;
import de.ivu.elect.jee.presentation.view.AbstractView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.dokumente.ProgressView;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.replikation.ReplikationPresentationStatus;


import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.toList;

@OnlineHilfeId("214")
@ViewAccessScoped // ELECT-16131
@Named(FiktiveErgebniseingaengeErzeugenView.COMPONENT_NAME)
@View(viewId = FiktiveErgebniseingaengeErzeugenView.VIEW_ID, useCase = UseCasePermission.STIMMEINGABE_TESTWERTE_FUELLEN)
public class FiktiveErgebniseingaengeErzeugenView extends AbstractView implements ProgressView {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "fiktiveErgebniseingaengeErzeugenView";

  public static final String VIEW_ID = "/stimmeingabe/ergebniseingang-fiktive-ergebniseingaenge-erzeugen.xhtml";

  @Inject
  private transient ElectCommonPrincipal principal;

  @Inject
  private transient WahldurchfuehrungsgebietSessionSelect wahldurchfuehrungsgebietSelect;

  @Inject
  private transient FiktiveErgebniseingaengeErzeugenProgressMonitor progressMonitor;

  @Inject
  private transient StimmeingabeFiktivewerteBatchCreate stimmeingabeFiktivwerteBatchCreate;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient ReplikationPresentationStatus replikationPresentationStatus;

  @Inject
  private transient ErgebnisSessionSelect ergebnisSessionSelect;

  private boolean generierungLaeuft = false;

  private AdvancedDynaFormModel model;

  private Collection<Wahldurchfuehrungsgebiet> gebieteErzeugung = null;

  private HochrechnungsalgorithmusArt hochrechnungsalgorithmusArt = getDefaultHochrechnerAlgorithmusArt();

  private final Map<HochrechnungsalgorithmusArt, HochrechnungsalgorithmusKonfiguration> konfigurationNachHochrechnungsalgorithmusArt = new HashMap<>();

  public FiktiveErgebniseingaengeErzeugenView() {
    super();
  }

  protected void addProperties(FormBuilder formBuilder) {
    List<HochrechnungsalgorithmusArt> hochrechnungsalgorithmen = new ArrayList<>(HochrechnungsalgorithmusArt
        .getHochrechnungsalgorithmusArtenInBevorzugteReihenfolge());
    if (!hochrechnungsalgorithmen.contains(HochrechnungsalgorithmusArt.UEBERNAHME_VOM_VORLAEUFIGEN_ERGEBNIS)) {
      hochrechnungsalgorithmen.add(HochrechnungsalgorithmusArt.UEBERNAHME_VOM_VORLAEUFIGEN_ERGEBNIS);
    }

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

    hochrechnungsalgorithmen.forEach(algorithmus -> {
          konfigurationNachHochrechnungsalgorithmusArt.computeIfAbsent(algorithmus, HochrechnungsalgorithmusArt::getKonfiguration);
          HochrechnungsalgorithmusKonfiguration konfiguration = konfigurationNachHochrechnungsalgorithmusArt.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);
  }

  public HochrechnungsalgorithmusArt getHochrechnungsalgorithmusArt() {
    return this.hochrechnungsalgorithmusArt;
  }

  public void setHochrechnungsalgorithmusArt(HochrechnungsalgorithmusArt hochrechnungsalgorithmusArt) {
    this.hochrechnungsalgorithmusArt = hochrechnungsalgorithmusArt;
  }

  private HochrechnungsalgorithmusArt getDefaultHochrechnerAlgorithmusArt() {
    List<HochrechnungsalgorithmusArt> hochrechnungsalgorithmusArtenInBevorzugteReihenfolge = HochrechnungsalgorithmusArt.getHochrechnungsalgorithmusArtenInBevorzugteReihenfolge();
    if (hochrechnungsalgorithmusArtenInBevorzugteReihenfolge.contains(HochrechnungsalgorithmusArt.ZUFAELLIG)) {
      return HochrechnungsalgorithmusArt.ZUFAELLIG; // funktioniert in 100 % der Fälle erstmal
    }

    return hochrechnungsalgorithmusArtenInBevorzugteReihenfolge.isEmpty() ? null : hochrechnungsalgorithmusArtenInBevorzugteReihenfolge.get(0);
  }

  @PreConditionCheck
  public String preConditionCheck() {
    if (!isWahldurchfuehrungsgebietSelected()) {
      throw new GebietNichtAusgewaehltException();
    }

    return null;
  }

  public void onGebietChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent gebietSelectChangeEvent) {
    reset();
  }

  private void reset() {
    gebieteErzeugung = null;
    generierungLaeuft = false;
    resetSearchParametersModel();
  }

  public void erzeugeErgebniseingaenge() {
    getProgressMonitor().reset();

    generierungLaeuft = true;
    getProgressMonitor().progress(ErgebniseingangGenerierenProgress.STARTE_GENERIERUNG);

    stimmeingabeFiktivwerteBatchCreate
        .create(getWahldurchfuehrung(), getGebieteErzeugung(), getErgebniseingangsart(), getErfasser(), getProgressMonitor(), getHochrechnungsalgorithmusArt(),
            getHochrechnungsalgorithmusKonfiguration());
  }

  private HochrechnungsalgorithmusKonfiguration getHochrechnungsalgorithmusKonfiguration() {
    HochrechnungsalgorithmusArt hochrechnungsalgorithmusArt = getHochrechnungsalgorithmusArt();
    if (hochrechnungsalgorithmusArt != null) {
      konfigurationNachHochrechnungsalgorithmusArt.computeIfAbsent(hochrechnungsalgorithmusArt, HochrechnungsalgorithmusArt::getKonfiguration);
      return konfigurationNachHochrechnungsalgorithmusArt.get(hochrechnungsalgorithmusArt);
    }
    return null;
  }

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

  public int getGebieteErzeugungAnzahl() {
    return getGebieteErzeugung().size();
  }

  private Collection<Wahldurchfuehrungsgebiet> getGebieteErzeugung() {
    if (gebieteErzeugung == null) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = getSelectedWahldurchfuehrungsgebiet();

      gebieteErzeugung = new ArrayList<>();
      gebieteErzeugung.addAll(
          wahldurchfuehrungsgebiet.getInGebietshierarchieUntergeordneteGebiete().stream().map(gebiet -> castTo(gebiet, Wahldurchfuehrungsgebiet.class))
              .collect(toList()));
      gebieteErzeugung.add(wahldurchfuehrungsgebiet);
      gebieteErzeugung.removeIf(gebiet -> !gebiet.isErfassungsgebiet());

      if (!isFiktiveWerteErzeugenMoeglich()) {
        addErrorMessage(KeineWahlberechtigtenImportiertException.MESSAGE_KEY);
      }
    }

    return gebieteErzeugung;
  }

  protected boolean isFiktiveWerteErzeugenMoeglich() {
    return true;
  }

  public String getGebietName() {
    return gebietNummerFormatter.getNameMitNummer(getSelectedWahldurchfuehrungsgebiet());
  }

  private boolean isWahldurchfuehrungsgebietSelected() {
    return wahldurchfuehrungsgebietSelect.isSelected();
  }

  private Wahldurchfuehrungsgebiet getSelectedWahldurchfuehrungsgebiet() {
    return wahldurchfuehrungsgebietSelect.getEntity();
  }

  private FiktiveErgebniseingaengeErzeugenProgressMonitor getProgressMonitor() {
    return progressMonitor;
  }

  private Ergebniseingangsart getErgebniseingangsart() {
    return getWahldurchfuehrung().getErgebniseingangsart();
  }

  private Wahldurchfuehrung getWahldurchfuehrung() {
    return getSelectedWahldurchfuehrungsgebiet().getWahldurchfuehrung();
  }

  @Override
  public void cancelProgress() {
    progressMonitor.requestCancel();
    reset();

    FacesContextUtil.redirectToView(goBack(ErgebniseingangStatusView.VIEW_ID));
  }

  public void resetPartialUpdate() {
    hochrechnungsalgorithmusArt = getDefaultHochrechnerAlgorithmusArt();
    konfigurationNachHochrechnungsalgorithmusArt.clear();
    reset();
  }

  @Override
  public List<String> getProgressMessages() {
    return getProgressMonitor().getProgressMessages();
  }

  @Override
  public void poll() {
    if (progressMonitor.isCompleted()) {
      generierungLaeuft = false;
    }
  }

  @Override
  public int getProgressBar() {
    return getProgressMonitor().getProgressBar().get();
  }

  @Override
  public boolean isModalDialogProgressVisible() {
    return isGenerierungLaeuft();
  }

  @Override
  public boolean isGenerierungLaeuft() {
    return generierungLaeuft;
  }

  @Override
  public boolean isBtnCancelRendered() {
    return true; // ELECT-5276
  }

  public boolean isBtnGenerierenDisabled() {
    return isGenerierungLaeuft() || !isFiktiveWerteErzeugenMoeglich() || isSlave();
  }

  // WASDE-1153
  private boolean isSlave() {
    return replikationPresentationStatus.isSlave();
  }

  public boolean isBtnCancelDisabled() {
    return isGenerierungLaeuft();
  }

  public boolean isBtnResetPartialUpdateDisabled() {
    return isGenerierungLaeuft();
  }

  public AdvancedDynaFormModel getModel() {
    if (isNull(model)) {
      model = createModel();
    }

    return model;
  }

  public void resetSearchParametersModel() {
    model = null;
  }

  protected AdvancedDynaFormModel createModel() {
    FormBuilder formBuilder = createFormBuilder();

    addProperties(formBuilder);

    return formBuilder.createModel();
  }
}
