
package de.ivu.elect.presentation.ergebnis;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.annotation.Nullable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.NoResultException;
import javax.swing.text.DateFormatter;

import org.primefaces.model.chart.AxisType;
import org.primefaces.model.chart.CategoryAxis;
import org.primefaces.model.chart.LineChartModel;
import org.primefaces.model.chart.LineChartSeries;

import de.ivu.elect.business.ergebnis.boundary.ErgebnisExtendedDAO;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisSessionSelect;
import de.ivu.elect.business.ergebnis.boundary.Ergebnisberechnung;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisberechnungHintergrund;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisberechnungStatistikStore;
import de.ivu.elect.business.ergebnis.boundary.StimmenExtendedDAO;
import de.ivu.elect.business.ergebnis.control.Zwischenergebnisberechnung;
import de.ivu.elect.business.ergebnis.control.ZwischenergebnisberechnungBackgroundService;
import de.ivu.elect.business.ergebnis.control.ZwischenergebnisberechnungStatus;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisConstants;
import de.ivu.elect.business.ergebnis.entity.ErgebnisProperties;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.ErgebnisberechnungStatistikEntry;
import de.ivu.elect.business.ergebnis.entity.ErgebnisberechnungStatus;
import de.ivu.elect.business.ergebniskonflikt.boundary.ErgebnisKonfliktExtendedDAO;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.business.security.control.ElectCommonPrincipal;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangExtendedDAO;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.boundary.WahlSessionSelect;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlParameterExtendedLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.jee.background.AbstractBackgroundService;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamicform.AbstractBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.AbstractIntegerProperty;
import de.ivu.elect.jee.presentation.dynamicform.AbstractStringProperty;
import de.ivu.elect.jee.presentation.dynamicform.AbstractTimestampTimeProperty;
import de.ivu.elect.jee.presentation.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.DynamicFormEditOrCreateView;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyLongProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyStringProperty;
import de.ivu.elect.jee.presentation.security.StartViewResolver;
import de.ivu.elect.jee.presentation.view.AbstractView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;

@OnlineHilfeId("333")
@RequestScoped
@Named(ErgebnisStatusView.COMPONENT_NAME)
@View(viewId = ErgebnisStatusView.VIEW_ID, useCase = UseCasePermission.ERGEBNIS_STATUSUEBERSICHT_ANZEIGEN)
public class ErgebnisStatusView extends AbstractView implements DynamicFormEditOrCreateView, ViewHavingErgebnisStatusInfo {

  private static final long serialVersionUID = 1L;

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

  public static final String COMPONENT_NAME = "ergebnisStatusView";

  @Inject
  private transient ElectCommonPrincipal principal;

  @Inject
  private transient ErgebniseingangExtendedDAO ergebniseingangExtendedDAO;

  @Inject
  private transient ErgebnisKonfliktExtendedDAO ergebnisKonfliktExtendedDAO;

  @Inject
  private transient ErgebnisExtendedDAO ergebnisExtendedDAO;

  @Inject
  private transient StimmenExtendedDAO stimmenExtendedDAO;

  @Inject
  private transient WahlSessionSelect wahlSelect;

  @Inject
  private transient ErgebnisSessionSelect ergebnisSelect;

  @Inject
  private transient Ergebnisberechnung ergebnisberechnung;

  @Inject
  private transient WahlParameterExtendedLoader wahlParameterExtendedLoader;

  @Inject
  private transient ErgebnisberechnungHintergrund ergebnisberechnungHintergrund;

  @Inject
  private transient ErgebnisberechnungStatistikStore ergebnisberechnungStatistikStore;

  @Inject
  private transient StartViewResolver startViewResolver;

  @Inject
  private transient ErgebniseingangKonfiguration ergebniseingangKonfiguration;

  private AdvancedDynaFormModel model;

  private LineChartModel ergebnisDauerLineChartModel;

  private boolean ergebnisDauerLineChartRendered = false;

  public ErgebnisStatusView() {
    super();
  }

  @PreConditionCheck
  public String checkErgebnisSelected() {
    if (!ergebnisSelect.isSelected()) {
      return startViewResolver.getStartViewId();
    }

    return null;
  }

