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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.ToLongFunction;

import javax.annotation.Nullable;

import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentMethod;
import de.ivu.document.generator.template.annotations.DocumentParam;
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.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietDescriptor;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.niederschrift.boundary.NiederschriftFormatter;
import de.ivu.elect.business.niederschrift.entity.Niederschrift;
import de.ivu.elect.business.niederschrift.entity.NiederschriftByGebietNummerAndNiederschriftArtComparator;
import de.ivu.elect.dokumente.ElectDocument;
import de.ivu.elect.dokumente.WasElectContext;
import de.ivu.elect.dokumente.ergebnis.ErgebnisDocumentMitWahlverwaltungGebietInformation;
import de.ivu.elect.dokumente.gebiet.GebietDokument;
import de.ivu.elect.dokumente.kandidatur.KandidaturCoreDaten;
import de.ivu.elect.dokumente.wahlvorschlag.AbstractGruppeDokument;

public class GesamtErgebnisVergleichGebietDokument extends ElectDocument implements ErgebnisDocumentMitWahlverwaltungGebietInformation<GebietDokument> {

  private static final long serialVersionUID = 1L;

  private static final long ANZAHL_UNBEKANNT = GesamtErgebnisVergleichEingabeDokument.ANZAHL_UNBEKANNT;

  @DocumentComplexField("Wahlgebiet")
  public final GebietDokument wahlgebiet;

  @DocumentComplexField("Wahlkreis")
  public final GebietDokument wahlkreis;

  @DocumentComplexField("Stimmkreis")
  public final GebietDokument stimmkreis;

  @DocumentComplexField("Wahlbereich")
  public final GebietDokument wahlbereich;

  @DocumentComplexField("Bundesland")
  public final GebietDokument bundesland;

  @DocumentComplexField("Landkreis")
  public final GebietDokument landkreis;

  @DocumentComplexField("Gemeindeverband")
  public final GebietDokument gemeindeverband;

  @DocumentComplexField("Gemeinde")
  public final GebietDokument gemeinde;

  @DocumentComplexField("Ortsbezirk")
  public final GebietDokument ortsbezirk; // ELECT-13692

  @DocumentComplexField("Stadtbezirk")
  public final GebietDokument stadtbezirk;

  @DocumentComplexField("Gebiet")
  public final GebietDokument gebiet;

  @DocumentSimpleField("Gebiet-Ergebnis-Korrektur-Ist-Vorhanden")
  public final boolean gebietErgebnisKorrekturIstVorhanden;

  @DocumentComplexField("Gebiet-Ergebnis-Vorlaeufig")
  public final GebietMitDetails gebietErgebnisVorlaeufig;

  @DocumentSimpleField("Gebiet-Ergebnis-Vorlaeufig-Ist-Vorhanden")
  public final boolean gebietErgebnisVorlaeufigIstVorhanden;

  @DocumentComplexField("Gebiet-Ergebnis-Endgueltig")
  public final GebietMitDetails gebietErgebnisEndgueltig;

  @DocumentSimpleField("Gebiet-Ergebnis-Endgueltig-Ist-Vorhanden")
  public final boolean gebietErgebnisEndgueltigIstVorhanden;

  @DocumentSimpleField("Gebiet-Ergebnis-Nur-Vorlaeufig-Ist-Vorhanden")
  public final boolean gebietErgebnisNurVorlaeufigIstVorhanden;

  @DocumentSimpleField("Gebiet-Ergebnis-Nur-Endgueltig-Ist-Vorhanden")
  public final boolean gebietErgebnisNurEndgueltigIstVorhanden;

  private final GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt;

  @DocumentSimpleField("Vergleiche-Wahlberechtigte-Anzahl-Differenz-Ist-Vorhanden")
  public final boolean vergleicheWahlberechtigteAnzahlDifferenzIstVorhanden;

  @DocumentComplexField("Vergleich-Wahlberechtigte-Anzahl-Ohne-Wahlschein")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWahlberechtigteAnzahlOhneWahlschein;

  @DocumentComplexField("Vergleich-Wahlberechtigte-Anzahl-Mit-Wahlschein")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWahlberechtigteAnzahlMitWahlschein;

  @DocumentComplexField("Vergleich-Wahlberechtigte-Anzahl-Nicht-Im-Waehlerverzeichnis")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWahlberechtigteAnzahlNichtImWaehlerverzeichnis;

  @DocumentComplexField("Vergleich-Wahlberechtigte-Anzahl-Gesamt")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWahlberechtigteAnzahlGesamt;

