
package de.ivu.elect.dokumente.ergebnis.p22;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.document.generator.template.annotations.Document;
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.dokumente.entity.NlDokumentVorlageArt;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.NlErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.OSVLegacyErgebnisDatenParser;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.AbstractGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppeArt;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.control.NlStimmzettelFormatter;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.control.EntityMitPersonendatenByNamenComparator;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyKonfliktCustomData;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.dokumente.ergebnis.AbstractNlKandidatenStimmenProListeDocument;
import de.ivu.elect.dokumente.ergebnis.NlErgebnisDatenCustomDataDocumentContext;
import de.ivu.elect.dokumente.ergebnis.NlGewaehlteProGruppeDocument;
import de.ivu.elect.dokumente.ergebnis.NlKandidatStimmenDocument;
import de.ivu.elect.dokumente.ergebnis.NlListeKandidatenStimmenProListeDocument;
import de.ivu.elect.dokumente.ergebnis.NlStelKandidatenStimmenProListeDocument;
import de.ivu.elect.dokumente.ergebnis.adapter.Fraction;
import de.ivu.elect.dokumente.ergebnis.adapter.NlErgebnisDaten2ErgebnisDokumentAdapter;
import de.ivu.elect.dokumente.ergebnis.dhondt.NlDHondtQuotientPolitischeGruppeTabelleDocument;
import de.ivu.elect.dokumente.ergebnis.dhondt.NlDHondtTableRow;
import de.ivu.elect.dokumente.wahl.NlCoreWahlCustomDataDocument;
import de.ivu.elect.dokumente.wahlvorschlag.GruppeDokument;
import de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis.KandidaturMitStimmergebnissenDocument;
import de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis.ListeMitStimmergebnissenDocument;
import de.ivu.elect.sitzberechnung.osv.exchange.p22.BesonderheitBeiNominierungDerGewaehlten;
import de.ivu.elect.sitzberechnung.osv.exchange.p22.BesonderheitBeiNominierungDerGewaehltenTyp;

@Document(value = NlDokumentVorlageArt.P22_SUB_TEMPLATE, isSubTemplate = true)
public class P22ErgebnisDatenCustomDataDocument extends BaseDocument {

  private static final long serialVersionUID = 1L;

  public static final P22ErgebnisDatenCustomDataDocument EMPTY_OBJECT = new P22ErgebnisDatenCustomDataDocument();

  public static final int LISTE_OHNE_STEL_NUMMER = -1;

  private static final Logger LOG = LoggerFactory.getLogger(P22ErgebnisDatenCustomDataDocument.class);

  @DocumentSimpleField("Wahlteiler") //kiesdeler
  public final String wahlteiler;

  @DocumentSimpleField("Anzahl-Sitze-Zu-Verteilen-Bei-Erster-Zuteilung") //Totaal aantal zetels
  public final BigDecimal anzahlSitzeZuVerteilenBeiErsterZuteilung;

  @DocumentSimpleField("Gesamtzahl-Sitze-Aus-Erster-Zuteilung") //Totaal aantal toegewesen volle zetels
  public final BigDecimal gesamtzahlSitzeAusErsterZuteilung;

  @DocumentSimpleField("Anzahl-Restsitze-Nach-Erster-Zuteilung") //Restzetels
  public final BigDecimal anzahlRestsitzeNachErsterZuteilung;

  @DocumentSimpleField("Anzahl-Unbesetzte-Restsitze-Vor-Oberverteilung-Nach-DHondt") // Hierna waren er nog...
  public final BigDecimal anzahlUnbesetzteRestsitzeVorOberverteilungNachDHondt;

  @DocumentComplexField("DHondtQuotienten")
  public final NlDHondtQuotientPolitischeGruppeTabelleDocument dhondt;

  @DocumentSimpleField("Gesamtzahl-der-Sitze")
  public final long anzahlSitze;
  @DocumentSimpleField("Vorzugsschwelle-in-Prozent")
  public final long vorzugsschwelleInProzent;

  @DocumentRepeat(value = "GruppenUnterverteilungen", elementClass = GruppeUnterverteilungenDokument.class, itemName = "gruppeUnterverteilung")
  public final List<GruppeUnterverteilungenDokument> gruppenUnterverteilungen = new ArrayList<>();
  @DocumentRepeat(value = "KandidatenStimmenProListe", elementClass = AbstractNlKandidatenStimmenProListeDocument.class, itemName = "stimmenDokument")
  public final List<AbstractNlKandidatenStimmenProListeDocument> kandidatenStimmenProListeDokumente = new ArrayList<>();
  @DocumentRepeat(value = "GewaehlteKandidatenAlphabetisch", elementClass = NlKandidatStimmenDocument.class, itemName = "kandidat")
  public final List<NlKandidatStimmenDocument> gewaehlteKandidatenAlphabetisch;
  @DocumentRepeat(value = "Besonderheiten", elementClass = String.class, itemName = "Besonderheit")
  public final List<String> besonderheiten;
  @DocumentRepeat(value = "listenerschoepfungenVonGruppen", elementClass = String.class, itemName = "listenerschoepfung")
  public final List<String> listenerschoepfungenVonGruppen;
  @DocumentRepeat(value = "p22GruppenSortiertNachAnzahlStimmen", elementClass = P22GruppeDocument.class, itemName = "p22Gruppe")
  public final List<P22GruppeDocument> p22GruppenSortiertNachOrdnungszahl = new ArrayList<>();
  @DocumentRepeat(value = "p22GruppenSortiertNachAnzahlStimmen", elementClass = P22GruppeDocument.class, itemName = "p22Gruppe")
  public final List<P22GruppeDocument> p22GruppenSortiertNachAnzahlStimmen;
  @DocumentRepeat(value = "p22GruppenSortiertNachAnzahlStimmenGewichtetMitVoteValues", elementClass = P22GruppeDocument.class, itemName = "p22Gruppe")
  public final List<P22GruppeDocument> p22GruppenSortiertNachAnzahlStimmenGewichtetMitVoteValues;
  @DocumentRepeat(value = "p22GruppenSortiertNachAnzahlSitze", elementClass = P22GruppeDocument.class, itemName = "p22Gruppe")
  public final List<P22GruppeDocument> p22GruppenSortiertNachAnzahlSitze;
  @DocumentRepeat(value = "GewaehlteProGruppe", elementClass = NlGewaehlteProGruppeDocument.class, itemName = "gewaehlte")
  public List<NlGewaehlteProGruppeDocument> gewaehlteProGruppeDokumente;

