
package de.ivu.elect.presentation.gruppe;

import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Function;

import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
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.PrimeFaces;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.boundary.GruppeKumulierteStimmenSearch;
import de.ivu.elect.business.gruppe.boundary.GruppeKumulierteStimmenSearchParameters;
import de.ivu.elect.business.gruppe.boundary.KopfzahlKumulierteStimmenSearch;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeKumulierteStimmen;
import de.ivu.elect.business.gruppe.entity.KopfzahlKumulierteStimmen;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescriptionResolver;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenExtendedDAOFactory;
import de.ivu.elect.jee.faces.HttpHeaderContentSecurityPolicy;
import de.ivu.elect.jee.presentation.AjaxHelper;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.charts.BarChart;
import de.ivu.elect.presentation.charts.BarChartDataset;
import de.ivu.elect.presentation.charts.BarChartEntry;
import de.ivu.elect.presentation.gruppe.table.GruppeKumulierteStimmenColumnBuilder;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;

@OnlineHilfeId("256")
@ViewAccessScoped
@View(viewId = GruppeKumulierteStimmenSearchView.VIEW_ID, useCase = UseCasePermission.ERGEBNIS_KUMULIERTE_STIMMEN_ANZEIGEN)
@Named(GruppeKumulierteStimmenSearchView.COMPONENT_NAME)
@HttpHeaderContentSecurityPolicy(policy = "script-src", add = "'unsafe-eval'")
public class GruppeKumulierteStimmenSearchView extends AbstractGruppeKumulierteStimmenSearchView<GruppeKumulierteStimmenSearchParameters> {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "gruppeKumulierteStimmenSearchView";

  public static final String VIEW_ID = "/gruppe/gruppe-kumulierte-stimmen-search.xhtml";

  @Inject
  private transient EntityAufStimmzettelDatenExtendedDAOFactory entityAufStimmzettelDatenExtendedDAOFactory;

  @Inject
  private transient StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver;

  @Inject
  private transient KopfzahlKumulierteStimmenSearch kopfzahlKumulierteStimmenSearch;

  @Inject
  private transient GruppeKumulierteStimmenSearch gruppeKumulierteStimmenSearch;

  @Inject
  @ConversationGroup(GruppeKumulierteStimmenSearchParameters.class)
  private transient GruppeKumulierteStimmenSearchParameters gruppeKumulierteStimmenSearchParameters;

  @Inject
  private transient GruppeKumulierteStimmenColumnBuilder gruppeKumulierteStimmenColumnBuilder;

  private List<KopfzahlKumulierteStimmen> kopfzahlKumulierteStimmen;

  private BarChart barChartModelDirektstimmen;

  private BarChart barChartModelListenstimmen;

  private BarChart barChartModelGesamtstimmen;

  private BarChart barChartModelGewinnVerlustDirektstimmen;

  private BarChart barChartModelGewinnVerlustListenstimmen;

  private BarChart barChartModelGewinnVerlustGesamtstimmen;

  private NumberFormat percentInstance;

  private Boolean vorperiodenStimmenVorhanden;

  private int selectedTabIndex = 0;

  private Boolean isTabGesamtstimmenRendered;

  public GruppeKumulierteStimmenSearchView() {
    super();
  }

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

  public void preRenderView() {
  }