  public Wahl getWahl() {
    return wahlSelect.getEntity();
  }

  public Ergebnis getErgebnis() {
    return ergebnisSelect.getEntity();
  }

  @Override
  public AdvancedDynaFormModel getModel() {
    if (model == null) {
      FormBuilder formBuilder = new FormBuilder();

      Ergebnis ergebnis = getErgebnis();

      if (ergebnis != null) {
        addProperties(formBuilder, ergebnis);
      }

      model = formBuilder.createModel();
    }

    return model;
  }

  protected void addProperties(FormBuilder formBuilder, Ergebnis ergebnis) {
    ErgebnisProperties ergebnisProperties = ergebnis.getProperties();

    formBuilder.addSeparator("ergebnis.status.berechnung");

    formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("ergebnis.status.berechnung.aktiviert"), isErgebnisBerechnungAktiviert()));

    formBuilder.addProperty(
        new ReadOnlyBooleanProperty(getMessage("ergebnis.status.berechnung.laeuft.gerade"), ZwischenergebnisberechnungBackgroundService.RUNNING.get() > AbstractBackgroundService.RUNNING_INITIAL));

    ZwischenergebnisberechnungStatus zwischenergebnisberechnungStatus = Zwischenergebnisberechnung.getZwischenergebnisberechnungStatus();

    if (zwischenergebnisberechnungStatus != null) {
      formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("ergebnis.status.berechnung.wahl"), zwischenergebnisberechnungStatus.getWahl()));
      formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("ergebnis.status.berechnung.ergebnisart"), getMessage(zwischenergebnisberechnungStatus.getErgebnisart())));
      formBuilder.addProperty(new ReadOnlyTimestampTime(getMessage("ergebnis.status.berechnung.gestartet"), zwischenergebnisberechnungStatus.getGestartet()));
      formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("ergebnis.status.berechnung.lose.automatisch.loesen"), zwischenergebnisberechnungStatus.isLoeseKonflikte()));
    }

    formBuilder.addSeparator(ErgebnisConstants.MESSAGE_KEY_ENTITY_EINZAHL);

    formBuilder.addProperty(ergebnisProperties.getErgebnisartProperty()).readonly();
    formBuilder.addProperty(ergebnisProperties.getErgebnisStatusProperty()).readonly();
    formBuilder.addProperty(ergebnisProperties.getZeitstempelProperty()).readonly();
    formBuilder.addProperty(ergebnisProperties.getEingangssequenznummerProperty()).readonly();

    formBuilder.addProperty(new ReadOnlyLongProperty(getMessage("ergebnis.status.eingang.groesste.ergebnissequenznummer"), getByMaxErgebnissequenznummerByWahlUndErgebniseingangsarten()));
    formBuilder.addProperty(new ReadOnlyLongProperty(getMessage("ergebnis.status.eingang.keine.ergebnissequenznummer"),
        ergebniseingangExtendedDAO.findByCountErgebnissequenznummerIsNullByWahlEingangsArt(getWahl(), Ergebniseingangsart.getKorrespondierendeErgebniseingangsart(ergebnis))));

    // formBuilder.addProperty(ergebnisProperties.getAusloeserProperty()).readonly();
    formBuilder.addProperty(ergebnisProperties.getSitzberechnungsalgorithmusProperty()).readonly();

    formBuilder.addSeparator("ergebnis.status.separator.losentscheide");
    formBuilder.addProperty(new LosentscheideAnzahlGesamtProperty()).readonly();
    formBuilder.addProperty(new LosentscheideAnzahlOffenProperty()).readonly();


    formBuilder.addSeparator("ergebnis.status.separator.ergebnis.vorperiode");
    formBuilder.addProperty(new ErgebnisVorperiodeVorhandenProperty()).readonly();


    //  ELECTMUC-934 war nur für die Flexpräsentation relevant
    //    if (getErgebnisVorperiode() != null) {
    //      formBuilder.addProperty(new ErgebnisVorperiodeDatumAlsStringProperty()).readonly();
    //    }
  }

  @Override
  public ErgebnisStatusInfo getErgebnisStatusInfo() {
    return new WasErgebnisStatusInfo(getMessages(), getWahl(), ergebniseingangKonfiguration, getErgebnis());
  }

  protected Boolean isErgebnisBerechnungAktiviert() {
    Wahl wahl = getWahl();
    if (wahl == null) {
      return false;
    }

    return ergebnisberechnungHintergrund.isAktiviert() && wahlParameterExtendedLoader.resolveEnumValue(wahl, WahlParameterWerte.ERGEBNISBERECHNUNG_STATUS, ErgebnisberechnungStatus.class).isAktiv();
  }

  @Nullable
  protected Long getByMaxErgebnissequenznummerByWahlUndErgebniseingangsarten() {
    try {
      return ergebniseingangExtendedDAO.findByMaxErgebnissequenznummerByWahlUndErgebniseingangsart(getWahl(), Ergebniseingangsart.getKorrespondierendeErgebniseingangsart(getErgebnis()));
    } catch (NoResultException nre) {
      return null;
    }
  }

  public int getAnzahlLosentscheideGesamt() {
    return ergebnisKonfliktExtendedDAO.findByErgebnisOrderById(getErgebnis()).size();
  }

  public int getAnzahlLosentscheideOffen() {
    int result = 0;

    List<ErgebnisKonflikt> losentscheide = ergebnisKonfliktExtendedDAO.findByErgebnisOrderById(getErgebnis());

    for (ErgebnisKonflikt ergebnisKonflikt : losentscheide) {
      if (!ergebnisKonflikt.isGeloest()) {
        result++;
      }
    }

    return result;
  }

  private Ergebnis getErgebnisVorperiode() {
    if (getWahl() == null) {
      return null;
    }

    List<Ergebnis> ergebnisseVorperiode = ergebnisExtendedDAO.findByWahlAndErgebnisart(getWahl(), Ergebnisart.VORPERIODE);

    if (ergebnisseVorperiode.isEmpty()) {
      return null;
    }

    return ergebnisseVorperiode.get(0);
  }

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

  @Override
  public String getBtnSaveLabel() {
    return getMessage("entity.edit.action.save");
  }

  @Override
  public String save() {
    return null;
  }

  @Override
  public boolean isBtnResetRendered() {
    return true;
  }

  @Override
  public String reset() { // A.k.a "Aktualisieren"
    resetModel();

    return null;
  }

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

  @Override
  public String cancel() {
    return null;
  }

  public boolean isBtnBerechnenDisabled() {
    return !isErgebnisBerechnungAktiviert();

  }

  public boolean isBtnBerechnenRendered() {
    return principal.hasUseCasePermission(UseCasePermission.ERGEBNIS_BERECHNUNG_AUSLOESEN);
  }

  @UseCaseSecure(UseCasePermission.ERGEBNIS_BERECHNUNG_AUSLOESEN)
  public void berechnen() {
    resetModel();

    ergebnisberechnung.ergebnisBerechnenAusloesenOhneUebernahme(getErgebnis());
    ergebnisExtendedDAO.refresh(getErgebnis());

    addInfoMessage("ergebnis.berechnung.ausgeloest");
  }

  @UseCaseSecure(UseCasePermission.ERGEBNIS_BERECHNUNG_AUSLOESEN)
  public void berechnenUebernehmen() {
    resetModel();

    ergebnisberechnung.ergebnisBerechnenAusloesenUebernahmeGueltigerEingaenge(getErgebnis());
    ergebnisExtendedDAO.refresh(getErgebnis());

    addInfoMessage("ergebnis.berechnung.ausgeloest");
  }

  public boolean isErgebnisDauerLineChartRendered() {
    getErgebnisDauerLineChartModel(); // baue Modell wenn noch nicht erfolgt

    return ergebnisDauerLineChartRendered;
  }

  public LineChartModel getErgebnisDauerLineChartModel() {
    if (ergebnisDauerLineChartModel == null) {
      ergebnisDauerLineChartModel = erzeugeErgebnisDauerLineChartModel();
    }

    return ergebnisDauerLineChartModel;
  }

  private LineChartModel erzeugeErgebnisDauerLineChartModel() {
    LineChartSeries dauerSeries = new LineChartSeries();
    dauerSeries.setLabel("Dauer in ms");

    LineChartModel lineChartModel = new LineChartModel();
    lineChartModel.addSeries(dauerSeries);

    lineChartModel.setLegendPosition("ne");
    lineChartModel.getAxes().put(AxisType.X, new CategoryAxis("Zeit"));
    lineChartModel.getAxis(AxisType.X).setTickAngle(270);
    lineChartModel.getAxis(AxisType.Y).setMin(0);
    lineChartModel.getAxis(AxisType.Y).setLabel(" Millisekunden ");
    lineChartModel.setZoom(false);

    for (ErgebnisberechnungStatistikEntry ergebnisberechnungStatistikEntry : ergebnisberechnungStatistikStore.getStatistikFuerErgebnis(getErgebnis())) {
      ergebnisDauerLineChartRendered = true;

      dauerSeries.set(getUserLocale().formatTime(ergebnisberechnungStatistikEntry.getZeitpunkt()), ergebnisberechnungStatistikEntry.getDauerInMs());
    }

    return lineChartModel;
  }

  private void resetModel() {
    model = null;
  }

  public class LosentscheideAnzahlGesamtProperty extends AbstractIntegerProperty {

    @Override
    public Integer getValue() {
      return getAnzahlLosentscheideGesamt();
    }

    @Override
    public void setValue(Integer arg0) {
    }

    @Override
    public String getDefaultLabel() {
      return getMessage("ergebnis.status.label.losentscheide.anzahl.gesamt");
    }

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

  public class LosentscheideAnzahlOffenProperty extends AbstractIntegerProperty {

    @Override
    public Integer getValue() {
      return getAnzahlLosentscheideOffen();
    }

    @Override
    public void setValue(Integer arg0) {
    }

    @Override
    public String getDefaultLabel() {
      return getMessage("ergebnis.status.label.losentscheide.anzahl.offen");
    }

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

  public class ErgebnisVorperiodeVorhandenProperty extends AbstractBooleanProperty {

    @Override
    public Boolean getValue() {
      return stimmenExtendedDAO.findByCountStimmenByWahlAndErgebnisart(getWahl(), Ergebnisart.VORPERIODE) > 0L;
    }

    @Override
    public void setValue(Boolean value) {
    }

    @Override
    public String getDefaultLabel() {
      return getMessage("ergebnis.status.label.ergebnis.vorperiode.vorhanden");
    }

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

  public class ErgebnisVorperiodeDatumAlsStringProperty extends AbstractStringProperty {

    @Override
    public String getValue() {
      Ergebnis ergebnisVorperiode = getErgebnisVorperiode();
      if (ergebnisVorperiode != null && ergebnisVorperiode.getZeitstempel() != null) {
        DateFormatter dateFormatter = new DateFormatter(DateFormat.getDateInstance());
        try {
          return dateFormatter.valueToString(ergebnisVorperiode.getZeitstempel());
        } catch (ParseException e) {
          throw new IllegalStateException("Der Zeitstempel des Vorperiodenergebnisses kann vom DateFormatter nicht geparst werden.", e);
        }
      }
      if (ergebnisVorperiode != null) {
        addWarningMessage("ergebnis.status.warnung.ergebnis.vorperiode.datum.nicht.gesetzt");
      }
      return getMessage("ergebnis.status.ergebnis.vorperiode.datum.nicht.gesetzt");
    }

    @Override
    public void setValue(String value) {
    }

    @Override
    public int getDefaultMaxLength() {
      return 255;
    }

    @Override
    public String getDefaultLabel() {
      return getMessage("ergebnis.status.label.ergebnis.vorperiode.datum");
    }

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

  public class ReadOnlyTimestampTime extends AbstractTimestampTimeProperty {

    private final String label;

    private final Date value;

    public ReadOnlyTimestampTime(String label, Date value) {
      super();

      this.label = label;
      this.value = value;
    }

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

    @Override
    public boolean isReadonly() {
      return true;
    }

    @Override
    public String getDefaultLabel() {
      return label;
    }

    @Override
    public Date getValue() {
      return value;
    }

    @Override
    public void setValue(Date date) {
      // nichts zu tun
    }

    @Override
    protected DateFormat createDateFormat(Locale locale) {
      return DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
    }
  }
}