  @DocumentSimpleField("Anzahl-Gruppen-Mit-Einzelner-Liste")
  public final Long anzahlGruppenMitEinzelnerListe; // ELECT-13535

  @DocumentSimpleField("Anzahl-Gruppen-Mit-Identischer-Liste")
  public final Long anzahlGruppenMitIdentischerListe; // ELECT-13535

  @DocumentSimpleField("Anzahl-Gruppen-Mit-Listengruppen")
  public final Long anzahlGruppenMitListengruppen; // ELECT-13535

  private final NlErgebnisDatenCustomDataDocumentContext context;

  private final NlErgebnisDaten2ErgebnisDokumentAdapter adapter;

  private final NlCoreWahlCustomDataDocument wahlCustomDataDocument;

  public P22ErgebnisDatenCustomDataDocument(NlErgebnisDatenCustomDataDocumentContext context, NlCoreWahlCustomDataDocument wahlCustomDataDocument) {
    super();

    NlErgebnisDaten nlErgebnisDaten = context.getNlErgebnisDaten();
    Stimmabgabeart stimmabgabeart = context.getStimmabgabeart();

    this.context = context;
    this.adapter = new NlErgebnisDaten2ErgebnisDokumentAdapter(OSVLegacyErgebnisDatenParser.parseOSVLegacyErgebnisDaten(nlErgebnisDaten.getWahl()), nlErgebnisDaten.getKonflikte());
    this.wahlCustomDataDocument = wahlCustomDataDocument;

    this.anzahlSitze = adapter.getAnzahlSitzeZuVerteilenBeiErsterZuteilung();
    this.vorzugsschwelleInProzent = this.adapter.getVorzugsschwelleInProzent();

    Map<Long, Long> gesamteSitzenzahlProGruppe = getGesamteSitzenzahlProGruppeMap(nlErgebnisDaten);

    List<PolitischeGruppe> politischeGruppenSortiertNachPosition = this.context.getPolitischeGruppenSortedByPosition();

    long anzahlGruppenMitEinzelnerListe = 0L;
    long anzahlGruppenMitIdentischerListe = 0L;
    long anzahlGruppenMitListengruppen = 0L;

    for (PolitischeGruppe politischeGruppe : politischeGruppenSortiertNachPosition) {
      Long anzahlSitzeFuerGruppe = gesamteSitzenzahlProGruppe.get(politischeGruppe.getId());

      if(anzahlSitzeFuerGruppe == null) {
        anzahlSitzeFuerGruppe = 0L;
      }

      long anzahlStimmenFuerGruppe = nlErgebnisDaten.getSummeGruppeListenstimmen(politischeGruppe, stimmabgabeart);
      long anzahlStimmenGewichtetMitVoteValuesFuerGruppe = nlErgebnisDaten.getSummeGruppeListenstimmenGewichtetMitVoteValues(politischeGruppe, stimmabgabeart);

      P22GruppeDocument p22GruppeDocument = new P22GruppeDocument(this.context, this.adapter, politischeGruppe, anzahlSitzeFuerGruppe, anzahlStimmenFuerGruppe, anzahlStimmenGewichtetMitVoteValuesFuerGruppe);
      this.p22GruppenSortiertNachOrdnungszahl.add(p22GruppeDocument);

      if (p22GruppeDocument.isEinzelneListe) {
        anzahlGruppenMitEinzelnerListe++;
      } else if (p22GruppeDocument.isGruppeIdentischerListe) {
        anzahlGruppenMitIdentischerListe++;
      } else {
        anzahlGruppenMitListengruppen++;
      }
    }

    this.anzahlGruppenMitEinzelnerListe = anzahlGruppenMitEinzelnerListe;
    this.anzahlGruppenMitIdentischerListe = anzahlGruppenMitIdentischerListe;
    this.anzahlGruppenMitListengruppen = anzahlGruppenMitListengruppen;

    this.p22GruppenSortiertNachAnzahlStimmen = this.p22GruppenSortiertNachOrdnungszahl.stream().sorted((l1, l2) -> Long.compare(l2.stimmenzahlDerGruppe, l1.stimmenzahlDerGruppe)).collect(toList());
    this.p22GruppenSortiertNachAnzahlStimmenGewichtetMitVoteValues = this.p22GruppenSortiertNachOrdnungszahl.stream().sorted((l1, l2) -> Long.compare(l2.stimmenzahlDerGruppeGewichtetMitVoteValues, l1.stimmenzahlDerGruppeGewichtetMitVoteValues)).collect(toList());

    this.p22GruppenSortiertNachAnzahlSitze = this.p22GruppenSortiertNachOrdnungszahl.stream().sorted((p1, p2) -> Long.compare(p2.gesamteSitzenzahl, p1.gesamteSitzenzahl)).collect(toList());

    this.wahlteiler = this.adapter.getWahlteiler().toString();

    this.anzahlSitzeZuVerteilenBeiErsterZuteilung = new BigDecimal(this.adapter.getAnzahlSitzeZuVerteilenBeiErsterZuteilung());
    this.gesamtzahlSitzeAusErsterZuteilung = new BigDecimal(this.adapter.getGesamtzahlSitzeAusErsterZuteilung());
    this.anzahlRestsitzeNachErsterZuteilung = new BigDecimal(this.adapter.getAnzahlRestsitzeNachErsterZuteilung());

    this.dhondt = new NlDHondtQuotientPolitischeGruppeTabelleDocument(this.context, this.adapter, this.p22GruppenSortiertNachOrdnungszahl);

    this.anzahlUnbesetzteRestsitzeVorOberverteilungNachDHondt = new BigDecimal(this.adapter.getAnzahlUnbesetzteRestsitzeVorOberverteilungNachDHondt());

    erzeugeGruppenDokumente(this.context, this.adapter, politischeGruppenSortiertNachPosition, gesamteSitzenzahlProGruppe);

    this.gewaehlteKandidatenAlphabetisch = getGewaehlteInAlphabetischerReihenfolge();

    this.besonderheiten = this.adapter.getBesonderheiten().stream()
            .map(b -> getLokalisierteBesonderheitMessage(b, getKandidaturnameFormatter(this.context), nlErgebnisDaten.getListenkandidaturen()))
        .collect(toList());

    this.besonderheiten.add(getMehrheitssicherungsklausel());

    this.listenerschoepfungenVonGruppen = getVonListenerschoepfungBeiOberverteilungBetroffeneGruppen();

    // ELECT-13638, zu Testzwecken... dump(this);
  }