  public List<GruppeKumulierteStimmen> getGruppeKumulierteStimmen() {
    List<GruppeKumulierteStimmen> result = new ArrayList<>(getSearchResult());

    boolean gruppeKumulierteStimmenMessagesRendered = kopfzahlKumulierteStimmen == null;

    List<KopfzahlKumulierteStimmen> kopfzahlenKumulierteStimmen = getKopfzahlKumulierteStimmen(); // WASDE-1201

    // Bei mehr Einträgen als nur Wahlberechtigte gesamt und/oder Wähler gesamt braucht man die Beschreibung zur Unterscheidung!
    boolean kopfzahlenKumulierteStimmenBeschreibungInNameRequired = kopfzahlenKumulierteStimmen.stream().anyMatch(entry -> !Stimmenart.GESAMT.equals(entry.getStimmenart()));

    result.addAll(0, kopfzahlenKumulierteStimmen.stream() //
        .map(entry -> asGruppeKumulierteStimmenEntry(entry, kopfzahlenKumulierteStimmenBeschreibungInNameRequired)) //
        .collect(toList()));

    if (gruppeKumulierteStimmenMessagesRendered) {
      for (GruppeKumulierteStimmen gruppeKumulierteStimmen : result) {
        for (Entry<Severity, Map<String, Object[]>> entryPerSeverity : gruppeKumulierteStimmen.getMessagesPerSeverity().entrySet()) {
          Severity severity = entryPerSeverity.getKey();
          BiConsumer<String, Object[]> severityFct = this::addInfoMessage;

          if (FacesMessage.SEVERITY_ERROR.equals(severity)) {
            severityFct = this::addErrorMessage;
          } else if (FacesMessage.SEVERITY_WARN.equals(severity)) {
            severityFct = this::addWarningMessage;
          }

          for (Entry<String, Object[]> paramsPerMsgKey : entryPerSeverity.getValue().entrySet()) {
            severityFct.accept(paramsPerMsgKey.getKey(), paramsPerMsgKey.getValue());
          }
        }
      }
    }

    return result;
  }

  public List<KopfzahlKumulierteStimmen> getKopfzahlKumulierteStimmen() {
    if (kopfzahlKumulierteStimmen == null) {
      kopfzahlKumulierteStimmen = kopfzahlKumulierteStimmenSearch.getSearchResult(gruppeKumulierteStimmenSearchParameters);
    }

    return kopfzahlKumulierteStimmen;
  }

  private void resetKopfzahlKumulierteStimmen() {
    kopfzahlKumulierteStimmen = null;
  }

  @Override
  protected List<GruppeKumulierteStimmen> runSearch() {
    List<GruppeKumulierteStimmen> result = super.runSearch();

    if (!isKumulierteStimmenFuerPolitischeGruppenVorhanden(result)) {
      addInfoMessage("gruppe.kumulierte.stimmen.keine.stimmen.fuer.politische.gruppen.vorhanden.message");
    }

    // Schadet, nichts, notwendig für ELECTKOM-241
    PrimeFaces.current().ajax().update(AjaxHelper.GLOBAL_MESSAGES_ID);

    // WASDE-1291, falls eine neue Suche (nach Stimmen der Gruppen) ausgeführt wird, auch immer die erfassten Kopfzahlen neu laden!
    resetKopfzahlKumulierteStimmen();

    return result;
  }

  @Override
  protected GruppeKumulierteStimmenSearchParameters getSearchParametersInternal() {
    return gruppeKumulierteStimmenSearchParameters;
  }

  @Override
  protected GruppeKumulierteStimmenSearch getSearch() {
    return gruppeKumulierteStimmenSearch;
  }

  @Override
  protected GruppeKumulierteStimmenColumnBuilder getColumnBuilder() {
    return gruppeKumulierteStimmenColumnBuilder;
  }

  @Override
  public void resetSearchResult() {
    barChartModelDirektstimmen = null;
    barChartModelListenstimmen = null;
    barChartModelGesamtstimmen = null;

    barChartModelGewinnVerlustDirektstimmen = null;
    barChartModelGewinnVerlustListenstimmen = null;
    barChartModelGewinnVerlustGesamtstimmen = null;

    vorperiodenStimmenVorhanden = null;

    resetKopfzahlKumulierteStimmen();

    super.resetSearchResult();
  }

  @Override
  protected boolean isSearchResultAufDiesemGebietAnzeigen() {
    return super.isSearchResultAufDiesemGebietAnzeigen() && isKumulierteStimmenFuerPolitischeGruppenVorhanden(getSearchResult());
  }

  // ELECT-2907 (Keine kumulierten Stimmen oberhalb von Wahlgebiet anzeigen)
  public boolean isKumulierteStimmenFuerPolitischeGruppenVorhanden(List<GruppeKumulierteStimmen> searchResult) {
    return searchResult.stream().anyMatch(gruppeKumulierteStimmen -> !gruppeKumulierteStimmen.isSystemGruppe());
  }

