
package de.ivu.elect.presentation.stimmeingabe.status;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

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

import org.primefaces.model.chart.PieChartModel;

import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietConstants;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangExtendedDAO;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.presentation.charts.PieChartModelBuilder;
import de.ivu.elect.presentation.security.EinsehenGebietSecurity;
import de.ivu.elect.presentation.security.GebietSecurity;
import de.ivu.elect.presentation.stimmeingabe.StimmeingabeSecurity;


import static java.util.Arrays.asList;

@RequestScoped
public class ErgebniseingangErfassungsstatusPieChartModelBuilder implements Serializable {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient Messages messages;

  @Inject
  private transient GebietSecurity gebietSecurity;

  @Inject
  private transient StimmeingabeSecurity stimmeingabeSecurity;

  @Inject
  private transient WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietDao;

  @Inject
  private transient ErgebniseingangExtendedDAO ergebniseingangDao;

  @Inject
  private transient ErgebniseingangKonfiguration ergebniseingangKonfiguration;

  @Inject
  private transient ErgebniseingangAufVollstaendigkeitPruefenSchalter ergebniseingangAufVollstaendigkeitPruefenSchalter;

  private transient Map<Gebiet, Map<GebietErfassungsstatusInErgebniseingabestufe, Long>> anzahlErfassungsgebieteInLetztenErgebniseingaengenAktuellCache;

  private transient Map<Gebiet, Map<GebietErfassungsstatusInErgebniseingabestufe, Long>> anzahlErfassungsgebieteInLetztenErgebniseingaengenProErgebniseingabestufeCache;

  public ErgebniseingangErfassungsstatusPieChartModelBuilder() {
    super();

    this.anzahlErfassungsgebieteInLetztenErgebniseingaengenAktuellCache = new HashMap<>();
    this.anzahlErfassungsgebieteInLetztenErgebniseingaengenProErgebniseingabestufeCache = new HashMap<>();
  }

  public List<Ergebniseingabestufe> collectErgebniseingabestufen(Wahl wahl) {
    return ergebniseingangKonfiguration.collectErgebniseingabestufen(wahl);
  }

  public PieChartModel createPieChartModel(Wahl wahl, Gebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe) {
    PieChartModelBuilder pieChartModelBuilder = PieChartModelBuilder.getDefaultInstance();

    Map<GebietErfassungsstatus, Long> anzahlErfassungsgebietePerGebietErfassungsstatus = new HashMap<>();

    for (Entry<GebietErfassungsstatusInErgebniseingabestufe, Long> entry : countAnzahlErfassungsgebieteInLetztenErgebniseingaengenProErgebniseingabestufe(wahl, uebergeordnetesGebiet,
        ergebniseingangsart).entrySet()) {
      GebietErfassungsstatusInErgebniseingabestufe gebietErfassungsstatusInErgebniseingabestufe = entry.getKey();

      if (gebietErfassungsstatusInErgebniseingabestufe.getErgebniseingabestufe().equals(ergebniseingabestufe)) {
        GebietErfassungsstatus gebietErfassungsstatus = gebietErfassungsstatusInErgebniseingabestufe.getGebietErfassungsstatus();
        long anzahlErfassungsgebiete = anzahlErfassungsgebietePerGebietErfassungsstatus.getOrDefault(gebietErfassungsstatus, 0L) + entry.getValue();

        anzahlErfassungsgebietePerGebietErfassungsstatus.put(gebietErfassungsstatus, anzahlErfassungsgebiete);
      }
    }

    List<GebietErfassungsstatus> gebietErfassungsstatusSorted = new ArrayList<>(anzahlErfassungsgebietePerGebietErfassungsstatus.keySet());
    gebietErfassungsstatusSorted.sort(GebietErfassungsstatus.DEFAULT_COMPARATOR);

    for (GebietErfassungsstatus gebietErfassungsstatus : gebietErfassungsstatusSorted) {
      Long anzahlErfassungsgebiete = anzahlErfassungsgebietePerGebietErfassungsstatus.get(gebietErfassungsstatus);

      pieChartModelBuilder.add(getPieChartLabel(gebietErfassungsstatus, anzahlErfassungsgebiete), anzahlErfassungsgebiete, getPieChartColor(gebietErfassungsstatus));
    }

    return pieChartModelBuilder.buildModel();
  }

  private String getPieChartLabel(GebietErfassungsstatus gebietErfassungsstatus, Long anzahlErfassungsgebiete) {
    return messages.getMessageForMessageKey("wahl.statusuebersicht.erfassungsstatus.mit.anzahl.gebiete.label", messages.lokalisiere(gebietErfassungsstatus), anzahlErfassungsgebiete,
        messages.getMessageForMessageKey(anzahlErfassungsgebiete == 1 ? GebietConstants.MESSAGE_KEY_ENTITY_EINZAHL : GebietConstants.MESSAGE_KEY_ENTITY_MEHRZAHL));
  }

