
package de.ivu.elect.presentation.wahlbeteiligung;

import static de.ivu.elect.presentation.wahlbeteiligung.status.WahlbeteiligungErfassungsstatus.ERFASST;
import static de.ivu.elect.presentation.wahlbeteiligung.status.WahlbeteiligungErfassungsstatus.NICHT_ERFASST;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.time.DateUtils.isSameDay;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietSessionSelect;
import de.ivu.elect.business.gebietsbaum.control.GebietByGebietsartAndPositionComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.wahl.boundary.WahlSessionSelector;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlbeteiligung.boundary.WahlbeteiligungAuswertungExtendedDAO;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungAuswertung;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungAuswertungPK;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.jee.presentation.status.StatusEntry;
import de.ivu.elect.presentation.security.EinsehenGebietSecurity;
import de.ivu.elect.presentation.security.GebietSecurity;
import de.ivu.elect.presentation.stimmeingabe.CollapsedProperty;
import de.ivu.elect.presentation.wahlbeteiligung.chart.WahlbeteiligungAuswertungChart;
import de.ivu.elect.presentation.wahlbeteiligung.chart.WahlbeteiligungAuswertungChartBuilder;
import de.ivu.elect.presentation.wahlbeteiligung.status.WahlbeteiligungErfassungsstatus;
import de.ivu.elect.presentation.wahlbeteiligung.status.WahlbeteiligungSeparatorStatusEntry;
import de.ivu.elect.presentation.wahlbeteiligung.status.WahlbeteiligungStatusEntryFactory;

@RequestScoped
@Named(WahlbeteiligungAuswertungStatusuebersichtViewHelper.COMPONENT_NAME)
public class WahlbeteiligungAuswertungStatusuebersichtViewHelper {

  public static final String COMPONENT_NAME = "wahlbeteiligungAuswertungStatusuebersichtViewHelper";

  public static final int ENTRIES_SCHRITTWEISE_LADEN_SCHRITTGROESSE = 200;

  @Inject
  private transient Messages messages;

  @Inject
  protected transient UserLocale userLocale;

  @Inject
  protected transient WahlbeteiligungAuswertungExtendedDAO wahlbeteiligungAuswertungDAO;

  @Inject
  protected transient WahlSessionSelector wahlSessionSelector;

  @Inject
  protected transient WahldurchfuehrungsgebietSessionSelect wahldurchfuehrungsgebietSessionSelect;

  @Inject
  protected transient WahlbeteiligungAuswertungChartBuilder wahlbeteiligungAuswertungChartBuilder;

  @Inject
  protected transient WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietDAO;

  @Inject
  protected transient WahlbeteiligungStatusEntryFactory wahlbeteiligungAuswertungStatusEntryFactory;

  @Inject
  protected transient WahlbeteiligungAuswertungStatusViewState wahlbeteiligungAuswertungStatusViewState;

  @Inject
  protected transient WahlbeteiligungAuswertungChartBuilder chartBuilder;

  @Inject
  private transient GebietSecurity gebietSecurity;

  private List<WahlbeteiligungAuswertung> auswertungen;
  private List<WahlbeteiligungAuswertungChart> auswertungCharts;
  private final Map<WahlbeteiligungAuswertungPK, Map<WahlbeteiligungErfassungsstatus, List<StatusEntry>>> statusEntriesProWahlbeteiligungAuswertungUndStatus = new HashMap<>();

  public WahlbeteiligungAuswertung getAusgewaehlteAuswertung() {
    WahlbeteiligungAuswertung ausgewaehlteAuswertung = wahlbeteiligungAuswertungStatusViewState.getAusgewaehlteWahlbeteiligung();

    if (!getAuswertungen().contains(ausgewaehlteAuswertung)) {
      ausgewaehlteAuswertung = null;

      wahlbeteiligungAuswertungStatusViewState.setAusgewaehlteWahlbeteiligung(ausgewaehlteAuswertung);
    }

    return ausgewaehlteAuswertung;
  }

  public void setAusgewaehlteAuswertung(WahlbeteiligungAuswertung ausgewaehlteAuswertung) {
    resetCharts();

    wahlbeteiligungAuswertungStatusViewState.setAusgewaehlteWahlbeteiligung(ausgewaehlteAuswertung);
  }