  public BarChart getBarChartDirektstimmen() {
    if (barChartModelDirektstimmen == null) {
      barChartModelDirektstimmen = buildBarModelLong(GruppeKumulierteStimmen::getAnzahlDirektstimmen, GruppeKumulierteStimmen::getNameDirektkandidat,
          GruppeKumulierteStimmen::isHatDirektkandidatenInGebietshierarchie, getLabelAbsolut());
    }

    return barChartModelDirektstimmen;
  }

  public BarChart getBarChartListenstimmen() {
    if (barChartModelListenstimmen == null) {
      barChartModelListenstimmen = buildBarModelLong(GruppeKumulierteStimmen::getAnzahlListenstimmen, getBarChartListenstimmenLabelFunction(), GruppeKumulierteStimmen::isHatListeInGebietshierarchie,
          getLabelAbsolut());
    }

    return barChartModelListenstimmen;
  }

  protected Function<GruppeKumulierteStimmen, String> getBarChartListenstimmenLabelFunction() {
    return GruppeKumulierteStimmen::getNameOhneDirektkandidat;
  }

  public BarChart getBarChartGesamtstimmen() {
    if (barChartModelGesamtstimmen == null) {
      barChartModelGesamtstimmen = buildBarModelLong(GruppeKumulierteStimmen::getAnzahlGesamtstimmen, GruppeKumulierteStimmen::getNameOhneDirektkandidat, s -> true, getLabelAbsolut());
    }

    return barChartModelGesamtstimmen;
  }

  public BarChart getBarChartGewinnVerlustDirektstimmen() {
    if (barChartModelGewinnVerlustDirektstimmen == null) {
      if (isVorperiodenStimmenVorhanden()) {
        barChartModelGewinnVerlustDirektstimmen = buildBarModelPercent(GruppeKumulierteStimmen::getAnteilDirektstimmenDifferenz, GruppeKumulierteStimmen::getNameDirektkandidat,
            GruppeKumulierteStimmen::isHatDirektkandidatenInGebietshierarchie, getLabelGewinnVerlust());
      }
    }

    return barChartModelGewinnVerlustDirektstimmen;
  }

  public BarChart getBarChartGewinnVerlustListenstimmen() {
    if (barChartModelGewinnVerlustListenstimmen == null) {
      if (isVorperiodenStimmenVorhanden()) {
        barChartModelGewinnVerlustListenstimmen = buildBarModelPercent(GruppeKumulierteStimmen::getAnteilListenstimmenDifferenz, getBarChartListenstimmenLabelFunction(),
            GruppeKumulierteStimmen::isHatListeInGebietshierarchie, getLabelGewinnVerlust());
      }
    }

    return barChartModelGewinnVerlustListenstimmen;
  }

  public BarChart getBarChartGewinnVerlustGesamtstimmen() {
    if (barChartModelGewinnVerlustGesamtstimmen == null) {
      if (isVorperiodenStimmenVorhanden()) {
        barChartModelGewinnVerlustGesamtstimmen = buildBarModelPercent(GruppeKumulierteStimmen::getAnteilGesamtstimmenDifferenz, GruppeKumulierteStimmen::getNameOhneDirektkandidat, s -> true,
            getLabelGewinnVerlust());
      }
    }

    return barChartModelGewinnVerlustGesamtstimmen;
  }

  protected NumberFormat getPercentInstance() {
    if (percentInstance == null) {
      percentInstance = NumberFormat.getPercentInstance(getUserLocale().getLocale());

      if (percentInstance instanceof DecimalFormat) {
        DecimalFormat df = (DecimalFormat) percentInstance;
        df.setDecimalSeparatorAlwaysShown(true);
        df.setMinimumFractionDigits(2);
        df.setMaximumFractionDigits(2);
      }
    }

    return percentInstance;
  }

  protected BarChart buildBarModelLong(Function<GruppeKumulierteStimmen, Number> anzahlFunction, Function<GruppeKumulierteStimmen, String> labelFunction,
      Function<GruppeKumulierteStimmen, Boolean> hatStimmen, String datasetLabel) {
    return buildBarModel(anzahlFunction, labelFunction, hatStimmen, datasetLabel, false);
  }

  protected BarChart buildBarModelPercent(Function<GruppeKumulierteStimmen, Number> anzahlFunction, Function<GruppeKumulierteStimmen, String> labelFunction,
      Function<GruppeKumulierteStimmen, Boolean> hatStimmen, String datasetLabel) {
    return buildBarModel(anzahlFunction, labelFunction, hatStimmen, datasetLabel, true);
  }