  private String getPieChartColor(GebietErfassungsstatus gebietErfassungsstatus) {
    return gebietErfassungsstatus.getPieChartColor();
  }

  /**
   * @return Die Anzahl Erfassungsgebiete für VORHANDENE GebietErfassungsstatus aus den letzten Ergebniseingängen AKTUELL.
   */
  public Map<GebietErfassungsstatusInErgebniseingabestufe, Long> countAnzahlErfassungsgebieteInLetztenErgebniseingaengenAktuell(Wahl wahl, Gebiet uebergeordnetesGebiet,
      Ergebniseingangsart ergebniseingangsart) {
    Ergebniseingabestufe ergebniseingabestufeInitial = ergebniseingangKonfiguration.getErgebniseingabestufeInitial(wahl);

    if (uebergeordnetesGebiet == null) {
      Map<GebietErfassungsstatusInErgebniseingabestufe, Long> result = new HashMap<>();

      for (GebietErfassungsstatus gebietErfassungsstatus : collectGebietErfassungsstatusAnzeigbar()) {
        result.put(new GebietErfassungsstatusInErgebniseingabestufe(ergebniseingabestufeInitial, gebietErfassungsstatus), 0L);
      }

      return result;
    }

    return anzahlErfassungsgebieteInLetztenErgebniseingaengenAktuellCache.computeIfAbsent(uebergeordnetesGebiet, x -> {
      Map<GebietErfassungsstatusInErgebniseingabestufe, Long> result = new HashMap<>();

      Map<Wahldurchfuehrungsgebiet, Ergebniseingang> letzterErgebniseingangAktuellPerErfassungsgebiet = collectLetzterErgebniseingangAktuellPerErfassungsgebiet(uebergeordnetesGebiet,
          ergebniseingangsart);

      getUntergeordneteErfassungsgebiete(uebergeordnetesGebiet).forEach(erfassungsgebiet -> {
        Ergebniseingabestufe ergebniseingabestufe = ergebniseingabestufeInitial;

        Ergebniseingang ergebniseingang = letzterErgebniseingangAktuellPerErfassungsgebiet.get(erfassungsgebiet);

        if (ergebniseingang != null) {
          ergebniseingabestufe = ergebniseingang.getErgebniseingabestufe();
        }

        updateAnzahlErfassungsgebieteInLetztenErgebniseingaengenByGebietErfassungsstatusAnzeigbar(result, letzterErgebniseingangAktuellPerErfassungsgebiet,
            erfassungsgebiet, ergebniseingabestufe, ergebniseingangsart);
      });

      return result;
    });
  }

  /**
   * @return Die Anzahl Erfassungsgebiete für ALLE GebietErfassungsstatus aus den letzten Ergebniseingängen PRO ERGEBNISEINGABESTUFE.
   */
  private Map<GebietErfassungsstatusInErgebniseingabestufe, Long> countAnzahlErfassungsgebieteInLetztenErgebniseingaengenProErgebniseingabestufe(Wahl wahl, Gebiet uebergeordnetesGebiet,
      Ergebniseingangsart ergebniseingangsart) {
    List<Ergebniseingabestufe> ergebniseingabestufen = collectErgebniseingabestufen(wahl);

    if (uebergeordnetesGebiet == null) {
      Map<GebietErfassungsstatusInErgebniseingabestufe, Long> result = new HashMap<>();

      for (Ergebniseingabestufe ergebniseingabestufe : ergebniseingabestufen) {
        for (GebietErfassungsstatus gebietErfassungsstatus : collectGebietErfassungsstatusAnzeigbar()) {
          result.put(new GebietErfassungsstatusInErgebniseingabestufe(ergebniseingabestufe, gebietErfassungsstatus), 0L);
        }
      }

      return result;
    }

    return anzahlErfassungsgebieteInLetztenErgebniseingaengenProErgebniseingabestufeCache.computeIfAbsent(uebergeordnetesGebiet, x -> {
      Map<GebietErfassungsstatusInErgebniseingabestufe, Long> result = new HashMap<>();

      for (Ergebniseingabestufe ergebniseingabestufe : ergebniseingabestufen) {
        Map<Wahldurchfuehrungsgebiet, Ergebniseingang> letzterErgebniseingangInErgebniseingabestufePerErfassungsgebiet = collectLetzterErgebniseingangInErgebniseingabestufePerErfassungsgebiet(
            uebergeordnetesGebiet, ergebniseingangsart, ergebniseingabestufe);

        for (GebietErfassungsstatus gebietErfassungsstatus : collectGebietErfassungsstatusAnzeigbar()) {
          result.put(new GebietErfassungsstatusInErgebniseingabestufe(ergebniseingabestufe, gebietErfassungsstatus), 0L);
        }

        getUntergeordneteErfassungsgebiete(uebergeordnetesGebiet).forEach(
            erfassungsgebiet -> updateAnzahlErfassungsgebieteInLetztenErgebniseingaengenByGebietErfassungsstatusAnzeigbar(result,
                letzterErgebniseingangInErgebniseingabestufePerErfassungsgebiet, erfassungsgebiet, ergebniseingabestufe, ergebniseingangsart));
      }

      return result;
    });
  }