  public List<WahlbeteiligungAuswertung> getAuswertungen() {
    if (isNull(auswertungen)) {
      auswertungen = wahlbeteiligungAuswertungDAO.findByWahl(wahlSessionSelector.getEntity());
    }

    return auswertungen;
  }

  public List<WahlbeteiligungAuswertungChart> getAuswertungCharts() {
    if (auswertungCharts == null && wahldurchfuehrungsgebietSessionSelect.isSelected()) {
      auswertungCharts = buildCharts();
    }

    return auswertungCharts;
  }

  private List<WahlbeteiligungAuswertungChart> buildCharts() {
    return chartBuilder.buildCharts(wahldurchfuehrungsgebietSessionSelect.getEntity(), getAusgewaehlteAuswertung());
  }

  public void resetCharts() {
    auswertungCharts = null;
  }

  public String getAuswertungItemLabel(WahlbeteiligungAuswertung auswertung) {
    Wahl wahl = auswertung.getWahl();

    if (isSameDay(auswertung.getDatum(), wahl.getWahldatum())) {
      return messages.getMessageForMessageKey("wahlbeteiligung.auswertung.statusuebersicht.item.label.nur.uhrzeit", userLocale.formatTimeHHmm(auswertung.getDatum()));
    }

    return messages.getMessageForMessageKey("wahlbeteiligung.auswertung.statusuebersicht.item.label", userLocale.formatDate(wahl.getWahldatum()), userLocale.formatTimeHHmm(auswertung.getDatum()));
  }

  public List<WahlbeteiligungErfassungsstatus> getVorhandeneStatus(WahlbeteiligungAuswertungPK auswertungPk) {

    final List<WahlbeteiligungErfassungsstatus> sortierung = WahlbeteiligungErfassungsstatus.getSortierungFuerStatusEntryPanels();

    return getStatusEntriesFuerAuswertung(auswertungPk).keySet().stream().sorted(Comparator.comparingInt(sortierung::indexOf)).collect(Collectors.toList());
  }

  public String lokalisiere(WahlbeteiligungAuswertungPK auswertungPk, WahlbeteiligungErfassungsstatus status) {
    return messages.getMessageForMessageKey(status.getPieChartLabel(), bestimmeAnzahl(getStatusEntriesFuerAuswertungUndStatus(auswertungPk, status)));
  }

  private long bestimmeAnzahl(List<StatusEntry> statusEntriesFuerAuswertungUndStatus) {
    return statusEntriesFuerAuswertungUndStatus.stream().filter(s -> !s.isSeparator()).count();
  }

  public CollapsedProperty getCollapsed(WahlbeteiligungAuswertungPK auswertungPk, WahlbeteiligungErfassungsstatus status) {

    long anzahl = bestimmeAnzahl(getStatusEntriesFuerAuswertungUndStatus(auswertungPk, status));

    return wahlbeteiligungAuswertungStatusViewState.getCollapsedProperty(auswertungPk, status, anzahl >= ENTRIES_SCHRITTWEISE_LADEN_SCHRITTGROESSE);
  }

  public Map<WahlbeteiligungAuswertungPK, Map<WahlbeteiligungErfassungsstatus, List<StatusEntry>>> getStatusEntriesProWahlbeteiligungAuswertungUndStatus() {
    return statusEntriesProWahlbeteiligungAuswertungUndStatus;
  }

  public List<StatusEntry> getStatusEntriesFuerAuswertungUndStatus(WahlbeteiligungAuswertungPK auswertungPk, WahlbeteiligungErfassungsstatus status) {
    return getStatusEntriesFuerAuswertung(auswertungPk).get(status);
  }

