
package de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis;

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

import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentRepeat;
import de.ivu.document.generator.template.annotations.DocumentSimpleField;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.dokumente.CoreElectContext;
import de.ivu.elect.dokumente.kandidatur.KandidaturCoreDaten;
import de.ivu.elect.dokumente.wahlvorschlag.CoreWahlvorschlagsdatenDocument;
import de.ivu.elect.jee.faces.UserLocale;

// Cf. ListeStimmenDocument, GebietMitListenDocument, GebietMitDirektkandidatenDocument!!!
public class ListeMitStimmergebnissenDocument extends CoreWahlvorschlagsdatenDocument {

  private static final long serialVersionUID = 1L;

  private static final Comparator<KandidaturMitStimmergebnissenDocument> NACH_RANG_COMPARATOR = new KandidaturDocumentNachRangComparator();

  @DocumentRepeat(value = "Listenkandidaturen", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "listenkandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> listenkandidaturen;

  @DocumentRepeat(value = "ListenkandidaturenSortiertNachRangOhneErsatz", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "listenkandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> listenkandidaturenSortiertNachRang;

  @DocumentRepeat(value = "ListenkandidaturenGewaehlt", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "listenkandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> listenkandidaturenGewaehlt;

  @DocumentComplexField("ErgebnisGewähltMarkiert")
  public final ErgebnisGewaehltMarkiertDocument ergebnisGewaehltMarkiert; // ELECT-9429

  @DocumentSimpleField("AnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert")
  public final Long anzahlStimmzettelWahlvorschlagUnveraendert;

  @DocumentSimpleField("AnzahlStimmzettelNurEinzelnerWahlvorschlagVeraendert")
  public final Long anzahlStimmzettelWahlvorschlagVeraendert;

  @DocumentSimpleField("AnzahlStimmzettelNurEinzelnerWahlvorschlagGesamt")
  public final Long anzahlStimmzettelWahlvorschlagGesamt;

  @DocumentSimpleField("AnzahlSitze")
  public final Long anzahlSitze;

  @DocumentSimpleField("AnzahlSitzeAnspruch")
  public final Long anzahlSitzeAnspruch;

  @DocumentSimpleField("AnzahlStimmenGueltig")
  public final Long anzahlGueltigerStimmen;

  @DocumentSimpleField("AnzahlStimmenGueltigFormatiert")
  public final String anzahlGueltigerStimmenFormatiert;

  @DocumentSimpleField("AnzahlStimmenGueltigNurListe")
  public final Long anzahlGueltigerStimmenNurListe;

  @DocumentSimpleField("AnzahlStimmenGueltigNurListeFormatiert")
  public final String anzahlGueltigerStimmenNurListeFormatiert;

  @DocumentSimpleField("AnzahlStimmenGueltigNurListenkandidaturen")
  public final Long anzahlGueltigerStimmenNurListenkandidaturen;

  @DocumentSimpleField("AnzahlStimmenGueltigNurListenkandidaturenFormatiert")
  public final String anzahlGueltigerStimmenNurListenkandidaturenFormatiert;

  /**
   * @deprecated
   * @see ListeMitStimmergebnissenDocument#ergebnisGewaehltMarkiert#anzahlGueltigerStimmenNurListenkandidaturen
   */
  @Deprecated
  @DocumentSimpleField("AnzahlListenkandidaturStimmenGesamt")
  public final Long anzahlListenkandidaturStimmenGesamt;

  /**
   * @deprecated
   * @see ListeMitStimmergebnissenDocument#ergebnisGewaehltMarkiert#anzahlKandidaturenIstGewaehltGesamt
   */
  @Deprecated
  @DocumentSimpleField("AnzahlGewählte")
  public final Long anzahlGewaehlte;

  /**
   * @deprecated
   * @see ListeMitStimmergebnissenDocument#ergebnisGewaehltMarkiert#anzahlKandidaturenIstGewaehltGesamt
   */
  @Deprecated
  @DocumentSimpleField("GewählteVorhanden")
  public final boolean gewaehlteVorhanden;

  /**
   * @deprecated Anzahl entspricht NICHT der Anzahl laut GewaehltStatus#NICHT_GEWAEHLT!!!
   */
  @Deprecated
  @DocumentSimpleField("AnzahlNichtGewählte")
  public final Long anzahlNichtGewaehlte;

  /**
   * @deprecated Anzahl entspricht NICHT der Anzahl laut GewaehltStatus#NICHT_GEWAEHLT!!!
   */
  @Deprecated
  @DocumentSimpleField("NichtGewählteVorhanden")
  public final boolean nichtGewaehlteVorhanden;

  @DocumentSimpleField("LetzterGewählterRang")
  public final Long letzterGewaehlterRang;

  @DocumentSimpleField("ErsterNichtGewaehlterRang")
  public final Long ersterNichtGewaehlterRang;

  @DocumentSimpleField("AnzahlZugelassenerKandidaten")
  public final Long anzahlZugelassenerKandidaten;

  @DocumentSimpleField("IstInListenverbindung")
  public final boolean inListenverbindung;

  public ListeMitStimmergebnissenDocument(CoreElectContext coreElectContext, ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart, Liste liste, Long laufendeNummer,
      KandidaturCoreDaten kandidaturCoreDaten, Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    super(coreElectContext, ergebnisDaten.getEntityAufStimmzettelDaten(), gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet(), liste, laufendeNummer);

    this.listenkandidaturen = bestimmeListenkandidaturen(coreElectContext, ergebnisDaten, gebietUndStimmabgabeart, liste, kandidaturCoreDaten, ergebnisDokumentPerKandidatur, false);

    this.listenkandidaturenSortiertNachRang = new ArrayList<>(filterOhneErsatzKandidatur(this.listenkandidaturen));
    this.listenkandidaturenSortiertNachRang.sort(NACH_RANG_COMPARATOR);

    this.listenkandidaturenGewaehlt = bestimmeListenkandidaturen(coreElectContext, ergebnisDaten, gebietUndStimmabgabeart, liste, kandidaturCoreDaten, ergebnisDokumentPerKandidatur, true);

    this.ergebnisGewaehltMarkiert = new ErgebnisGewaehltMarkiertDocument(coreElectContext, this.listenkandidaturenSortiertNachRang);
    this.anzahlGewaehlte = this.ergebnisGewaehltMarkiert.anzahlKandidaturenIstGewaehltGesamt;
    this.gewaehlteVorhanden = this.ergebnisGewaehltMarkiert.anzahlKandidaturenIstGewaehltGesamt > 0L;

    this.anzahlStimmzettelWahlvorschlagUnveraendert = ergebnisDaten.getStimmzettelNurEinzelnerWahlvorschlagUnveraendert(liste, gebietUndStimmabgabeart);
    this.anzahlStimmzettelWahlvorschlagVeraendert = ergebnisDaten.getStimmzettelNurEinzelnerWahlvorschlagVeraendert(liste, gebietUndStimmabgabeart);
    this.anzahlStimmzettelWahlvorschlagGesamt = sumNullToZero(this.anzahlStimmzettelWahlvorschlagUnveraendert, this.anzahlStimmzettelWahlvorschlagVeraendert);

    this.anzahlGueltigerStimmen = ergebnisDaten.getZweitstimmenAktuell(liste.getGruppe(), gebietUndStimmabgabeart);
    this.anzahlGueltigerStimmenFormatiert = UserLocale.formatNumber(this.anzahlGueltigerStimmen, coreElectContext.getLocale());
    this.anzahlGueltigerStimmenNurListe = ergebnisDaten.getStimmenNurListe(liste, gebietUndStimmabgabeart);
    this.anzahlGueltigerStimmenNurListeFormatiert = UserLocale.formatNumber(this.anzahlGueltigerStimmenNurListe, coreElectContext.getLocale());
    this.anzahlGueltigerStimmenNurListenkandidaturen = ergebnisDaten.getStimmenNurListenkandidaturen(liste, gebietUndStimmabgabeart);
    this.anzahlGueltigerStimmenNurListenkandidaturenFormatiert = UserLocale.formatNumber(this.anzahlGueltigerStimmenNurListenkandidaturen, coreElectContext.getLocale());
    this.anzahlListenkandidaturStimmenGesamt = this.anzahlGueltigerStimmenNurListenkandidaturen;

    this.inListenverbindung = liste.getListenverbindung() != null;

    Sitze sitze = ergebnisDaten.getSitzeFuerListe(liste);

    if (sitze == null) {
      this.anzahlSitze = 0L;
      this.anzahlSitzeAnspruch = 0L;
    } else {
      this.anzahlSitze = sitze.getAnzahl();
      this.anzahlSitzeAnspruch = sitze.getAnzahlAnspruch();
    }

    long anzahlListenkandidaturenZugelassen = 0L;

    // Schalter listenkandidatur#gewaehlt == false impliziert NICHT GewaehltStatus#NICHT_GEWAEHLT!!!
    long anzahlListenkandidaturenGewaehltEqualsFalse = 0L;
    Long rangMaxListenkandidaturenGewaehlt = null;
    Long rangMinListenkandidaturenGewaehltEqualsFalse = null;

    for (KandidaturMitStimmergebnissenDocument listenkandidatur : listenkandidaturenSortiertNachRang) {
      if (listenkandidatur.zugelassen) {
        anzahlListenkandidaturenZugelassen++;

        if (listenkandidatur.gewaehlt) {
          rangMaxListenkandidaturenGewaehlt = listenkandidatur.rang;
        } else {
          anzahlListenkandidaturenGewaehltEqualsFalse++;

          if (rangMinListenkandidaturenGewaehltEqualsFalse == null) {
            rangMinListenkandidaturenGewaehltEqualsFalse = listenkandidatur.rang;
          }
        }
      }
    }

    this.anzahlNichtGewaehlte = anzahlListenkandidaturenGewaehltEqualsFalse;
    this.nichtGewaehlteVorhanden = this.anzahlNichtGewaehlte > 0;

    this.anzahlZugelassenerKandidaten = anzahlListenkandidaturenZugelassen;

    this.letzterGewaehlterRang = rangMaxListenkandidaturenGewaehlt;
    this.ersterNichtGewaehlterRang = rangMinListenkandidaturenGewaehltEqualsFalse;
  }

  private List<KandidaturMitStimmergebnissenDocument> filterOhneErsatzKandidatur(List<KandidaturMitStimmergebnissenDocument> listenkandidaturen) {
    return listenkandidaturen.stream().filter(kandidaturMitStimmergebnissenDocument -> !kandidaturMitStimmergebnissenDocument.ersatzkandidatur)
        .collect(Collectors.toCollection(() -> new ArrayList<>(listenkandidaturen.size())));
  }

  private List<KandidaturMitStimmergebnissenDocument> bestimmeListenkandidaturen(CoreElectContext coreElectContext, ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart,
      Liste liste, KandidaturCoreDaten kandidaturCoreDaten, Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur, boolean nurGewaehlte) {
    List<KandidaturMitStimmergebnissenDocument> result = new ArrayList<>();

    long laufendeNummer = 0;

    for (Listenkandidatur listenkandidatur : liste.getListenkandidaturenSortiertNachRelativerPosition()) {
      if (!nurGewaehlte || isGewaehlt(listenkandidatur, ergebnisDaten)) {
        laufendeNummer++;

        KandidaturMitStimmergebnissenDocument dokument = new KandidaturMitStimmergebnissenDocument(coreElectContext, ergebnisDaten, gebietUndStimmabgabeart, this, kandidaturCoreDaten,
            listenkandidatur, laufendeNummer);

        ergebnisDokumentPerKandidatur.put(listenkandidatur, dokument);

        result.add(dokument);
      }
    }

    return result;
  }

  private boolean isGewaehlt(Listenkandidatur listenkandidatur, ErgebnisDaten ergebnisDaten) {
    Gewaehlt gewaehlteFuerKandidatur = ergebnisDaten.getGewaehlteFuerKandidatur(listenkandidatur);

    return gewaehlteFuerKandidatur != null && gewaehlteFuerKandidatur.getIstGewaehlt();
  }
}