  private Function<Kandidatur, String> getKandidaturnameFormatter(NlErgebnisDatenCustomDataDocumentContext context) {
    if (context.getStimmzettelFormatter() instanceof NlStimmzettelFormatter) {
      return k -> ((NlStimmzettelFormatter) context.getStimmzettelFormatter()).formatKuerzenKandidaturNamenInDokument(k);
    }

    return k -> context.getStimmzettelFormatter().formatKandidaturNamen(k);
  }

  private P22ErgebnisDatenCustomDataDocument() {
    super();

    BigDecimal emptyDecimal = new BigDecimal("0");

    this.context = null;
    this.adapter = NlErgebnisDaten2ErgebnisDokumentAdapter.EMPTY_OBJECT;
    this.wahlCustomDataDocument = null;

    this.wahlteiler = "";
    this.dhondt = NlDHondtQuotientPolitischeGruppeTabelleDocument.EMPTY_OBJECT;
    this.anzahlSitze = 0;
    this.vorzugsschwelleInProzent = 0;
    this.anzahlRestsitzeNachErsterZuteilung = emptyDecimal;
    this.anzahlSitzeZuVerteilenBeiErsterZuteilung = emptyDecimal;
    this.gesamtzahlSitzeAusErsterZuteilung = emptyDecimal;
    this.anzahlUnbesetzteRestsitzeVorOberverteilungNachDHondt = emptyDecimal;
    this.gewaehlteKandidatenAlphabetisch = emptyList();

    this.besonderheiten = emptyList();
    this.p22GruppenSortiertNachAnzahlStimmen = emptyList();
    this.p22GruppenSortiertNachAnzahlStimmenGewichtetMitVoteValues = emptyList();
    this.p22GruppenSortiertNachAnzahlSitze = emptyList();
    this.listenerschoepfungenVonGruppen = emptyList();

    this.anzahlGruppenMitEinzelnerListe = 0L;
    this.anzahlGruppenMitIdentischerListe = 0L;
    this.anzahlGruppenMitListengruppen = 0L;
  }

  private List<NlKandidatStimmenDocument> getGewaehlteInAlphabetischerReihenfolge() {
    return kandidatenStimmenProListeDokumente.stream().flatMap(d -> d.kandidatenStimmenzahl.stream()).filter(k -> k.isEndgueltigGewaehlt)
        .sorted((u, v) -> new EntityMitPersonendatenByNamenComparator<>().compare(u.getListenkandidatur(), v.getListenkandidatur())).collect(toList());
  }

  private Map<Long, Long> getGesamteSitzenzahlProGruppeMap(NlErgebnisDaten nlErgebnisDaten) {
    return nlErgebnisDaten.getSitze().stream().collect(groupingBy(Sitze::getGruppe)).entrySet().stream()
        .map(e -> new SimpleImmutableEntry<>(e.getKey().getId(), e.getValue().stream().mapToLong(Sitze::getAnzahl).reduce(Long::sum).orElse(0L)))
        .collect(toMap(SimpleImmutableEntry::getKey, SimpleImmutableEntry::getValue));
  }

  private void erzeugeGruppenDokumente(NlErgebnisDatenCustomDataDocumentContext context, NlErgebnisDaten2ErgebnisDokumentAdapter adapter, List<PolitischeGruppe> politischeGruppenSortiertNachPosition, Map<Long, Long> gesamteSitzenzahlProGruppe) {
    EntityAufStimmzettelDaten entityAufStimmzettelDaten = context.getEntityAufStimmzettelDaten();

    for (PolitischeGruppe politischeGruppe : politischeGruppenSortiertNachPosition) {
      GruppePK gruppePk = politischeGruppe.getPk();
      GruppeDokument gruppeDokument = new GruppeDokument(politischeGruppe);

      SortedMap<Wahldurchfuehrungsgebiet, Liste> listePerWahlkreis = context.getListePerWahlkreis(politischeGruppe);
      Set<Liste> listenDerGruppe = new HashSet<>(listePerWahlkreis.values());

      NlPolitischeGruppeArt nlPolitischeGruppeArt = NlPolitischeGruppe.bestimmeEinreichungsart(listenDerGruppe, entityAufStimmzettelDaten);

      if (nlPolitischeGruppeArt.isListengruppe()) {
        SortedMap<Integer, SortedMap<Wahldurchfuehrungsgebiet, Liste>> sortiertesStelMap = createListenSortiertNachWahlkreisProStelMap(context, listePerWahlkreis);

        gruppenUnterverteilungen.add(createGruppeUnterverteilungenDokument(context, adapter, sortiertesStelMap, gruppeDokument, politischeGruppe));

        kandidatenStimmenProListeDokumente.addAll(createKandidatStimmenFuerListengruppe(context, adapter, sortiertesStelMap, gruppeDokument, gruppePk));
      } else if (nlPolitischeGruppeArt.isIdentischeListe()) {
        Optional<Integer> stelNummerIfPresent = listenDerGruppe.stream().findAny().flatMap(NlListe::getStelNummerWas);

        if (stelNummerIfPresent.isPresent()) {
          Integer stelNummer = stelNummerIfPresent.get();
          String stelKey = NlListe.createStelKey(gruppePk, stelNummer);

          kandidatenStimmenProListeDokumente.add(new NlStelKandidatenStimmenProListeDocument(context, gruppeDokument, listePerWahlkreis, adapter::isMitVorzugsstimmenGewaehlt, adapter.isMitGeaenderterReihenfolge(stelKey), stelNummer, true));
        } else {
          LOG.error("Gruppe ist identische Liste OHNE Stelnummer [gruppe = {}]", politischeGruppe);
        }
      } else if (nlPolitischeGruppeArt.isEinzelliste()) {
        if(listePerWahlkreis.size() > 1) { // ELECT-13600, Spezialfall EUW -> NUR Einzellisten im Listenwahlgebiet (Wurzelgebiet), aber Dokument enthält Angaben für alle untergeordneten Wahlkreise...
          LOG.info("Gruppe ist Einzelliste, die auf mehreren untergeordneten Wahlkreisen antritt [gruppe = {}, wahlkreise = {}]", politischeGruppe, listePerWahlkreis.keySet());

          Integer stelNummer = LISTE_OHNE_STEL_NUMMER;
          String stelKey = NlListe.createStelKey(gruppePk, stelNummer);

          boolean mitGeaenderterReihenfolge = listenDerGruppe.stream().anyMatch(liste -> adapter.isMitGeaenderterReihenfolge(liste.getPk())); // ELECT-13637

          kandidatenStimmenProListeDokumente.add(new NlStelKandidatenStimmenProListeDocument(context, gruppeDokument, listePerWahlkreis, adapter::isMitVorzugsstimmenGewaehlt, mitGeaenderterReihenfolge, stelNummer, false));
        } else {
          Entry<Wahldurchfuehrungsgebiet, Liste> entry = listePerWahlkreis.entrySet().iterator().next();

          Wahldurchfuehrungsgebiet wahlkreis = entry.getKey();
          Liste einzelneListe = entry.getValue();

          kandidatenStimmenProListeDokumente.add(new NlListeKandidatenStimmenProListeDocument(context, gruppeDokument, wahlkreis, einzelneListe, adapter.isMitGeaenderterReihenfolge(einzelneListe.getPk()), adapter::isMitVorzugsstimmenGewaehlt));
        }
      } else {
        LOG.error("Gruppe hat unbekannte Art [art = {}, gruppe = {}] ", nlPolitischeGruppeArt, politischeGruppe);
      }
    }

    Map<Long, List<AbstractNlKandidatenStimmenProListeDocument>> kandidatenStimmenProGruppeMap = kandidatenStimmenProListeDokumente.stream()
        .collect(groupingBy(d -> d.gruppe.id, TreeMap::new, toList()));

    gewaehlteProGruppeDokumente = kandidatenStimmenProGruppeMap.entrySet().stream()
            .map(e -> new NlGewaehlteProGruppeDocument(gesamteSitzenzahlProGruppe.get(e.getKey()), e.getValue()))
            .sorted(Comparator.comparingLong(gd -> gd.gruppe.positionVorgegeben))
            .collect(toList());
  }