  private List<GebietErfassungsstatus> collectGebietErfassungsstatusAnzeigbar() {
    List<GebietErfassungsstatus> result = new ArrayList<>(asList(GebietErfassungsstatus.values()));

    if (isGebietErfassungsstatusFiktivAlsGueltigAnzeigen()) {
      result.remove(GebietErfassungsstatus.FIKTIV); // ELECT-9794
    }

    return result;
  }

  private void updateAnzahlErfassungsgebieteInLetztenErgebniseingaengenByGebietErfassungsstatusAnzeigbar(Map<GebietErfassungsstatusInErgebniseingabestufe, Long> result,
                                                                                                         Map<Wahldurchfuehrungsgebiet, Ergebniseingang> letzterErgebniseingangPerErfassungsgebiet, Wahldurchfuehrungsgebiet erfassungsgebiet,
                                                                                                         Ergebniseingabestufe ergebniseingabestufe, Ergebniseingangsart ergebniseingangsart) {
    GebietErfassungsstatus gebietErfassungsstatus = GebietErfassungsstatus.fromErgebniseingang(letzterErgebniseingangPerErfassungsgebiet.get(erfassungsgebiet),
        isGebietErfassungsstatusUnvollstaendigAnzeigen(), erfassungsgebiet, ergebniseingangsart);

    if (GebietErfassungsstatus.FIKTIV.equals(gebietErfassungsstatus) && isGebietErfassungsstatusFiktivAlsGueltigAnzeigen()) {
      gebietErfassungsstatus = GebietErfassungsstatus.GUELTIG;
    }

    GebietErfassungsstatusInErgebniseingabestufe gebietErfassungsstatusInErgebniseingabestufe = new GebietErfassungsstatusInErgebniseingabestufe(
        ergebniseingabestufe, gebietErfassungsstatus);

    result.put(gebietErfassungsstatusInErgebniseingabestufe, result.getOrDefault(gebietErfassungsstatusInErgebniseingabestufe, 0L) + 1);
  }

  public boolean isGebietErfassungsstatusUnvollstaendigAnzeigen() {
    return ergebniseingangAufVollstaendigkeitPruefenSchalter.isErgebniseingangAufVollstaendigkeitPruefen();
  }

  public boolean isGebietErfassungsstatusFiktivAlsGueltigAnzeigen() {
    return !stimmeingabeSecurity.hasPermissionLetzterErgebniseingangFiktivErkennen();
  }

  private List<Wahldurchfuehrungsgebiet> getUntergeordneteErfassungsgebiete(Gebiet uebergeordnetesGebiet) {
    return getErgebniseingangPerErfassungsgebietCollector().findUntergeordneteErfassungsgebiete(getEinsehenGebietSecurity(), uebergeordnetesGebiet);
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> collectLetzterErgebniseingangAktuellPerErfassungsgebiet(Gebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart) {
    return getErgebniseingangPerErfassungsgebietCollector().findLetzterErgebniseingangAktuellPerUntergeordnetesErfassungsgebiet(getEinsehenGebietSecurity(), uebergeordnetesGebiet, ergebniseingangsart,
        false);
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> collectLetzterErgebniseingangInErgebniseingabestufePerErfassungsgebiet(Gebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart,
      Ergebniseingabestufe ergebniseingabestufe) {
    return getErgebniseingangPerErfassungsgebietCollector().findLetzterErgebniseingangInErgebniseingabestufePerUntergeordnetesErfassungsgebiet(getEinsehenGebietSecurity(), uebergeordnetesGebiet,
        ergebniseingangsart, ergebniseingabestufe, false);
  }

  private ErgebniseingangPerErfassungsgebietCollector getErgebniseingangPerErfassungsgebietCollector() {
    return new ErgebniseingangPerErfassungsgebietCollector(wahldurchfuehrungsgebietDao, ergebniseingangDao);
  }

  private EinsehenGebietSecurity getEinsehenGebietSecurity() {
    return gebietSecurity.getEinsehenGebietSecurity();
  }
}