  protected BarChart buildBarModel(Function<GruppeKumulierteStimmen, Number> anzahlFunction, Function<GruppeKumulierteStimmen, String> labelFunction,
      Function<GruppeKumulierteStimmen, Boolean> hatStimmen, String datasetLabel, boolean percent) {
    BarChart barChart;

    if (percent) {
      barChart = BarChart.createHorizontalBarChartPercent();
    } else {
      barChart = BarChart.createHorizontalBarChart();
    }

    BarChartDataset dataset = barChart.createDataset(datasetLabel);

    for (GruppeKumulierteStimmen gruppeKumulierteStimmen : getSearchResult()) {
      Gruppe gruppe = gruppeKumulierteStimmen.getGruppe();

      if (hatStimmen.apply(gruppeKumulierteStimmen) && (gruppe.isPolitischeGruppe() || percent || SystemGruppe.UNGUELTIGE.equalsGruppe(gruppe, getSystemGruppeEigenschaften())
          || SystemGruppe.UEBRIGE.equalsGruppe(gruppe, getSystemGruppeEigenschaften()))) {
        Number anzahl = anzahlFunction.apply(gruppeKumulierteStimmen);
        String label = labelFunction.apply(gruppeKumulierteStimmen);

        if (label != null && anzahl != null && isNotBlank(label)) {
          BarChartEntry entry = barChart.createEntry(label, gruppe.getFarbe());

          if (percent) {
            dataset.addData(entry, anzahl.doubleValue() * 100d);
          } else {
            dataset.addData(entry, anzahl);
          }
        }
      }
    }

    return barChart;
  }

  public boolean isVorperiodenStimmenVorhanden() {
    if (vorperiodenStimmenVorhanden == null) {
      vorperiodenStimmenVorhanden = bestimmeVorperiodenStimmenVorhanden();
    }

    return vorperiodenStimmenVorhanden;
  }

  private Boolean bestimmeVorperiodenStimmenVorhanden() {
    return getSearchResult().stream().anyMatch(GruppeKumulierteStimmen::isVorperiodenStimmenVorhanden);
  }

  public String getLabelDirektstimmen() {
    if (isWahlMitListengebieten() || isGesamtstimmenAuswerten()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = getSelectedGebiet();

      if (wahldurchfuehrungsgebiet == null) {
        return "";
      }

      return stimmeingabeErfassenDescriptionResolver.getStimmeingabeErfassenDescriptionByGebiet(wahldurchfuehrungsgebiet).getStimmeingabeGruppeStimmenErfassenLabelGenerator()
          .getStimmenartName(wahldurchfuehrungsgebiet, Stimmenart.DIREKTSTIMME);
    }

    return getMessage("gruppe.kumulierte.stimmen.stimmen");
  }

  public String getLabelAbsolut() {
    return getMessage("stimmen.absolut.label");
  }

  public String getLabelGewinnVerlust() {
    return getMessage("stimmen.gewinn.verlust.prozent.punkt");
  }

  public String getLabelListenstimmen() {
    if (isWahlMitDirektkandidatengebieten() || isGesamtstimmenAuswerten()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = getSelectedGebiet();

      if (wahldurchfuehrungsgebiet == null) {
        return "";
      }

      return stimmeingabeErfassenDescriptionResolver.getStimmeingabeErfassenDescriptionByGebiet(wahldurchfuehrungsgebiet).getStimmeingabeGruppeStimmenErfassenLabelGenerator()
          .getStimmenartName(wahldurchfuehrungsgebiet, Stimmenart.LISTENSTIMME);
    }

    return getMessage("gruppe.kumulierte.stimmen.stimmen");
  }

  public String getLabelGesamtstimmen() {
    if (isWahlMitDirektkandidatengebieten() || isWahlMitListengebieten()) {
      return getMessage("gruppe.kumulierte.stimmen.gesamtstimmen");
    }

    return getMessage("gruppe.kumulierte.stimmen.stimmen");
  }

  //WASDE-1182
  public boolean isFirstTabSelected() {
    if (selectedTabIndex == 0) {
      return true;
    }

    return false;
  }

