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

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.control.GebietByNummerComparator;
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.control.StimmzettelFormatter;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
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.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.WasElectContext;
import de.ivu.elect.dokumente.ergebnis.AbstractNlKandidatenStimmenProListeDocument;
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.jee.faces.Messages;
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.OSVLegacyErgebnisDaten;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

@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_KEY = -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 = "p22GruppenSortiertNachAnzahlSitze", elementClass = P22GruppeDocument.class, itemName = "p22Gruppe")
  public final List<P22GruppeDocument> p22GruppenSortiertNachAnzahlSitze;
  @DocumentRepeat(value = "GewaehlteProGruppe", elementClass = NlGewaehlteProGruppeDocument.class, itemName = "gewaehlte")
  public List<NlGewaehlteProGruppeDocument> gewaehlteProGruppeDokumente;

  private final NlCoreWahlCustomDataDocument wahlCustomDataDocument;
  private final NlErgebnisDaten2ErgebnisDokumentAdapter adapter;
  private final NlErgebnisDaten ergebnisDaten;
  private Messages messages;

  public P22ErgebnisDatenCustomDataDocument(WasElectContext wasElectContext, NlErgebnisDaten ergebnisDaten, NlCoreWahlCustomDataDocument wahlCustomDataDocument) {
    this.ergebnisDaten = ergebnisDaten;
    this.wahlCustomDataDocument = wahlCustomDataDocument;
    messages = wasElectContext.getMessages();

    OSVLegacyErgebnisDaten osvLegacyErgebnisDaten = OSVLegacyErgebnisDatenParser.parseOSVLegacyErgebnisDaten(ergebnisDaten.getErgebnisDaten().getWahl());

    adapter = new NlErgebnisDaten2ErgebnisDokumentAdapter(osvLegacyErgebnisDaten, ergebnisDaten.getKonflikte());
    this.anzahlSitze = adapter.getAnzahlSitzeZuVerteilenBeiErsterZuteilung();
    this.vorzugsschwelleInProzent = adapter.getVorzugsschwelleInProzent();

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

    List<PolitischeGruppe> politischeGruppenSortiertNachPosition = ergebnisDaten.getPolitischeGruppenSortiertNachPosition();
    for (PolitischeGruppe gruppe : politischeGruppenSortiertNachPosition) {
      Long stimmenzahl = ergebnisDaten.getListenDerGruppe(gruppe).stream()
          .map(l -> ergebnisDaten.getSummeListeListenstimmen(l.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity(), Stimmabgabeart.GESAMT, l)).reduce(Long::sum).orElse(0L);
      p22GruppenSortiertNachOrdnungszahl
          .add(new P22GruppeDocument(gruppe, ergebnisDaten.getListenDerGruppe(gruppe), adapter, gesamteSitzenzahlProGruppe.getOrDefault(gruppe.getId(), 0L), stimmenzahl));
    }

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

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

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

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

    dhondt = new NlDHondtQuotientPolitischeGruppeTabelleDocument(ergebnisDaten, p22GruppenSortiertNachOrdnungszahl, adapter);

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

    erzeugeGruppenDokumente(politischeGruppenSortiertNachPosition, wasElectContext.getStimmzettelFormatter(), gesamteSitzenzahlProGruppe);

    gewaehlteKandidatenAlphabetisch = getGewaehlteInAlphabetischerReihenfolge();

    besonderheiten = adapter.getBesonderheiten().stream()
            .map(b -> getLokalisierteBesonderheitMessage(b, getKandidaturnameFormatter(wasElectContext), ergebnisDaten.getErgebnisDaten().getKandidaturen()))
        .collect(Collectors.toList());

    besonderheiten.add(getMehrheitssicherungsklausel());

    listenerschoepfungenVonGruppen = getVonListenerschoepfungBeiOberverteilungBetroffeneGruppen();
  }

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

  private P22ErgebnisDatenCustomDataDocument() {
    this.wahlteiler = "";
    BigDecimal emptyDecimal = new BigDecimal("0");

    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 = Collections.emptyList();

    adapter = NlErgebnisDaten2ErgebnisDokumentAdapter.EMPTY_OBJECT;
    wahlCustomDataDocument = null;
    this.ergebnisDaten = null;
    this.besonderheiten = Collections.emptyList();
    p22GruppenSortiertNachAnzahlStimmen = Collections.emptyList();
    p22GruppenSortiertNachAnzahlSitze = Collections.emptyList();
    listenerschoepfungenVonGruppen = Collections.emptyList();
  }

  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(Collectors.toList());
  }

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

  private void erzeugeGruppenDokumente(List<PolitischeGruppe> politischeGruppenSortiertNachPosition, StimmzettelFormatter stimmzettelFormatter, Map<Long, Long> gesamteSitzenzahlProGruppe) {
    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getErgebnisDaten().getEntityAufStimmzettelDaten();
    for (PolitischeGruppe politischeGruppe : politischeGruppenSortiertNachPosition) {
      GruppePK gruppePk = politischeGruppe.getPk();
      GruppeDokument gruppeDokument = new GruppeDokument(politischeGruppe);
      Collection<Liste> listenDerGruppe = ergebnisDaten.getListenDerGruppe(politischeGruppe);
      NlPolitischeGruppeArt nlPolitischeGruppeArt = NlPolitischeGruppe.bestimmeEinreichungsart(listenDerGruppe, entityAufStimmzettelDaten);

      if (nlPolitischeGruppeArt.isListengruppe()) {
        Map<Integer, List<Liste>> sortiertesStelMap = createListenSortiertNachGebietsnummerProStelMap(listenDerGruppe, entityAufStimmzettelDaten);
        gruppenUnterverteilungen.add(createGruppeUnterverteilungenDokument(sortiertesStelMap, gruppeDokument, gruppePk));
        kandidatenStimmenProListeDokumente.addAll(createKandidatStimmenProWahlbezirkDokumenteFuerListengruppe(sortiertesStelMap, gruppeDokument, ergebnisDaten, stimmzettelFormatter, gruppePk));
      } else if (nlPolitischeGruppeArt.isIdentischeListe()) {
        Optional<Integer> stelnummer = listenDerGruppe.stream().findAny().flatMap(NlListe::getStelNummerWas);
        if (stelnummer.isPresent()) {
          String stelKey = NlListe.createStelKey(gruppePk, stelnummer.get());
          kandidatenStimmenProListeDokumente.add(new NlStelKandidatenStimmenProListeDocument(gruppeDokument, sortListeNachGebietsnummer(listenDerGruppe), ergebnisDaten, stimmzettelFormatter,
              adapter::isMitVorzugsstimmenGewaehlt, adapter.isMitGeaenderterReihenfolge(stelKey), stelnummer.get(), true));
        } else {
          LOG.error("Folgende Gruppe Identischer Liste besitzt keine Stelnummer : {}", politischeGruppe);
        }
      } else if (nlPolitischeGruppeArt.isEinzelliste()) {
        Liste einzelneListe = listenDerGruppe.stream().findAny().orElseThrow(NullPointerException::new);
        kandidatenStimmenProListeDokumente.add(new NlListeKandidatenStimmenProListeDocument(gruppeDokument, einzelneListe, adapter.isMitGeaenderterReihenfolge(einzelneListe.getPk()), ergebnisDaten,
            stimmzettelFormatter, adapter::isMitVorzugsstimmenGewaehlt));
      } else {
        LOG.error("Unbekannte Art von Politische Gruppe: {} [{}] ", nlPolitischeGruppeArt, politischeGruppe);
      }
    }

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

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

  }

  private List<AbstractNlKandidatenStimmenProListeDocument> createKandidatStimmenProWahlbezirkDokumenteFuerListengruppe(Map<Integer, List<Liste>> sortiertesStelMap, GruppeDokument gruppe,
      NlErgebnisDaten ergebnisDaten, StimmzettelFormatter stimmzettelFormatter, 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, List<Liste>> stelListEntry : sortiertesStelMap.entrySet()) {
      Integer stelnummer = stelListEntry.getKey();
      List<Liste> listenNachGebietsnummer = stelListEntry.getValue();

      if (stelnummer.equals(LISTE_OHNE_STEL_KEY)) {
        listenNachGebietsnummer.stream()
            .map(
                l -> new NlListeKandidatenStimmenProListeDocument(gruppe, l, adapter.isMitGeaenderterReihenfolge(l.getPk()), ergebnisDaten, stimmzettelFormatter, adapter::isMitVorzugsstimmenGewaehlt))
            .forEach(einzelneListen::add);
      } else {
        String stelKey = NlListe.createStelKey(gruppePK, stelnummer);
        result.add(new NlStelKandidatenStimmenProListeDocument(gruppe, listenNachGebietsnummer, ergebnisDaten, stimmzettelFormatter, adapter::isMitVorzugsstimmenGewaehlt,
            adapter.isMitGeaenderterReihenfolge(stelKey), stelnummer, false));
      }
    }

    result.addAll(einzelneListen);

    return result;
  }

  private String getLokalisierteBesonderheitMessage(BesonderheitBeiNominierungDerGewaehlten besonderheit,
                                                    Function<Kandidatur, String> kandidaturnameFormatter,
                                                    List<Kandidatur> kandidaturen) {
    return messages.getMessageForMessageKey(besonderheit.getTyp().name(),
            getFormartierenPersonennamenFuerPersonendatenPk(besonderheit, kandidaturen, kandidaturnameFormatter));
  }

  private Object[] getFormartierenPersonennamenFuerPersonendatenPk(BesonderheitBeiNominierungDerGewaehlten besonderheit,
                                                                   List<Kandidatur> 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 = ergebnisDaten.getPolitischeGruppenSortiertNachPosition().stream().collect(Collectors.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 = messages.getMessageForMessageKey("Result_User_List_0_HasAbsoluteMajorityOfVotesAndReceivesSeatFromList_1", gewinner, verlierer);
    }
    return result;
  }

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

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

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

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

  @DocumentMethod
  public boolean hatListenMitGeaenderterReihenfolge(@DocumentParam("gruppe") Object value) {
    return getPolitischeGruppe(value).map(ergebnisDaten::getListenDerGruppe).orElse(Collections.emptyList()).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 gruppe = getPolitischeGruppe(value).orElse(null);
    if (gruppe == null) {
      return result;
    }
    List<Liste> listenDerGruppe = ergebnisDaten.getListenDerGruppe(gruppe);

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

    result += vonListenerschoepfungBeiUnterverteilungBetroffeneListenInGruppe.entrySet().stream().map(e -> {
      String kieskringString = listenDerGruppe.stream().filter(l -> l.getPk().equals(e.getKey())).map(l -> "kieskring " + l.getGebiet().getNummer()).findAny().orElse("-1");
      return getListenerschoepfungMessage(kieskringString, e.getValue());
    }).collect(Collectors.joining("\n"));

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

    return result;
  }

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

  private String getListenerschoepfungMessage(String lijstOderKieskringString, Integer anzahlSitze) {
    if (anzahlSitze == 1) {
      return messages.getMessageForMessageKey("Result_User_ListExhaustionFor_0_SeatLostToOtherList", lijstOderKieskringString);
    } else {
      return messages.getMessageForMessageKey("Result_User_ListExhaustionEKFor_0_1_SeatsLostToOtherList", lijstOderKieskringString, 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) {
    return getAdapter().isMitVorzugsstimmenGewaehlt(getKandidatur(value).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.ergebnisDaten.getPolitischeGruppenSortiertNachPosition().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 GruppeUnterverteilungenDokument createGruppeUnterverteilungenDokument(Map<Integer, List<Liste>> stelMap, GruppeDokument gruppeDokument, GruppePK gruppePk) {
    return new GruppeUnterverteilungenDokument(adapter, this.ergebnisDaten, gruppeDokument, gruppePk, stelMap);
  }

  protected Map<Integer, List<Liste>> createListenSortiertNachGebietsnummerProStelMap(Collection<Liste> listen, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    Map<Integer, List<Liste>> stelMap = listen.stream().collect(Collectors.groupingBy(l -> {

      if (NlListe.getStelInformationen(l, listen, entityAufStimmzettelDaten).getNlPolitischeGruppeArt().isEinzelliste()) {
        return LISTE_OHNE_STEL_KEY;
      } else {
        Optional<Integer> stelNummer = NlListe.getStelNummerWas(l);
        if (stelNummer.isPresent()) {
          return stelNummer.get();
        } else {
          LOG.info("Die folgende Liste hat keine Stelnummer, obwohl sie als \"einzel Liste\" nicht gekennzeichnet ist: " + l);
          return LISTE_OHNE_STEL_KEY;
        }
      }
    }));

    stelMap.forEach((key, value) -> stelMap.replace(key, sortListeNachGebietsnummer(value)));

    return new TreeMap<>(stelMap);//sortiert die Stelnummer-keys
  }

  private List<Liste> sortListeNachGebietsnummer(Collection<Liste> values) {
    return values.stream().sorted((l1, l2) -> GebietByNummerComparator.compareNummer(l1.getGebiet().getNummer(), l2.getGebiet().getNummer())).collect(Collectors.toList());
  }

  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 ergebnisDaten.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(Collectors.toList());
  }

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

}