  @DocumentSimpleField("Vergleiche-Waehler-Anzahl-Differenz-Ist-Vorhanden")
  public final boolean vergleicheWaehlerAnzahlDifferenzIstVorhanden;

  @DocumentComplexField("Vergleich-Waehler-Anzahl-Ohne-Wahlschein")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWaehlerAnzahlOhneWahlschein;

  @DocumentComplexField("Vergleich-Waehler-Anzahl-Mit-Wahlschein")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWaehlerAnzahlMitWahlschein;

  @DocumentComplexField("Vergleich-Waehler-Anzahl-Mit-Wahlschein-Anzahl-Aus-Urnenwahl")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWaehlerAnzahlMitWahlscheinAnzahlAusUrnenwahl;

  @DocumentComplexField("Vergleich-Waehler-Anzahl-Mit-Wahlschein-Anzahl-Aus-Briefwahl")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWaehlerAnzahlMitWahlscheinAnzahlAusBriefwahl;

  @DocumentComplexField("Vergleich-Waehler-Anzahl-Mit-Vollmacht")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWaehlerAnzahlMitVollmacht;

  @DocumentComplexField("Vergleich-Waehler-Anzahl-Gesamt")
  public final GesamtErgebnisVergleichEingabeDokument vergleichWaehlerAnzahlGesamt;

  @DocumentSimpleField("Vergleiche-Gueltige-StimmenDifferenz-Ist-Vorhanden")
  public final boolean vergleicheGueltigeStimmenDifferenzIstVorhanden;

  @DocumentComplexField("Vergleich-Gueltige-Direktstimmen")
  public final GesamtErgebnisVergleichEingabeDokument vergleichGueltigeDirektstimmen;

  @DocumentComplexField("Vergleich-Gueltige-Listenstimmen")
  public final GesamtErgebnisVergleichEingabeDokument vergleichGueltigeListenstimmen;

  @DocumentSimpleField("Vergleiche-Ungueltige-Stimmen-Differenz-Ist-Vorhanden")
  public final boolean vergleicheUngueltigeStimmenDifferenzIstVorhanden;

  @DocumentComplexField("Vergleich-Ungueltige-Direktstimmen")
  public final GesamtErgebnisVergleichEingabeDokument vergleichUngueltigeDirektstimmen;

  @DocumentComplexField("Vergleich-Ungueltige-Listenstimmen")
  public final GesamtErgebnisVergleichEingabeDokument vergleichUngueltigeListenstimmen;

  @DocumentSimpleField("Vergleiche-Politische-Gruppe-Direktstimmen-Differenz-Ist-Vorhanden")
  public final boolean vergleichePolitischeGruppeDirektstimmenDifferenzIstVorhanden;

  @DocumentSimpleField("Vergleiche-Politische-Gruppe-Listenstimmen-Differenz-Ist-Vorhanden")
  public final boolean vergleichePolitischeGruppeListenstimmenDifferenzIstVorhanden;

  @DocumentSimpleField("Vergleiche-Politische-Gruppe-Stimmen-Differenz-Ist-Vorhanden")
  public final boolean vergleichePolitischeGruppeStimmenDifferenzIstVorhanden;

  @DocumentRepeat(value = "Politische-Gruppen", elementClass = PolitischeGruppenErgebnisAufGebiet.class, itemName = "politischeGruppe")
  public final List<PolitischeGruppenErgebnisAufGebiet> politischeGruppen;

  public final Map<Long, GesamtErgebnisVergleichEingabeDokument> vergleichPolitischeGruppeDirektstimmenPerGruppeId;

  public final Map<Long, GesamtErgebnisVergleichEingabeDokument> vergleichPolitischeGruppeListenstimmenPerGruppeId;

  @DocumentRepeat(value = "Niederschrift-Uebersichten", elementClass = NiederschriftUebersichtDocument.class, itemName = "niederschriftUebersicht")
  public final List<NiederschriftUebersichtDocument> niederschriftUebersichten;

  public GesamtErgebnisVergleichGebietDokument(WasElectContext wasElectContext, WahlverwaltungGebietDescriptor wahlverwaltungGebietDescriptor, Stimmabgabeart stimmabgabeart,
      ErgebnisDaten ergebnisDatenAktuell, @Nullable GebietMitStimmenDocument wahlgebietUebergeordnetAktuell,
      Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidaturAktuell,

      @Nullable ErgebnisDaten ergebnisDatenVergleich, @Nullable GebietMitStimmenDocument wahlgebietUebergeordnetVergleich,
      Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidaturVergleich,

      KandidaturCoreDaten kandidaturCoreDaten, NiederschriftFormatter niederschriftFormatter, Collection<Niederschrift> niederschriften) {
    super(wasElectContext);

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = castTo(wahlverwaltungGebietDescriptor.getGebiet(), Wahldurchfuehrungsgebiet.class);
    GebietUndStimmabgabeart gebietUndStimmabgabeart = new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart);