  private static List<AbstractNlKandidatenStimmenProListeDocument> createKandidatStimmenFuerListengruppe(NlErgebnisDatenCustomDataDocumentContext context, NlErgebnisDaten2ErgebnisDokumentAdapter adapter, SortedMap<Integer, SortedMap<Wahldurchfuehrungsgebiet, Liste>> sortiertesStelMap, GruppeDokument gruppe, GruppePK gruppePK) {
    List<AbstractNlKandidatenStimmenProListeDocument> result = new ArrayList<>();
    // Zuerst die Stels und danach die einzelne Listen der Gruppe.
    List<AbstractNlKandidatenStimmenProListeDocument> einzelneListen = new ArrayList<>();

    for (Entry<Integer, SortedMap<Wahldurchfuehrungsgebiet, Liste>> entryPerStelNummer : sortiertesStelMap.entrySet()) {
      Integer stelNummer = entryPerStelNummer.getKey();
      SortedMap<Wahldurchfuehrungsgebiet, Liste> listePerWahlkreis = entryPerStelNummer.getValue();

      if (stelNummer.equals(LISTE_OHNE_STEL_NUMMER)) {
        listePerWahlkreis.entrySet().stream()
            .map(entry -> new NlListeKandidatenStimmenProListeDocument(context, gruppe, entry.getKey(), entry.getValue(), adapter.isMitGeaenderterReihenfolge(entry.getValue().getPk()), adapter::isMitVorzugsstimmenGewaehlt))
            .forEach(einzelneListen::add);
      } else {
        String stelKey = NlListe.createStelKey(gruppePK, stelNummer);

        result.add(new NlStelKandidatenStimmenProListeDocument(context, gruppe, listePerWahlkreis, adapter::isMitVorzugsstimmenGewaehlt, adapter.isMitGeaenderterReihenfolge(stelKey), stelNummer, false));
      }
    }

    result.addAll(einzelneListen);

    return result;
  }

  private String getLokalisierteBesonderheitMessage(BesonderheitBeiNominierungDerGewaehlten besonderheit,
                                                    Function<Kandidatur, String> kandidaturnameFormatter,
                                                    List<Listenkandidatur> kandidaturen) {
    return context.getMessage(besonderheit.getMsgKey(),
            getFormartierenPersonennamenFuerPersonendatenPk(besonderheit, kandidaturen, kandidaturnameFormatter));
  }

  private Object[] getFormartierenPersonennamenFuerPersonendatenPk(BesonderheitBeiNominierungDerGewaehlten besonderheit,
                                                                   List<Listenkandidatur> kandidaturen, Function<Kandidatur, String> kandidaturnameFormatter) {
    return besonderheit.getParameters().stream().map(p -> {
      if (p.contains(PersonendatenPK.class.getSimpleName())) {
        return kandidaturen.stream()
                .filter(k -> p.contains(k.getPk().toString()))
                .findAny()
                .map(kandidaturnameFormatter)
                .orElse(p);
      } else {
        return p;
      }
    }).toArray();
  }

  private String getMehrheitssicherungsklausel() {
    String result = "";
    if (adapter.kommtMehrheitssicherungsklauselZurAnwendung()) {
      Map<GruppePK, PolitischeGruppe> gruppeMap = context.getPolitischeGruppenSortedByPosition().stream().collect(toMap(Gruppe::getPk, Function.identity()));
      String gewinner = gruppeMap.get(adapter.getGewinnerBeiMehrheitssicherungsklausel(gruppeMap.keySet())).getRelativePosition().toString();
      String verlierer = gruppeMap.get(adapter.getVerliererBeiMehrheitssicherungsklausel(gruppeMap.keySet())).getRelativePosition().toString();
      result = context.getMessage(BesonderheitBeiNominierungDerGewaehltenTyp.Result_User_List_0_HasAbsoluteMajorityOfVotesAndReceivesSeatFromList_1.toMsgKey(), gewinner, verlierer);
    }
    return result;
  }