  public int getSelectedTabIndex() {
    return selectedTabIndex;
  }

  public void setSelectedTabIndex(int selectedTabIndex) {
    this.selectedTabIndex = selectedTabIndex;
  }

  private GruppeKumulierteStimmen asGruppeKumulierteStimmenEntry(KopfzahlKumulierteStimmen kopfzahlKumulierteStimmen, boolean beschreibungInNameRequired) {
    KopfzahlKumulierteStimmen result = new KopfzahlKumulierteStimmen(entityAufStimmzettelDatenExtendedDAOFactory.getEntityAufStimmzettelDaten(), getSystemGruppeEigenschaften(),
        kopfzahlKumulierteStimmen.getGruppeImGebiet(), kopfzahlKumulierteStimmen.getStimmenart(), kopfzahlKumulierteStimmen.getAnzahlDirektstimmen(),
        kopfzahlKumulierteStimmen.getAnzahlListenstimmen()) {

      private static final long serialVersionUID = 1L;

      @Override
      public Long getAnzahlDirektstimmen() {
        return getAnzahl();
      }

      @Override
      public Long getAnzahlDirektstimmenVorperiode() {
        return getAnzahlVorperiode();
      }

      @Override
      public Double getAnteilDirektstimmen() {
        return getAnteil();
      }

      @Override
      public String getAnteilDirektstimmenTooltip() {
        return getAnteilTooltip();
      }

      @Override
      public Double getAnteilDirektstimmenVorperiode() {
        return getAnteilVorperiode();
      }

      @Override
      public String getAnteilDirektstimmenVorperiodeTooltip() {
        return getAnteilVorperiodeTooltip();
      }

      @Override
      public Double getAnteilDirektstimmenDifferenz() {
        return getAnteilDifferenz();
      }

      @Override
      public Long getAnzahlListenstimmen() {
        return getAnzahlDirektstimmen();
      }

      @Override
      public Long getAnzahlListenstimmenVorperiode() {
        return getAnzahlDirektstimmenVorperiode();
      }

      @Override
      public Double getAnteilListenstimmen() {
        return getAnteilDirektstimmen();
      }

      @Override
      public String getAnteilListenstimmenTooltip() {
        return getAnteilDirektstimmenTooltip();
      }

      @Override
      public Double getAnteilListenstimmenVorperiode() {
        return getAnteilDirektstimmenVorperiode();
      }

      @Override
      public String getAnteilListenstimmenVorperiodeTooltip() {
        return getAnteilDirektstimmenVorperiodeTooltip();
      }

      @Override
      public Double getAnteilListenstimmenDifferenz() {
        return getAnteilDirektstimmenDifferenz();
      }
    };

    String name = kopfzahlKumulierteStimmen.getName();
    String bezeichnung = kopfzahlKumulierteStimmen.getBezeichnung();

    if (beschreibungInNameRequired) {
      name = bezeichnung;
    }

    result.setPosition(kopfzahlKumulierteStimmen.getPosition());
    result.setKennung(kopfzahlKumulierteStimmen.getKennung());
    result.setBezeichnung(bezeichnung);
    result.setName(name);

    for (Entry<Severity, Map<String, Object[]>> entryPerSeverity : kopfzahlKumulierteStimmen.getMessagesPerSeverity().entrySet()) {
      Severity severity = entryPerSeverity.getKey();

      for (Entry<String, Object[]> paramsPerMsgKey : entryPerSeverity.getValue().entrySet()) {
        result.addMessage(severity, paramsPerMsgKey.getKey(), paramsPerMsgKey.getValue());
      }
    }

    result.setAnzahl(kopfzahlKumulierteStimmen.getAnzahl());
    result.setAnteilUngerundet(kopfzahlKumulierteStimmen.getAnteilUngerundet());
    result.setAnteilTooltip(kopfzahlKumulierteStimmen.getAnteilTooltip());
    result.setAnzahlVorperiode(kopfzahlKumulierteStimmen.getAnzahlVorperiode());
    result.setAnteilVorperiodeUngerundet(kopfzahlKumulierteStimmen.getAnteilVorperiodeUngerundet());
    result.setAnteilVorperiodeTooltip(kopfzahlKumulierteStimmen.getAnteilVorperiodeTooltip());

    return result;
  }

}