  private Map<WahlbeteiligungErfassungsstatus, List<StatusEntry>> getStatusEntriesFuerAuswertung(WahlbeteiligungAuswertungPK wahlbeteiligungAuswertungPk) {

    if (statusEntriesProWahlbeteiligungAuswertungUndStatus.containsKey(wahlbeteiligungAuswertungPk)) {
      return statusEntriesProWahlbeteiligungAuswertungUndStatus.get(wahlbeteiligungAuswertungPk);
    }

    Map<WahlbeteiligungErfassungsstatus, List<StatusEntry>> result = new EnumMap<>(WahlbeteiligungErfassungsstatus.class);

    WahlbeteiligungAuswertung wahlbeteiligungAuswertung = wahlbeteiligungAuswertungDAO.findByPrimaryKey(wahlbeteiligungAuswertungPk);

    Wahldurchfuehrungsgebiet selectedGebiet = wahldurchfuehrungsgebietSessionSelect.getEntity();

    List<Wahldurchfuehrungsgebiet> erfassungsgebiete = selectedGebiet.getErfassungsgebieteUnterhalb().stream().filter(Wahldurchfuehrungsgebiet::isWahlbeteiligungErfassungsgebiet)
        .collect(Collectors.toList());

    EinsehenGebietSecurity einsehenGebietSecurity = gebietSecurity.getEinsehenGebietSecurity();
    List<Wahldurchfuehrungsgebiet> erfassteGebiete = wahldurchfuehrungsgebietDAO.findByWahlbeteiligungErfasst(wahlbeteiligungAuswertung) //
        .stream() //
        .filter(gebiet -> isGebietRelevant(selectedGebiet, gebiet, einsehenGebietSecurity)) //
        .collect(Collectors.toList());

    List<Wahldurchfuehrungsgebiet> nichtErfassteGebiete = erfassungsgebiete //
        .stream() //
        .filter(gebiet -> isGebietRelevant(selectedGebiet, gebiet, einsehenGebietSecurity)) //
        .filter(gebiet -> !erfassteGebiete.contains(gebiet)) //
        .collect(Collectors.toList());

    fuegeStatusEntriesHinzuWennNichtLeer(result, ERFASST, erfassteGebiete);
    fuegeStatusEntriesHinzuWennNichtLeer(result, NICHT_ERFASST, nichtErfassteGebiete);

    statusEntriesProWahlbeteiligungAuswertungUndStatus.put(wahlbeteiligungAuswertungPk, result);

    return result;
  }

  public static boolean isGebietRelevant(Wahldurchfuehrungsgebiet selectedGebiet, Wahldurchfuehrungsgebiet erfassungsGebiet, EinsehenGebietSecurity einsehenGebietSecurity) {
    return erfassungsGebiet.isUntergebietVon(selectedGebiet) && /* ELECTBB-410 */ einsehenGebietSecurity.isRechtVorhanden(erfassungsGebiet);
  }

  private void fuegeStatusEntriesHinzuWennNichtLeer(Map<WahlbeteiligungErfassungsstatus, List<StatusEntry>> statusEntriesProStatus, WahlbeteiligungErfassungsstatus status,
      List<Wahldurchfuehrungsgebiet> erfassungsgebiete) {

    List<StatusEntry> statusEntries = new ArrayList<>();

    Map<Gebiet, List<Gebiet>> erfassungsgebietePerUebergeordnetesGebiet = erfassungsgebiete.stream().collect(Collectors.groupingBy(Gebiet::getUebergeordnetesGebiet));

    List<Gebiet> uebergeordneteGebiete = erfassungsgebietePerUebergeordnetesGebiet.keySet().stream().sorted(new GebietByGebietsartAndPositionComparator()).collect(Collectors.toList());

    for (Gebiet uebergeordnetesGebiet : uebergeordneteGebiete) {
      List<Gebiet> gebiete = erfassungsgebietePerUebergeordnetesGebiet.get(uebergeordnetesGebiet).stream().sorted(new GebietByGebietsartAndPositionComparator()).collect(Collectors.toList());

      statusEntries.addAll(gebiete.stream().map(gebiet -> wahlbeteiligungAuswertungStatusEntryFactory.create(gebiet, status)).collect(Collectors.toList()));

      if (uebergeordneteGebiete.indexOf(uebergeordnetesGebiet) != uebergeordneteGebiete.size() - 1) {
        statusEntries.add(new WahlbeteiligungSeparatorStatusEntry());
      }
    }

    if (!statusEntries.isEmpty()) {
      statusEntriesProStatus.put(status, statusEntries);
    }
  }
}