  private List<String> getVonListenerschoepfungBeiOberverteilungBetroffeneGruppen() {
    Map<GruppePK, Integer> gruppeUndAnzahlErschoepfungenMap = adapter.getVonListenerschoepfungBeiOberverteilungBetroffeneGruppen(getAlleGruppePks());

    boolean isEK = NlWahlart.EERSTE_KAMER.equals(context.getNlWahlart()); // ELECT-13470

    return gruppeUndAnzahlErschoepfungenMap.entrySet().stream().filter(gruppeAnzahlSitzeEntry -> gruppeAnzahlSitzeEntry.getValue() != 0)
        .map(gruppeAnzahlSitzeEntry -> getListenerschoepfungMessageOberverteilung(isEK, "lijst " + getRelativePositionDerGruppe(gruppeAnzahlSitzeEntry.getKey()), gruppeAnzahlSitzeEntry.getValue()))
        .collect(toList());
  }

  private Long getRelativePositionDerGruppe(GruppePK gruppePk) {
    return context.getPolitischeGruppenSortedByPosition().stream().filter(g -> gruppePk.equals(g.getPk())).map(Gruppe::getRelativePosition).findAny().orElse(-1L);
  }

  private List<GruppePK> getAlleGruppePks() {
    return context.getPolitischeGruppenSortedByPosition().stream().map(Gruppe::getPk).collect(toList());
  }

  @DocumentMethod
  public boolean hatListenMitGeaenderterReihenfolge(@DocumentParam("gruppe") Object value) {
    return getPolitischeGruppe(value).map(context::getListen).orElse(emptySet()).stream()
        .anyMatch(liste -> isStelMitGeaenderterReihenfolge(liste) || adapter.isMitGeaenderterReihenfolge(liste.getPk()));
  }

  private boolean isStelMitGeaenderterReihenfolge(Liste liste) {
    return NlListe.isListeEinesStelWas(liste) && adapter.isMitGeaenderterReihenfolge(NlListe.createStelKey(liste.getGruppe().getPk(), NlListe.getStelNummerWas(liste).orElse(-1)));
  }

  @DocumentMethod
  public String getVonListenerschoepfungBeiUnterverteilungBetroffeneListenInGruppe(@DocumentParam("gruppe") Object value) {
    String result = "";

    PolitischeGruppe politischeGruppe = getPolitischeGruppe(value).orElse(null);

    if (politischeGruppe == null) {
      return result;
    }

    List<Liste> listenDerGruppeSortedByWahlkreis = new ArrayList<>(context.getListen(politischeGruppe));
    listenDerGruppeSortedByWahlkreis.sort((one, another) -> NlErgebnisDatenCustomDataDocumentContext.WAHLKREIS_COMPARATOR.compare(one.getGebiet(), another.getGebiet()));

    boolean isEK = NlWahlart.EERSTE_KAMER.equals(context.getNlWahlart()); // ELECT-13470

    Map<WahlvorschlagPK, Integer> vonListenerschoepfungBeiUnterverteilungBetroffeneListenInGruppe = adapter
        .getVonListenerschoepfungBeiUnterverteilungBetroffeneListenInGruppe(listenDerGruppeSortedByWahlkreis.stream().map(Wahlvorschlag::getPk).collect(toList()), politischeGruppe.getPk());

    result += vonListenerschoepfungBeiUnterverteilungBetroffeneListenInGruppe.entrySet().stream().map(entry -> {
      String kieskringString = listenDerGruppeSortedByWahlkreis.stream() //
          .filter(liste -> Objects.equals(liste.getPk(), entry.getKey())) //
          .map(liste -> "kieskring " + liste.getGebiet().getNummer()) // TODO ELECT-13600, LEGACY, NICHT allgemein gültig -> hier (weiterhin) die Annahme, dass Wahlkreis == Listenwahlgebiet...
          .findAny() //
          .orElse("-1");

      return getListenerschoepfungMessageUnterverteilung(isEK, kieskringString, entry.getValue());
    }).collect(joining("\n"));

    result += adapter.getVonListenerschoepfungBeiUnterverteilungBetroffeneStelsInGruppe(politischeGruppe.getPk()).entrySet().stream().map(e -> {
      String stelId = "stel " + e.getKey().split("_")[1];
      return getListenerschoepfungMessageUnterverteilung(isEK, stelId, e.getValue());
    }).collect(joining("\n"));

    return result;
  }

  private Optional<PolitischeGruppe> getPolitischeGruppe(Object value) {
    return wahlCustomDataDocument.findGruppe(value).filter(AbstractGruppe::isPolitischeGruppe).map(g -> (PolitischeGruppe) g);
  }

  // AnomalyFactory#listExhaustionToOtherList!!!
  private String getListenerschoepfungMessageOberverteilung(boolean isEK, String lijstOderKieskring, Integer anzahlSitze) {
    BesonderheitBeiNominierungDerGewaehltenTyp listExhaustionSeat = BesonderheitBeiNominierungDerGewaehltenTyp.Result_User_ListExhaustionFor_0_SeatLostToOtherList;
    BesonderheitBeiNominierungDerGewaehltenTyp listExhaustionSeats = BesonderheitBeiNominierungDerGewaehltenTyp.Result_User_ListExhaustionFor_0_1_SeatsLostToOtherList;

    if (isEK) {
      listExhaustionSeat = BesonderheitBeiNominierungDerGewaehltenTyp.Result_User_ListExhaustionEKFor_0_SeatLostToOtherList;
      listExhaustionSeats = BesonderheitBeiNominierungDerGewaehltenTyp.Result_User_ListExhaustionEKFor_0_1_SeatsLostToOtherList;
    }

    if (anzahlSitze == 1) {
      return context.getMessage(listExhaustionSeat.toMsgKey(), lijstOderKieskring);
    } else {
      return context.getMessage(listExhaustionSeats.toMsgKey(), lijstOderKieskring, anzahlSitze);
    }
  }

  private String getListenerschoepfungMessageUnterverteilung(boolean isEK, String stelOderKieskring, Integer anzahlSitze) {
    String msgKeyListExhaustionSeat = "Result_User_ListExhaustionFor_0_SeatLostToOtherListInGroup_p22";
    String msgKeyListExhaustionSeats = "Result_User_ListExhaustionFor_0_1_SeatLostToOtherListInGroup_p22";

    if (isEK) { // ELECT-13651
      msgKeyListExhaustionSeat = "Result_User_ListExhaustionEKFor_0_SeatLostToOtherListInGroup_p22";
      msgKeyListExhaustionSeats = "Result_User_ListExhaustionEKFor_0_1_SeatLostToOtherListInGroup_p22";
    }

    if (anzahlSitze == 1) {
      return context.getMessage(msgKeyListExhaustionSeat, stelOderKieskring);
    } else {
      return context.getMessage(msgKeyListExhaustionSeats, stelOderKieskring, anzahlSitze);
    }
  }