    GebietMitDetails gebietErgebnisAktuell = createGebietErgebnisDokument(wasElectContext, gebietUndStimmabgabeart, ergebnisDatenAktuell, wahlgebietUebergeordnetAktuell,
        ergebnisDokumentPerKandidaturAktuell, kandidaturCoreDaten);

    GebietMitDetails gebietErgebnisVergleich = createGebietErgebnisDokument(wasElectContext, gebietUndStimmabgabeart, ergebnisDatenVergleich, wahlgebietUebergeordnetVergleich,
        ergebnisDokumentPerKandidaturVergleich, kandidaturCoreDaten);

    this.gebiet = createGebietDokument(wasElectContext, wahldurchfuehrungsgebiet);

    if (wahlgebietUebergeordnetAktuell != null) {
      this.wahlgebiet = wahlgebietUebergeordnetAktuell;
    } else if (wahlgebietUebergeordnetVergleich != null) {
      this.wahlgebiet = wahlgebietUebergeordnetVergleich;
    } else {
      this.wahlgebiet = GebietDokument.EMPTY_OBJECT;
    }

    this.wahlkreis = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getWahlkreis());
    this.stimmkreis = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getStimmkreis());
    this.wahlbereich = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getWahlbereich());

    this.bundesland = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getBundesland());
    this.landkreis = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getKreis());
    this.gemeindeverband = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getGemeindeverband());
    this.gemeinde = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getGemeinde());
    this.ortsbezirk = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getOrtsbezirk());
    this.stadtbezirk = createGebietDokument(wasElectContext, wahlverwaltungGebietDescriptor.getStadtbezirk());

    if (isErgebnisartVorlaeufig(ergebnisDatenAktuell)) {
      this.gebietErgebnisVorlaeufig = gebietErgebnisAktuell;
    } else if (isErgebnisartVorlaeufig(ergebnisDatenVergleich)) {
      this.gebietErgebnisVorlaeufig = gebietErgebnisVergleich;
    } else {
      this.gebietErgebnisVorlaeufig = null;
    }

    this.gebietErgebnisVorlaeufigIstVorhanden = this.gebietErgebnisVorlaeufig != null;

    if (isErgebnisartEndgueltig(ergebnisDatenAktuell)) {
      this.gebietErgebnisEndgueltig = gebietErgebnisAktuell;
    } else if (isErgebnisartEndgueltig(ergebnisDatenVergleich)) {
      this.gebietErgebnisEndgueltig = gebietErgebnisVergleich;
    } else {
      this.gebietErgebnisEndgueltig = null;
    }

    this.gebietErgebnisEndgueltigIstVorhanden = this.gebietErgebnisEndgueltig != null;

    this.gebietErgebnisNurVorlaeufigIstVorhanden = this.gebietErgebnisVorlaeufigIstVorhanden && !this.gebietErgebnisEndgueltigIstVorhanden;
    this.gebietErgebnisNurEndgueltigIstVorhanden = !this.gebietErgebnisVorlaeufigIstVorhanden && this.gebietErgebnisEndgueltigIstVorhanden;

    this.vergleichUnbekannt = new GesamtErgebnisVergleichEingabeDokument(wasElectContext, ANZAHL_UNBEKANNT, ANZAHL_UNBEKANNT);

    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> vergleicheWahlberechtigteAnzahl = collectVergleicheWahlberechtigteAnzahl(wasElectContext, this.vergleichUnbekannt,
        this.gebietErgebnisVorlaeufig, this.gebietErgebnisEndgueltig);
    this.vergleichWahlberechtigteAnzahlOhneWahlschein = vergleicheWahlberechtigteAnzahl.get(Stimmenart.OHNE_WAHLSCHEIN);
    this.vergleichWahlberechtigteAnzahlMitWahlschein = vergleicheWahlberechtigteAnzahl.get(Stimmenart.MIT_WAHLSCHEIN);
    this.vergleichWahlberechtigteAnzahlNichtImWaehlerverzeichnis = vergleicheWahlberechtigteAnzahl.get(Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
    this.vergleichWahlberechtigteAnzahlGesamt = vergleicheWahlberechtigteAnzahl.get(Stimmenart.GESAMT);

    this.vergleicheWahlberechtigteAnzahlDifferenzIstVorhanden = anyDifferenzIstVorhanden( // ELECTKOM-1621
        this.vergleichWahlberechtigteAnzahlOhneWahlschein, //
        this.vergleichWahlberechtigteAnzahlMitWahlschein, //
        this.vergleichWahlberechtigteAnzahlNichtImWaehlerverzeichnis, //
        this.vergleichWahlberechtigteAnzahlGesamt //
    );

    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> vergleicheWaehlerAnzahl = collectVergleicheWaehlerAnzahl(wasElectContext, this.vergleichUnbekannt, this.gebietErgebnisVorlaeufig,
        this.gebietErgebnisEndgueltig);
    this.vergleichWaehlerAnzahlOhneWahlschein = vergleicheWaehlerAnzahl.get(Stimmenart.OHNE_WAHLSCHEIN);
    this.vergleichWaehlerAnzahlMitWahlschein = vergleicheWaehlerAnzahl.get(Stimmenart.MIT_WAHLSCHEIN);
    this.vergleichWaehlerAnzahlMitWahlscheinAnzahlAusUrnenwahl = vergleicheWaehlerAnzahl.get(Stimmenart.ALS_URNENWAHL_IN_URNE);
    this.vergleichWaehlerAnzahlMitWahlscheinAnzahlAusBriefwahl = vergleicheWaehlerAnzahl.get(Stimmenart.ALS_BRIEFWAHL_IN_URNE);
    this.vergleichWaehlerAnzahlMitVollmacht = vergleicheWaehlerAnzahl.get(Stimmenart.MIT_VOLLMACHT);
    this.vergleichWaehlerAnzahlGesamt = vergleicheWaehlerAnzahl.get(Stimmenart.GESAMT);

    this.vergleicheWaehlerAnzahlDifferenzIstVorhanden = anyDifferenzIstVorhanden( // ELECTKOM-1621
        this.vergleichWaehlerAnzahlOhneWahlschein, //
        this.vergleichWaehlerAnzahlMitWahlschein, //
        this.vergleichWaehlerAnzahlMitWahlscheinAnzahlAusUrnenwahl, //
        this.vergleichWaehlerAnzahlMitWahlscheinAnzahlAusBriefwahl, //
        this.vergleichWaehlerAnzahlMitVollmacht, //
        this.vergleichWaehlerAnzahlGesamt //
    );

    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> vergleicheGueltigeStimmen = collectVergleicheGueltigeStimmen(wasElectContext, this.vergleichUnbekannt, this.gebietErgebnisVorlaeufig,
        this.gebietErgebnisEndgueltig);
    this.vergleichGueltigeDirektstimmen = vergleicheGueltigeStimmen.get(Stimmenart.DIREKTSTIMME);
    this.vergleichGueltigeListenstimmen = vergleicheGueltigeStimmen.get(Stimmenart.LISTENSTIMME);

    this.vergleicheGueltigeStimmenDifferenzIstVorhanden = anyDifferenzIstVorhanden( // ELECTKOM-1621
        this.vergleichGueltigeDirektstimmen, //
        this.vergleichGueltigeListenstimmen //
    );

    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> vergleicheUngueltigeStimmen = collectVergleicheUngueltigeStimmen(wasElectContext, this.vergleichUnbekannt, this.gebietErgebnisVorlaeufig,
        this.gebietErgebnisEndgueltig);
    this.vergleichUngueltigeDirektstimmen = vergleicheUngueltigeStimmen.get(Stimmenart.DIREKTSTIMME);
    this.vergleichUngueltigeListenstimmen = vergleicheUngueltigeStimmen.get(Stimmenart.LISTENSTIMME);

    this.vergleicheUngueltigeStimmenDifferenzIstVorhanden = anyDifferenzIstVorhanden( // ELECTKOM-1621
        this.vergleichUngueltigeDirektstimmen, //
        this.vergleichUngueltigeListenstimmen //
    );

    if (gebietErgebnisAktuell != null) {
      this.politischeGruppen = gebietErgebnisAktuell.politischeGruppenErgebnis;
    } else if (gebietErgebnisVergleich != null) {
      this.politischeGruppen = gebietErgebnisVergleich.politischeGruppenErgebnis;
    } else {
      this.politischeGruppen = new ArrayList<>();
    }

    this.vergleichPolitischeGruppeDirektstimmenPerGruppeId = new HashMap<>();
    this.vergleichPolitischeGruppeListenstimmenPerGruppeId = new HashMap<>();

    for (PolitischeGruppenErgebnisAufGebiet politischeGruppe : this.politischeGruppen) {
      Long gruppeId = politischeGruppe.gruppe.id;

      Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> vergleichePolitischeGruppeStimmen = collectVergleichePolitischeGruppeStimmen(wasElectContext, this.vergleichUnbekannt, gruppeId,
          this.gebietErgebnisVorlaeufig, this.gebietErgebnisEndgueltig);

      this.vergleichPolitischeGruppeDirektstimmenPerGruppeId.put(gruppeId, vergleichePolitischeGruppeStimmen.get(Stimmenart.DIREKTSTIMME));
      this.vergleichPolitischeGruppeListenstimmenPerGruppeId.put(gruppeId, vergleichePolitischeGruppeStimmen.get(Stimmenart.LISTENSTIMME));
    }

    this.vergleichePolitischeGruppeDirektstimmenDifferenzIstVorhanden = anyDifferenzIstVorhanden(this.vergleichPolitischeGruppeDirektstimmenPerGruppeId.values()); // ELECT-13080
    this.vergleichePolitischeGruppeListenstimmenDifferenzIstVorhanden = anyDifferenzIstVorhanden(this.vergleichPolitischeGruppeListenstimmenPerGruppeId.values()); // ELECT-13080

    this.vergleichePolitischeGruppeStimmenDifferenzIstVorhanden = this.vergleichePolitischeGruppeDirektstimmenDifferenzIstVorhanden
        || this.vergleichePolitischeGruppeListenstimmenDifferenzIstVorhanden; //ELECTKOM-1621

    this.niederschriftUebersichten = collectNiederschriftUebersichtDokumente(wasElectContext, niederschriftFormatter, niederschriften);

    this.gebietErgebnisKorrekturIstVorhanden = this.vergleicheWahlberechtigteAnzahlDifferenzIstVorhanden || this.vergleicheWaehlerAnzahlDifferenzIstVorhanden
        || this.vergleicheGueltigeStimmenDifferenzIstVorhanden || this.vergleicheUngueltigeStimmenDifferenzIstVorhanden || this.vergleichePolitischeGruppeStimmenDifferenzIstVorhanden; // ELECTKOM-1621
  }

  @Override
  public GebietDokument getWahlkreis() {
    return wahlkreis;
  }

  @Override
  public GebietDokument getStimmkreis() {
    return stimmkreis;
  }

  @Override
  public GebietDokument getWahlbereich() {
    return wahlbereich;
  }

  @Override
  public GebietDokument getBundesland() {
    return bundesland;
  }

  @Override
  public GebietDokument getLandkreis() {
    return landkreis;
  }

  @Override
  public GebietDokument getGemeindeverband() {
    return gemeindeverband;
  }

  @Override
  public GebietDokument getGemeinde() {
    return gemeinde;
  }

  @Override
  public GebietDokument getOrtsbezirk() {
    return ortsbezirk;
  }

  @Override
  public GebietDokument getStadtbezirk() {
    return stadtbezirk;
  }

  private boolean anyDifferenzIstVorhanden(GesamtErgebnisVergleichEingabeDokument... vergleichEingabeDokumente) {
    return vergleichEingabeDokumente != null && anyDifferenzIstVorhanden(asList(vergleichEingabeDokumente));
  }

  private boolean anyDifferenzIstVorhanden(Collection<GesamtErgebnisVergleichEingabeDokument> vergleichEingabeDokumente) {
    return vergleichEingabeDokumente.stream().anyMatch(vergleichEingabeDokument -> vergleichEingabeDokument.differenzIstVorhanden);
  }

  @DocumentMethod
  public GesamtErgebnisVergleichEingabeDokument getVergleichPolitischeGruppeDirektstimmen(@DocumentParam("politischeGruppeReference") Object reference) {
    return getVergleichPolitischeGruppeStimmen(reference, vergleichPolitischeGruppeDirektstimmenPerGruppeId);
  }

  @DocumentMethod
  public GesamtErgebnisVergleichEingabeDokument getVergleichPolitischeGruppeListenstimmen(@DocumentParam("politischeGruppeReference") Object reference) {
    return getVergleichPolitischeGruppeStimmen(reference, vergleichPolitischeGruppeListenstimmenPerGruppeId);
  }

  private GesamtErgebnisVergleichEingabeDokument getVergleichPolitischeGruppeStimmen(Object reference, Map<Long, GesamtErgebnisVergleichEingabeDokument> vergleichPolitischeGruppeStimmenPerGruppeId) {
    Long gruppeId = -1L;

    if (reference instanceof GruppenErgebnisAufGebiet) {
      gruppeId = ((GruppenErgebnisAufGebiet) reference).gruppe.id;
    } else if (reference instanceof AbstractGruppeDokument) {
      gruppeId = ((AbstractGruppeDokument) reference).id;
    } else if (reference instanceof Number) {
      gruppeId = ((Number) reference).longValue();
    }

    return vergleichPolitischeGruppeStimmenPerGruppeId.getOrDefault(gruppeId, vergleichUnbekannt);
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> collectVergleicheWahlberechtigteAnzahl(WasElectContext wasElectContext,
      GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt, GebietMitDetails gebietErgebnisVorlaeufig, GebietMitDetails gebietErgebnisEndgueltig) {
    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> result = createVergleichPerStimmenart(wasElectContext, vergleichUnbekannt);

    Long anzahlOhneWahlscheinVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlOhneWahlscheinEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlNichtImWaehlerverzeichnisVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlNichtImWaehlerverzeichnisEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlGesamtVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlGesamtEndgueltig = ANZAHL_UNBEKANNT;

    if (gebietErgebnisVorlaeufig != null) {
      anzahlOhneWahlscheinVorlaeufig = gebietErgebnisVorlaeufig.anzahlWahlberechtigteOhneWahlschein;
      anzahlMitWahlscheinVorlaeufig = gebietErgebnisVorlaeufig.anzahlWahlberechtigteMitWahlschein;
      anzahlNichtImWaehlerverzeichnisVorlaeufig = gebietErgebnisVorlaeufig.anzahlWahlberechtigteNichtImWaehlerverzeichnis;
      anzahlGesamtVorlaeufig = gebietErgebnisVorlaeufig.anzahlWahlberechtigteGesamt;
    }

    if (gebietErgebnisEndgueltig != null) {
      anzahlOhneWahlscheinEndgueltig = gebietErgebnisEndgueltig.anzahlWahlberechtigteOhneWahlschein;
      anzahlMitWahlscheinEndgueltig = gebietErgebnisEndgueltig.anzahlWahlberechtigteMitWahlschein;
      anzahlNichtImWaehlerverzeichnisEndgueltig = gebietErgebnisEndgueltig.anzahlWahlberechtigteNichtImWaehlerverzeichnis;
      anzahlGesamtEndgueltig = gebietErgebnisEndgueltig.anzahlWahlberechtigteGesamt;
    }

    result.put(Stimmenart.OHNE_WAHLSCHEIN, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlOhneWahlscheinVorlaeufig, anzahlOhneWahlscheinEndgueltig));
    result.put(Stimmenart.MIT_WAHLSCHEIN, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlMitWahlscheinVorlaeufig, anzahlMitWahlscheinEndgueltig));
    result.put(Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS,
        new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlNichtImWaehlerverzeichnisVorlaeufig, anzahlNichtImWaehlerverzeichnisEndgueltig));
    result.put(Stimmenart.GESAMT, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlGesamtVorlaeufig, anzahlGesamtEndgueltig));

    return result;
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> collectVergleicheWaehlerAnzahl(WasElectContext wasElectContext, GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt,
      GebietMitDetails gebietErgebnisVorlaeufig, GebietMitDetails gebietErgebnisEndgueltig) {
    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> result = createVergleichPerStimmenart(wasElectContext, vergleichUnbekannt);

    Long anzahlOhneWahlscheinVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlOhneWahlscheinEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinAnzahlAusUrnenwahlVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinAnzahlAusUrnenwahlEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinAnzahlAusBriefwahlVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlMitWahlscheinAnzahlAusBriefwahlEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlMitVollmachtVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlMitVollmachtEndgueltig = ANZAHL_UNBEKANNT;
    Long anzahlGesamtVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlGesamtEndgueltig = ANZAHL_UNBEKANNT;

    if (gebietErgebnisVorlaeufig != null) {
      anzahlOhneWahlscheinVorlaeufig = gebietErgebnisVorlaeufig.anzahlWaehlerOhneWahlschein;
      anzahlMitWahlscheinVorlaeufig = gebietErgebnisVorlaeufig.anzahlWaehlerMitWahlschein;
      anzahlMitWahlscheinAnzahlAusUrnenwahlVorlaeufig = gebietErgebnisVorlaeufig.anzahlWaehlerMitWahlscheinAnteilAusUrnenwahl;
      anzahlMitWahlscheinAnzahlAusBriefwahlVorlaeufig = gebietErgebnisVorlaeufig.anzahlWaehlerMitWahlscheinAnteilAusBriefwahl;
      anzahlMitVollmachtVorlaeufig = gebietErgebnisVorlaeufig.anzahlWaehlerMitVollmacht;
      anzahlGesamtVorlaeufig = gebietErgebnisVorlaeufig.anzahlWaehlerGesamt;
    }

    if (gebietErgebnisEndgueltig != null) {
      anzahlOhneWahlscheinEndgueltig = gebietErgebnisEndgueltig.anzahlWaehlerOhneWahlschein;
      anzahlMitWahlscheinEndgueltig = gebietErgebnisEndgueltig.anzahlWaehlerMitWahlschein;
      anzahlMitWahlscheinAnzahlAusUrnenwahlEndgueltig = gebietErgebnisEndgueltig.anzahlWaehlerMitWahlscheinAnteilAusUrnenwahl;
      anzahlMitWahlscheinAnzahlAusBriefwahlEndgueltig = gebietErgebnisEndgueltig.anzahlWaehlerMitWahlscheinAnteilAusBriefwahl;
      anzahlMitVollmachtEndgueltig = gebietErgebnisEndgueltig.anzahlWaehlerMitVollmacht;
      anzahlGesamtEndgueltig = gebietErgebnisEndgueltig.anzahlWaehlerGesamt;
    }

    result.put(Stimmenart.OHNE_WAHLSCHEIN, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlOhneWahlscheinVorlaeufig, anzahlOhneWahlscheinEndgueltig));
    result.put(Stimmenart.MIT_WAHLSCHEIN, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlMitWahlscheinVorlaeufig, anzahlMitWahlscheinEndgueltig));
    result.put(Stimmenart.ALS_URNENWAHL_IN_URNE,
        new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlMitWahlscheinAnzahlAusUrnenwahlVorlaeufig, anzahlMitWahlscheinAnzahlAusUrnenwahlEndgueltig));
    result.put(Stimmenart.ALS_BRIEFWAHL_IN_URNE,
        new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlMitWahlscheinAnzahlAusBriefwahlVorlaeufig, anzahlMitWahlscheinAnzahlAusBriefwahlEndgueltig));
    result.put(Stimmenart.MIT_VOLLMACHT, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlMitVollmachtVorlaeufig, anzahlMitVollmachtEndgueltig));
    result.put(Stimmenart.GESAMT, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlGesamtVorlaeufig, anzahlGesamtEndgueltig));

    return result;
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> collectVergleicheGueltigeStimmen(WasElectContext wasElectContext, GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt,
      GebietMitDetails gebietErgebnisVorlaeufig, GebietMitDetails gebietErgebnisEndgueltig) {
    return collectVergleicheGruppeStimmenarten(wasElectContext, vergleichUnbekannt, gebietErgebnisVorlaeufig, gebietErgebnisEndgueltig, gebietErgebnis -> gebietErgebnis.anzahlGueltigeDirektstimmen,
        gebietErgebnis -> gebietErgebnis.anzahlGueltigeListenstimmen);
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> collectVergleicheUngueltigeStimmen(WasElectContext wasElectContext, GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt,
      GebietMitDetails gebietErgebnisVorlaeufig, GebietMitDetails gebietErgebnisEndgueltig) {
    return collectVergleicheGruppeStimmenarten(wasElectContext, vergleichUnbekannt, gebietErgebnisVorlaeufig, gebietErgebnisEndgueltig, gebietErgebnis -> gebietErgebnis.anzahlUngueltigeDirektstimmen,
        gebietErgebnis -> gebietErgebnis.anzahlUngueltigeListenstimmen);
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> collectVergleichePolitischeGruppeStimmen(WasElectContext wasElectContext,
      GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt, Long gruppeId, GebietMitDetails gebietErgebnisVorlaeufig, GebietMitDetails gebietErgebnisEndgueltig) {
    ToLongFunction<GebietMitDetails> anzahlDirektstimmenFct = gebietErgebnis -> gebietErgebnis.politischeGruppenErgebnis.stream() //
        .filter(politischeGruppeErgebnis -> Objects.equals(politischeGruppeErgebnis.gruppe.id, gruppeId) && politischeGruppeErgebnis.direktstimmenAufGebietErfassbar) //
        .findFirst() //
        .map(politischeGruppeErgebnis -> politischeGruppeErgebnis.direktstimmen.anzahlStimmen).orElse(ANZAHL_UNBEKANNT);

    ToLongFunction<GebietMitDetails> anzahlListenstimmenFct = gebietErgebnis -> gebietErgebnis.politischeGruppenErgebnis.stream() //
        .filter(politischeGruppeErgebnis -> Objects.equals(politischeGruppeErgebnis.gruppe.id, gruppeId) && politischeGruppeErgebnis.listenstimmenAufGebietErfassbar) //
        .findFirst() //
        .map(politischeGruppeErgebnis -> politischeGruppeErgebnis.listenstimmen.anzahlStimmen).orElse(ANZAHL_UNBEKANNT);

    return collectVergleicheGruppeStimmenarten(wasElectContext, vergleichUnbekannt, gebietErgebnisVorlaeufig, gebietErgebnisEndgueltig, anzahlDirektstimmenFct, anzahlListenstimmenFct);
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> collectVergleicheGruppeStimmenarten(WasElectContext wasElectContext, GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt,
      GebietMitDetails gebietErgebnisVorlaeufig, GebietMitDetails gebietErgebnisEndgueltig, ToLongFunction<GebietMitDetails> anzahlDirektstimmenFct,
      ToLongFunction<GebietMitDetails> anzahlListenstimmenFct) {
    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> result = createVergleichPerStimmenart(wasElectContext, vergleichUnbekannt);

    Long anzahlDirektstimmenVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlDirektstimmenEndgueltig = ANZAHL_UNBEKANNT;

    Long anzahlListenstimmenVorlaeufig = ANZAHL_UNBEKANNT;
    Long anzahlListenstimmenEndgueltig = ANZAHL_UNBEKANNT;

    if (gebietErgebnisVorlaeufig != null) {
      anzahlDirektstimmenVorlaeufig = anzahlDirektstimmenFct.applyAsLong(gebietErgebnisVorlaeufig);
      anzahlListenstimmenVorlaeufig = anzahlListenstimmenFct.applyAsLong(gebietErgebnisVorlaeufig);
    }

    if (gebietErgebnisEndgueltig != null) {
      anzahlDirektstimmenEndgueltig = anzahlDirektstimmenFct.applyAsLong(gebietErgebnisEndgueltig);
      anzahlListenstimmenEndgueltig = anzahlListenstimmenFct.applyAsLong(gebietErgebnisEndgueltig);
    }

    result.put(Stimmenart.DIREKTSTIMME, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlDirektstimmenVorlaeufig, anzahlDirektstimmenEndgueltig));
    result.put(Stimmenart.LISTENSTIMME, new GesamtErgebnisVergleichEingabeDokument(wasElectContext, anzahlListenstimmenVorlaeufig, anzahlListenstimmenEndgueltig));

    return result;
  }

  private static Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> createVergleichPerStimmenart(WasElectContext wasElectContext, GesamtErgebnisVergleichEingabeDokument vergleichUnbekannt) {
    Map<Stimmenart, GesamtErgebnisVergleichEingabeDokument> result = new HashMap<>();

    for (Stimmenart stimmenart : Stimmenart.values()) {
      result.put(stimmenart, vergleichUnbekannt);
    }

    return result;
  }

  private static boolean isErgebnisartVorlaeufig(ErgebnisDaten ergebnisDaten) {
    return ergebnisDaten != null && ergebnisDaten.getErgebnis().isVorlaeufig();
  }

  private static boolean isErgebnisartEndgueltig(ErgebnisDaten ergebnisDaten) {
    return ergebnisDaten != null && ergebnisDaten.getErgebnis().isEndgueltig();
  }

  private static GebietDokument createGebietDokument(WasElectContext wasElectContext, Gebiet gebiet) {
    return GebietDokument.lookupOrCreate(wasElectContext, gebiet);
  }

  private static GebietDokument createGebietDokument(WasElectContext wasElectContext, WahlverwaltungGebietInformation wahlverwaltungGebietInformation) {
    return new GebietDokument(wasElectContext, wahlverwaltungGebietInformation.getName(), wahlverwaltungGebietInformation.getNummer());
  }

  @Nullable
  private static GebietMitDetails createGebietErgebnisDokument(WasElectContext wasElectContext, GebietUndStimmabgabeart gebietUndStimmabgabeart, ErgebnisDaten ergebnisDaten,
      GebietMitStimmenDocument wahlgebietUebergeordnet, Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur, KandidaturCoreDaten kandidaturCoreDaten) {
    if (ergebnisDaten == null) {
      return null;
    }

    return GesamtErgebnisDokument.erstelleGebietMitDetails(wasElectContext, ergebnisDaten, gebietUndStimmabgabeart, wahlgebietUebergeordnet, kandidaturCoreDaten, ergebnisDokumentPerKandidatur);
  }

  private static List<NiederschriftUebersichtDocument> collectNiederschriftUebersichtDokumente(WasElectContext wasElectContext, NiederschriftFormatter niederschriftFormatter,
      Collection<Niederschrift> niederschriften) {
    Comparator<Niederschrift> niederschriftComparator = new NiederschriftByGebietNummerAndNiederschriftArtComparator(wasElectContext::getMessage);

    return GesamtErgebnisDokument.erstelleNiederschriftUebersichten(wasElectContext, niederschriftFormatter, niederschriftComparator, niederschriften);
  }
}