  @DocumentMethod //erhält die Gruppe in P22-1.11 eine Tabelle?
  public Boolean isGruppeMitUnterverteilung(@DocumentParam("gruppe") Object value) {
    return wahlCustomDataDocument.findGruppe(value).map(g -> getAdapter().isGruppeMitUnterverteilung(g.getPk())).orElse(false);
  }

  @DocumentMethod // P22-1.11
  public Long getGesamteAnzahlSitzeEinerListenGruppe(@DocumentParam("gruppe") Object value) {
    return wahlCustomDataDocument.findGruppe(value)
        .map(g -> getAdapter().getAnzahlSitze(g.getPk()).orElseThrow(() -> new IllegalArgumentException("Zur Gruppe gibt es keine Unterverteilung: " + g.getPk()))).orElse(0L);
  }

  @DocumentMethod // P22-1.11
  public Long getGesamteAnzahlStimmenEinerListenGruppe(@DocumentParam("gruppe") Object value) {
    return wahlCustomDataDocument.findGruppe(value)
        .map(g -> getAdapter().getAnzahlStimmen(g.getPk()).orElseThrow(() -> new IllegalArgumentException("Zur Gruppe gibt es keine Unterverteilung: " + g.getPk()))).orElse(0L);
  }

  @DocumentMethod // P22-1.11 groepskiesdeler
  public String getWahlteilerBeiUnterverteilungInGruppe(@DocumentParam("gruppe") Object value) {
    return wahlCustomDataDocument.findGruppe(value).map(g -> getAdapter().getWahlteilerBeiUnterverteilungInGruppe(g.getPk()).toString()).orElse("");
  }

  @DocumentMethod //Reeds toegewezen aantal zetels
  public Long getSitzeVorDHondtVerteilungBeschraenktAufEinenSitzOberverteilung(@DocumentParam("gruppe") Object value) {
    return getAdapter().getSitzeVorDHondtVerteilungBeschraenktAufEinenSitzOberverteilung(getGruppe(value).map(Gruppe::getPk).orElse(null));
  }

  @DocumentMethod //Gemiddeld aantal stemmen bij toewijzing restzetel
  public String getDHondtQuotientBeschraenktAufEinenSitzOberverteilung(@DocumentParam("gruppe") Object value) {
    return getAdapter().getDHondtQuotientBeschraenktAufEinenSitzOberverteilung(getGruppe(value).map(Gruppe::getPk).orElse(null)).toString();
  }

  @DocumentMethod
  public Boolean isTeilnehmerAnDHondtVerteilung(@DocumentParam("liste") Object value) {
    return getGruppe(value).map(g -> getAdapter().isTeilnehmerAnDHondtVerteilungBeschraenktAufEinenSitzOberverteilung(g.getPk())).orElse(false);
  }

  @DocumentMethod
  public Boolean isMitGeaenderterReihenfolge(@DocumentParam("liste") Object value) {
    return getAdapter().isMitGeaenderterReihenfolge(wahlCustomDataDocument.findListe(value).map(Wahlvorschlag::getPk).orElse(null));
  }

  @DocumentMethod
  public Boolean isMitvorzugsstimmenGewaehlt(@DocumentParam("kandidatur") Object value) {
    Kandidatur kandidatur = getKandidatur(value);

    return kandidatur != null && getAdapter().isMitVorzugsstimmenGewaehlt(kandidatur.getPk());
  }

  @DocumentMethod //aantal volle zetels
  public BigDecimal getAnzahlSitzeAusErsterZuteilung(@DocumentParam("liste") Object value) {
    return getGruppe(value).map(gruppe -> new BigDecimal(getAdapter().getAnzahlSitzeAusErsterZuteilung(gruppe.getPk()))).orElse(null);
  }

  @DocumentMethod //aantal stemmen
  public BigDecimal getAnzahlStimmen(@DocumentParam("liste") Object value) {
    return getGruppe(value)
        .map(gruppe -> new BigDecimal(getAdapter().getAnzahlStimmen(gruppe.getPk()).orElseThrow(() -> new IllegalArgumentException("Zur Gruppe gibt es keine Unterverteilung: " + gruppe.getPk()))))
        .orElse(null);
  }

  @DocumentMethod // overschot
  public String getRestNachErsterZuteilungFuerGruppeInOberverteilung(@DocumentParam("liste") Object value) {
    return formatRestNachErsterZuteilungFuerGruppeInOberverteilung(value, Fraction::formatGemischterBruch);
  }

  @DocumentMethod // ELECT-11986, overschot
  public String getRestNachErsterZuteilungFuerGruppeInOberverteilungGanzzahl(@DocumentParam("liste") Object value) {
    return formatRestNachErsterZuteilungFuerGruppeInOberverteilung(value, Fraction::formatGemischterBruchGanzzahl);
  }

  @DocumentMethod // ELECT-11986, overschot
  public String getRestNachErsterZuteilungFuerGruppeInOberverteilungBruch(@DocumentParam("liste") Object value) {
    return formatRestNachErsterZuteilungFuerGruppeInOberverteilung(value, Fraction::formatGemischterBruchBruch);
  }

  private String formatRestNachErsterZuteilungFuerGruppeInOberverteilung(Object value, Function<Fraction, String> formatFct) {
    return getGruppe(value).map(gruppe -> getAdapter().getRestNachErsterZuteilungFuerGruppeInOberverteilung(gruppe.getPk())).map(formatFct).orElse(null);
  }

  @DocumentMethod
  public Boolean isGewinner(@DocumentParam("liste") Object value, @DocumentParam("lauf") Integer index) {
    return getGruppe(value).filter(gruppe -> getAdapter().isSitzgewinnImLaufInOberverteilungNachDHondt(gruppe.getPk(), index)).isPresent();
  }

  @Deprecated // TODO ELECT-11353
  private Optional<Gruppe> getGruppe(Object value) {
    if (value instanceof ListeMitStimmergebnissenDocument) {
      return findGruppeBySchluessel(((ListeMitStimmergebnissenDocument) value).gruppe.schluessel);
    } else if (value instanceof NlDHondtTableRow) {
      return this.context.getPolitischeGruppenSortedByPosition().stream().filter(g -> g.getRelativePosition().equals(((NlDHondtTableRow) value).ordnungszahl))
          .map(g -> g.castTo(Gruppe.class).getEntity()).findAny();
    } else if (value instanceof P22GruppeDocument) {
      return findGruppeBySchluessel(((P22GruppeDocument) value).schluessel);
    }

    return wahlCustomDataDocument.findGruppe(value);
  }

  private static GruppeUnterverteilungenDokument createGruppeUnterverteilungenDokument(NlErgebnisDatenCustomDataDocumentContext context, NlErgebnisDaten2ErgebnisDokumentAdapter adapter, SortedMap<Integer, SortedMap<Wahldurchfuehrungsgebiet, Liste>> stelMap, GruppeDokument gruppeDokument, PolitischeGruppe gruppe) {
    return new GruppeUnterverteilungenDokument(context, adapter, gruppe, gruppeDokument, stelMap);
  }

  protected SortedMap<Integer, SortedMap<Wahldurchfuehrungsgebiet, Liste>> createListenSortiertNachWahlkreisProStelMap(NlErgebnisDatenCustomDataDocumentContext context, SortedMap<Wahldurchfuehrungsgebiet, Liste> listePerWahlkreis) {
    SortedMap<Integer, SortedMap<Wahldurchfuehrungsgebiet, Liste>> result = new TreeMap<>();

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = context.getEntityAufStimmzettelDaten();

    Set<Liste> listenDerGruppe = new HashSet<>(listePerWahlkreis.values());

    for (Entry<Wahldurchfuehrungsgebiet, Liste> entry : listePerWahlkreis.entrySet()) {
      Integer stelNummer = getStelNummer(entityAufStimmzettelDaten, listenDerGruppe, entry.getValue());

      result.computeIfAbsent(stelNummer, ignored -> new TreeMap<>(NlErgebnisDatenCustomDataDocumentContext.WAHLKREIS_COMPARATOR)).put(entry.getKey(), entry.getValue());
    }

    return result;
  }

  private Integer getStelNummer(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Set<Liste> listenDerGruppe, Liste liste) {
    if (NlListe.getStelInformationen(liste, listenDerGruppe, entityAufStimmzettelDaten).getNlPolitischeGruppeArt().isEinzelliste()) {
      return LISTE_OHNE_STEL_NUMMER;
    } else {
      Optional<Integer> stelNummer = NlListe.getStelNummerWas(liste);

      if (stelNummer.isPresent()) {
        return stelNummer.get();
      } else {
        LOG.info("Die folgende Liste hat keine Stelnummer, obwohl sie als \"einzel Liste\" nicht gekennzeichnet ist: " + liste);
        return LISTE_OHNE_STEL_NUMMER;
      }
    }
  }

  private Kandidatur getKandidatur(Object value) {
    if (value instanceof KandidaturMitStimmergebnissenDocument) {
      return ((KandidaturMitStimmergebnissenDocument) value).getKandidatur();
    }

    return wahlCustomDataDocument.findListenkandidatur(value).orElse(null);
  }

  private Optional<Gruppe> findGruppeBySchluessel(String gruppeSchluessel) {
    return context.getNlErgebnisDaten().findGruppeBySchluessel(gruppeSchluessel);
  }

  private List<OsvLegacyKonfliktCustomData> getOsvLegacyCustomDataObjekte(ErgebnisDaten ergebnisDaten) {
    return ergebnisDaten.getKonflikte().stream().filter(k -> k.getCustomData() instanceof OsvLegacyKonfliktCustomData).map(k -> k.getCustomData(OsvLegacyKonfliktCustomData.class))
        .collect(toList());
  }

  private NlErgebnisDaten2ErgebnisDokumentAdapter getAdapter() {
    if (adapter.equals(NlErgebnisDaten2ErgebnisDokumentAdapter.EMPTY_OBJECT)) {
      throw new IllegalStateException("P22-Daten wurden nicht initialisiert.");
    }

    return adapter;
  }

  private static void dump(P22ErgebnisDatenCustomDataDocument p22ErgebnisDatenCustomDataDocument) {
    String indent0 = " ";
    String indent1 = "   ";
    String indent2 = "     ";
    String indent3 = "       ";
    String indent4 = "         ";
    String indent5 = "           ";
    String indent6 = "             ";

    LOG.info(indent0 + "P22ErgebnisDatenCustomDataDocument");

    LOG.info(indent1 + "p22GruppenSortiertNachOrdnungszahl");
    for (P22GruppeDocument p22GruppeDocument : p22ErgebnisDatenCustomDataDocument.p22GruppenSortiertNachOrdnungszahl) {
      LOG.info(indent2 + dumpP22GruppeDocument(p22GruppeDocument));
    }

    LOG.info(indent1 + "p22GruppenSortiertNachAnzahlSitze");
    for (P22GruppeDocument p22GruppeDocument : p22ErgebnisDatenCustomDataDocument.p22GruppenSortiertNachAnzahlSitze) {
      LOG.info(indent2 + dumpP22GruppeDocument(p22GruppeDocument));
    }

    LOG.info(indent1 + "p22GruppenSortiertNachAnzahlStimmen");
    for (P22GruppeDocument p22GruppeDocument : p22ErgebnisDatenCustomDataDocument.p22GruppenSortiertNachAnzahlStimmen) {
      LOG.info(indent2 + dumpP22GruppeDocument(p22GruppeDocument));
    }

    LOG.info(indent1 + "p22GruppenSortiertNachAnzahlStimmenGewichtetMitVoteValues");
    for (P22GruppeDocument p22GruppeDocument : p22ErgebnisDatenCustomDataDocument.p22GruppenSortiertNachAnzahlStimmenGewichtetMitVoteValues) {
      LOG.info(indent2 + dumpP22GruppeDocument(p22GruppeDocument));
    }

    LOG.info(indent1 + "kandidatenStimmenProListeDokumente");
    for (AbstractNlKandidatenStimmenProListeDocument nlKandidatenStimmenProListeDocument : p22ErgebnisDatenCustomDataDocument.kandidatenStimmenProListeDokumente) {
      LOG.info(indent2 + dumpNlKandidatenStimmenProListeDocument(nlKandidatenStimmenProListeDocument));
      LOG.info(indent3 + "kandidatenStimmenzahl");
      for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlKandidatenStimmenProListeDocument.kandidatenStimmenzahl) {
        LOG.info(indent4 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
      }
      LOG.info(indent3 + "kandidatenStimmenzahlNachRang");
      for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlKandidatenStimmenProListeDocument.kandidatenStimmenzahlNachRang) {
        LOG.info(indent4 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
      }
    }

    LOG.info(indent1 + "gewaehlteProGruppeDokumente");
    for (NlGewaehlteProGruppeDocument nlGewaehlteProGruppeDocument : p22ErgebnisDatenCustomDataDocument.gewaehlteProGruppeDokumente) {
      LOG.info(indent2 + "nlGewaehlteProGruppeDocument [gruppe = " + nlGewaehlteProGruppeDocument.gruppe.name + "]");
      LOG.info(indent3 + "gewaehlteMitVorzugsstimmenSortiertNachStimmenzahl");
      for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlGewaehlteProGruppeDocument.gewaehlteMitVorzugsstimmenSortiertNachStimmenzahl) {
        LOG.info(indent4 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
      }
      LOG.info(indent3 + "gewaehlteMitVorzugsstimmenSortiertNachStimmenzahlGewichtetMitVoteValues");
      for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlGewaehlteProGruppeDocument.gewaehlteMitVorzugsstimmenSortiertNachStimmenzahlGewichtetMitVoteValues) {
        LOG.info(indent4 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
      }
      LOG.info(indent3 + "gewaehlteNichtMitVorzugsstimmenSortiertNachRang");
      for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlGewaehlteProGruppeDocument.gewaehlteNichtMitVorzugsstimmenSortiertNachRang) {
        LOG.info(indent4 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
      }
      LOG.info(indent3 + "listenMitGeaenderterReihenfolge");
      for (AbstractNlKandidatenStimmenProListeDocument nlKandidatenStimmenProListeDocument : nlGewaehlteProGruppeDocument.listenMitGeaenderterReihenfolge) {
        LOG.info(indent4 + dumpNlKandidatenStimmenProListeDocument(nlKandidatenStimmenProListeDocument));
        LOG.info(indent5 + "kandidatenStimmenzahl");
        for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlKandidatenStimmenProListeDocument.kandidatenStimmenzahl) {
          LOG.info(indent6 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
        }
        LOG.info(indent5 + "kandidatenStimmenzahlNachRang");
        for (NlKandidatStimmenDocument nlKandidatStimmenDocument : nlKandidatenStimmenProListeDocument.kandidatenStimmenzahlNachRang) {
          LOG.info(indent6 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
        }
      }
    }

    LOG.info(indent1 + "gewaehlteKandidatenAlphabetisch");
    for (NlKandidatStimmenDocument nlKandidatStimmenDocument : p22ErgebnisDatenCustomDataDocument.gewaehlteKandidatenAlphabetisch) {
      LOG.info(indent2 + dumpNlKandidatStimmenDocument(nlKandidatStimmenDocument));
    }
  }

  private static String dumpNlKandidatenStimmenProListeDocument(AbstractNlKandidatenStimmenProListeDocument nlKandidatenStimmenProListeDocument) {
    return "nlKandidatenStimmenProListeDocument [" + "gruppe = " + nlKandidatenStimmenProListeDocument.gruppe.name + ", " //
        + "stelnummer = " + nlKandidatenStimmenProListeDocument.stelnummer + ", " //
        + "gebietsnummer = " + nlKandidatenStimmenProListeDocument.gebietsnummer + ", " //
        + "gebietsname = " + nlKandidatenStimmenProListeDocument.gebietsname + ", " //
        + "isEinzelneListe = " + nlKandidatenStimmenProListeDocument.isEinzelneListe + ", " //
        + "isIdentischerListe = " + nlKandidatenStimmenProListeDocument.isIdentischerListe + ", " //
        + "isStelOderGruppeIdentischerListe = " + nlKandidatenStimmenProListeDocument.isStelOderGruppeIdentischerListe + ", " //
        + "isMitGeaenderterReihenfolge = " + nlKandidatenStimmenProListeDocument.isMitGeaenderterReihenfolge + ", " //
        + "isMitNichtUeberVorzugGewaehlte = " + nlKandidatenStimmenProListeDocument.isMitNichtUeberVorzugGewaehlte + "]";
  }

  private static String dumpP22GruppeDocument(P22GruppeDocument p22GruppeDocument) {
    return "p22GruppeDocument [" + "ordnungszahl = " + p22GruppeDocument.ordnungszahl + ", " //
        + "kennwort = " + p22GruppeDocument.kennwort + ", " //
        + "isEinzelneListe = " + p22GruppeDocument.isEinzelneListe + ", " //
        + "isGruppeIdentischerListe = " + p22GruppeDocument.isGruppeIdentischerListe //
        + "]";
  }

  private static String dumpNlKandidatStimmenDocument(NlKandidatStimmenDocument nlKandidatStimmenDocument) {
    return "nlKandidatStimmenDocument [" + "listenname = " + nlKandidatStimmenDocument.listenname + ", " //
        + "stelnummer = " + nlKandidatStimmenDocument.stelnummer + ", " //
        + "gebietsnummer = " + nlKandidatStimmenDocument.gebietsnummer + ", " //
        + "gebietsname = " + nlKandidatStimmenDocument.gebietsname + ", " //
        + "listenposition = " + nlKandidatStimmenDocument.listenposition + ", " //
        + "nameAufStimmzettelFormatted = " + nlKandidatStimmenDocument.nameAufStimmzettelFormatted + ", " //
        + "rang = " + nlKandidatStimmenDocument.rang + ", " //
        + "isEndgueltigGewaehlt = " + nlKandidatStimmenDocument.isEndgueltigGewaehlt + ", " //
        + "isUeberVorzugGewaehlt = " + nlKandidatStimmenDocument.isUeberVorzugGewaehlt + ", " //
        + "isAufStelGewaehlt = " + nlKandidatStimmenDocument.isAufStelGewaehlt + ", " //
        + "gewaehltInArt = " + nlKandidatStimmenDocument.gewaehltInArt + ", " //
        + "gewaehltIn = " + nlKandidatStimmenDocument.gewaehltIn + ", " //
        + "gewaehltInName = " + nlKandidatStimmenDocument.gewaehltInName + ", " //
        + "kandidatStimmenProGebiet = " + nlKandidatStimmenDocument.kandidatStimmenProGebiet + "" //
        + "]";
  }
}
