
package de.ivu.elect.business.ergebnis.control;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.ToLongFunction;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.NlGebiet;
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.NlPolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppeArt;
import de.ivu.elect.business.gruppe.entity.NlSystemGruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.internet.control.HashablePredicate;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
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.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

public class NlErgebnisDaten {

  private static final HashablePredicate<Gebiet> GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN = GebietUndStimmabgabeart.FALSE_TEST;

  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_URNENWAHL_GEMEINDE = new HashablePredicate<>() {

    @Override
    protected int hashCodeInternal() {
      return 0;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return NlGebiet.isUrnenwahlgebietGemeinde(gebiet);
    }
  };

  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_BRIEFWAHL_GEMEINDE = new HashablePredicate<>() {

    @Override
    protected int hashCodeInternal() {
      return 0;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return NlGebiet.isBriefwahlgebietGemeinde(gebiet);
    }
  };

  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_BRIEFWAHL_AUSLAND = new HashablePredicate<>() {

    @Override
    protected int hashCodeInternal() {
      return 1188;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return NlGebiet.isBriefwahlgebietAusland(gebiet);
    }
  };

  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_GEMEINDE = new HashablePredicate<>() {

    @Override
    protected int hashCodeInternal() {
      return 0;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return ERFASSUNGSGEBIET_URNENWAHL_GEMEINDE.test(gebiet) || ERFASSUNGSGEBIET_BRIEFWAHL_GEMEINDE.test(gebiet);
    }
  };

  private final ErgebnisDaten ergebnisDaten;

  private final AbstractErgebnisDatenWahlberechtigte ergebnisDatenWahlberechtigte;

  private final ErgebnisDatenListenkandidaturen ergebnisDatenListenkandidaturen;

  private final NlStimmenGewichtetKumulierer nlStimmenGewichtetKumulierer;

  private NlErgebnisDatenWaehler ergebnisDatenWaehler;

  private NlErgebnisDatenGueltige ergebnisDatenGueltige;

  private NlErgebnisDatenStimmzettel ergebnisDatenStimmzettel;

  private Map<Long, Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietPerId;

  private Map<String, Gruppe> gruppePerSchluessel;

  private List<PolitischeGruppe> sortiertePolitischeGruppeListe;

  public NlErgebnisDaten(ErgebnisDaten ergebnisDaten) {
    super();

    this.ergebnisDaten = ergebnisDaten;
    this.ergebnisDatenWahlberechtigte = ergebnisDaten.getErgebnisDatenWahlberechtigte();
    this.ergebnisDatenListenkandidaturen = ergebnisDaten.getErgebnisDatenListenkandidaturen();

    this.nlStimmenGewichtetKumulierer = new NlStimmenGewichtetKumulierer(ergebnisDaten.getWahl(), ergebnisDaten::getGebiete);
  }

  public Wahldurchfuehrung getWahl() {
    return getErgebnisDaten().getWahl();
  }

  public NlWahlart getNlWahlart() {
    return NlWahlart.valueOf(getWahl());
  }

  public EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
    return getErgebnisDaten().getEntityAufStimmzettelDaten();
  }

  public long getSummeWahlberechtigteGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWahlberechtigteGesamt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWahlberechtigteGesamtNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    long result = getSummeWahlberechtigteGesamtNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet) + getSummeWahlberechtigteGesamtNurBriefwahlGemeinde(wahldurchfuehrungsgebiet);

    if (result < 1L && wahldurchfuehrungsgebiet != null && wahldurchfuehrungsgebiet.isWurzelgebiet()) { // ELECT-15810
      result = wahldurchfuehrungsgebiet.getAnzahlWahlberechtigteOhneWahlscheinNotNull();
    }

    return result;
  }

  public long getSummeWahlberechtigteGesamtNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  // JA, auch bei BRIEFWAHL!
  public long getSummeWahlberechtigteGesamtNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  // JA, auch bei BRIEFWAHL!
  public long getSummeWahlberechtigteGesamtNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet));
  }

  public long getSummeWahlberechtigteGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    long result = getErgebnisDatenWahlberechtigte().getSummeWahlberechtigteGesamt(gebietUndStimmabgabeart);

    if (result < 1L && Stimmabgabeart.GESAMT.equals(gebietUndStimmabgabeart.getStimmabgabeart())) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

      if (wahldurchfuehrungsgebiet != null && wahldurchfuehrungsgebiet.isWurzelgebiet()) { // ELECT-15810
        result = wahldurchfuehrungsgebiet.getAnzahlWahlberechtigteOhneWahlscheinNotNull();
      }
    }

    return result;
  }

  public long getSummeWaehlerOhneWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerOhneWahlschein(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerOhneWahlscheinNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerOhneWahlscheinNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet); // Urnenwahl-spezifisch...
  }

  public long getSummeWaehlerOhneWahlscheinNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerOhneWahlschein(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerOhneWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerOhneWahlschein(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerMitWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerMitWahlschein(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerMitWahlscheinNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitWahlscheinNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet); // Urnenwahl-spezifisch...
  }

  public long getSummeWaehlerMitWahlscheinNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitWahlschein(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerMitWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerMitWahlschein(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerMitVollmacht(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerMitVollmacht(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerMitVollmachtNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitVollmachtNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet); // Urnenwahl-spezifisch...
  }

  public long getSummeWaehlerMitVollmachtNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitVollmacht(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerMitVollmacht(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerMitVollmacht(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerGesamt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerGesamtNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamtNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet) + getSummeWaehlerGesamtNurBriefwahlGemeinde(wahldurchfuehrungsgebiet);
  }

  public long getSummeWaehlerGesamtNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamt(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerGesamtNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamt(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerGesamtNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamt(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerGesamt(gebietUndStimmabgabeart);
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet (== Summe der Listenstimmen aller Gruppen).
   *
   * @see NlErgebnisDatenGueltige#getSummeGueltigeListenstimmen
   */
  public long getSummeGebietListenstimmen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeGebietListenstimmen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet (== Summe der Listenstimmen aller Gruppen).
   *
   * @see NlErgebnisDatenGueltige#getSummeGueltigeListenstimmen
   */
  public long getSummeGebietListenstimmenNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmenNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet) + getSummeGebietListenstimmenNurBriefwahlGemeinde(wahldurchfuehrungsgebiet);
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet (== Summe der Listenstimmen aller Gruppen).
   *
   * @see NlErgebnisDatenGueltige#getSummeGueltigeListenstimmen
   */
  public long getSummeGebietListenstimmenNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmen(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet (== Summe der Listenstimmen aller Gruppen).
   *
   * @see NlErgebnisDatenGueltige#getSummeGueltigeListenstimmen
   */
  public long getSummeGebietListenstimmenNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmen(NlErgebnisDaten.createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet (== Summe der Listenstimmen aller Gruppen).
   *
   * @see NlErgebnisDatenGueltige#getSummeGueltigeListenstimmen
   */
  public long getSummeGebietListenstimmenNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmen(NlErgebnisDaten.createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet (== Summe der Listenstimmen aller Gruppen).
   *
   * @see NlErgebnisDatenGueltige#getSummeGueltigeListenstimmen
   */
  public long getSummeGebietListenstimmen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getSummeGebietListenstimmenGewichtet(gebietUndStimmabgabeart, ignored -> 1L);
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet gewichtet (== Summe der Listenstimmen aller Gruppen gewichtet).
   */
  public long getSummeGebietListenstimmenGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeGebietListenstimmenGewichtetMitVoteValues(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet gewichtet (== Summe der Listenstimmen aller Gruppen gewichtet).
   */
  public long getSummeGebietListenstimmenNurGesamtGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(wahldurchfuehrungsgebiet)
        + getSummeGebietListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(wahldurchfuehrungsgebiet);
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet gewichtet (== Summe der Listenstimmen aller Gruppen gewichtet).
   */
  public long getSummeGebietListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmenGewichtetMitVoteValues(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet gewichtet (== Summe der Listenstimmen aller Gruppen gewichtet).
   */
  public long getSummeGebietListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmenGewichtetMitVoteValues(NlErgebnisDaten.createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet gewichtet (== Summe der Listenstimmen aller Gruppen gewichtet).
   */
  public long getSummeGebietListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeGebietListenstimmenGewichtetMitVoteValues(NlErgebnisDaten.createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet));
  }

  /**
   * @return Summe aller erfassten Listenstimmen auf dem Gebiet gewichtet (== Summe der Listenstimmen aller Gruppen gewichtet).
   */
  public long getSummeGebietListenstimmenGewichtetMitVoteValues(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return nlStimmenGewichtetKumulierer.getSummeListenstimmenGewichtetMitVoteValues(gebietUndStimmabgabeart, this::getSummeGebietListenstimmenGewichtet);
  }

  private long getSummeGebietListenstimmenGewichtet(GebietUndStimmabgabeart gebietUndStimmabgabeart, ToLongFunction<Wahldurchfuehrungsgebiet> wichtungFct) {
    Long wichtung = wichtungFct.applyAsLong(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());

    long summeListenstimmen = getErgebnisDaten().getZweitstimmenAktuell(gebietUndStimmabgabeart).values().stream()
        .mapToLong(anzahlListenstimmen -> anzahlListenstimmen == null ? 0L : anzahlListenstimmen).sum();

    return wichtung * summeListenstimmen;
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe (== Summe der Listenstimmen aller Listen).
   */
  public long getSummeGruppeListenstimmen(PolitischeGruppe gruppe, Stimmabgabeart stimmabgabeart) {
    return getSummeGruppeListenstimmen(gruppe, (wahldurchfuehrungsgebiet, liste) -> getSummeListeListenstimmen(wahldurchfuehrungsgebiet, stimmabgabeart, liste));
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe (== Summe der Listenstimmen aller Listen).
   */
  public long getSummeGruppeListenstimmenNurGesamtGemeinde(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmenNurUrnenwahlGemeinde(gruppe) + getSummeGruppeListenstimmenNurBriefwahlGemeinde(gruppe);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe (== Summe der Listenstimmen aller Listen).
   */
  public long getSummeGruppeListenstimmenNurUrnenwahlGemeinde(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmen(gruppe, this::getSummeListeListenstimmenNurUrnenwahlGemeinde);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe (== Summe der Listenstimmen aller Listen).
   */
  public long getSummeGruppeListenstimmenNurBriefwahlGemeinde(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmen(gruppe, this::getSummeListeListenstimmenNurBriefwahlGemeinde);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe (== Summe der Listenstimmen aller Listen).
   */
  public long getSummeGruppeListenstimmenNurBriefwahlAusland(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmen(gruppe, this::getSummeListeListenstimmenNurBriefwahlAusland);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe gewichtet (== Summe der Listenstimmen aller Listen gewichtet).
   */
  public long getSummeGruppeListenstimmenGewichtetMitVoteValues(PolitischeGruppe gruppe, Stimmabgabeart stimmabgabeart) {
    return getSummeGruppeListenstimmen(gruppe, (wahldurchfuehrungsgebiet, liste) -> getSummeListeListenstimmenGewichtetMitVoteValues(wahldurchfuehrungsgebiet, stimmabgabeart, liste));
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe gewichtet (== Summe der Listenstimmen aller Listen gewichtet).
   */
  public long getSummeGruppeListenstimmenNurGesamtGemeindeGewichtetMitVoteValues(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(gruppe) + getSummeGruppeListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(gruppe);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe gewichtet (== Summe der Listenstimmen aller Listen gewichtet).
   */
  public long getSummeGruppeListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmen(gruppe, this::getSummeListeListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe gewichtet (== Summe der Listenstimmen aller Listen gewichtet).
   */
  public long getSummeGruppeListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmen(gruppe, this::getSummeListeListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues);
  }

  /**
   * @return Summe aller erfassten Listenstimmen für die Gruppe gewichtet (== Summe der Listenstimmen aller Listen gewichtet).
   */
  public long getSummeGruppeListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues(PolitischeGruppe gruppe) {
    return getSummeGruppeListenstimmen(gruppe, this::getSummeListeListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues);
  }

  private long getSummeGruppeListenstimmen(PolitischeGruppe gruppe, BiFunction<Wahldurchfuehrungsgebiet, Liste, Long> summeListeListenstimmenFct) {
    return getListenDerGruppe(gruppe).stream().mapToLong(liste -> summeListeListenstimmenFct.apply(castTo(liste.getGebiet(), Wahldurchfuehrungsgebiet.class), liste)).sum();
  }

  public long getSummeListeListenstimmen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, Liste liste) {
    return getSummeListeListenstimmen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart), liste);
  }

  public long getSummeListeListenstimmenNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmenNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet, liste) + getSummeListeListenstimmenNurBriefwahlGemeinde(wahldurchfuehrungsgebiet, liste);
  }

  public long getSummeListeListenstimmenNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmen(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmen(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmen(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmen(GebietUndStimmabgabeart gebietUndStimmabgabeart, Liste liste) {
    return getSummeListeListenstimmenGewichtet(gebietUndStimmabgabeart, liste, ignored -> 1L);
  }

  public long getSummeListeListenstimmenGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, Liste liste) {
    return getSummeListeListenstimmenGewichtetMitVoteValues(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart), liste);
  }

  public long getSummeListeListenstimmenNurGesamtGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(wahldurchfuehrungsgebiet, liste)
        + getSummeListeListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(wahldurchfuehrungsgebiet, liste);
  }

  public long getSummeListeListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmenGewichtetMitVoteValues(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmenGewichtetMitVoteValues(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmenGewichtetMitVoteValues(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenGewichtetMitVoteValues(GebietUndStimmabgabeart gebietUndStimmabgabeart, Liste liste) {
    return nlStimmenGewichtetKumulierer.getSummeListenstimmenGewichtetMitVoteValues(gebietUndStimmabgabeart,
        (voteValueGebietUndStimmabgabeart, voteValueFct) -> getSummeListeListenstimmenGewichtet(voteValueGebietUndStimmabgabeart, liste, voteValueFct));
  }

  private long getSummeListeListenstimmenGewichtet(GebietUndStimmabgabeart gebietUndStimmabgabeart, Liste liste, ToLongFunction<Wahldurchfuehrungsgebiet> wichtungFct) {
    Long wichtung = wichtungFct.applyAsLong(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());

    Long summeListenstimmen = getErgebnisDaten().getZweitstimmenAktuell(liste.getGruppe(), gebietUndStimmabgabeart);

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

    return wichtung * summeListenstimmen;
  }

  public long getSummeListenkandidaturListenstimmen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet, listenkandidatur)
        + getSummeListenkandidaturListenstimmenNurBriefwahlGemeinde(wahldurchfuehrungsgebiet, listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmen(GebietUndStimmabgabeart gebietUndStimmabgabeart, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenGewichtet(gebietUndStimmabgabeart, listenkandidatur, ignored -> 1L);
  }

  public long getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurGesamtGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(wahldurchfuehrungsgebiet, listenkandidatur)
        + getSummeListenkandidaturListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(wahldurchfuehrungsgebiet, listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(GebietUndStimmabgabeart gebietUndStimmabgabeart, Listenkandidatur listenkandidatur) {
    return nlStimmenGewichtetKumulierer.getSummeListenstimmenGewichtetMitVoteValues(gebietUndStimmabgabeart,
        (voteValueGebietUndStimmabgabeart, voteValueFct) -> getSummeListenkandidaturListenstimmenGewichtet(voteValueGebietUndStimmabgabeart, listenkandidatur, voteValueFct));
  }

  private long getSummeListenkandidaturListenstimmenGewichtet(GebietUndStimmabgabeart gebietUndStimmabgabeart, Listenkandidatur listenkandidatur,
      ToLongFunction<Wahldurchfuehrungsgebiet> wichtungFct) {
    Long wichtung = wichtungFct.applyAsLong(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());

    long summeListenstimmen = getErgebnisDatenListenkandidaturen().getStimmenFuerListenkandidatur(listenkandidatur, gebietUndStimmabgabeart);

    return wichtung * summeListenstimmen;
  }

  public long getSummeKandidatListenstimmen(Stimmabgabeart stimmabgabeart, Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmen(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), stimmabgabeart, listenkandidatur));
  }

  public long getSummeKandidatListenstimmenNurGesamtGemeinde(Listenkandidatur any) {
    return getSummeKandidatListenstimmenNurUrnenwahlGemeinde(any) + getSummeKandidatListenstimmenNurBriefwahlGemeinde(any);
  }

  public long getSummeKandidatListenstimmenNurUrnenwahlGemeinde(Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenNurUrnenwahlGemeinde(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), listenkandidatur));
  }

  public long getSummeKandidatListenstimmenNurBriefwahlGemeinde(Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenNurBriefwahlGemeinde(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), listenkandidatur));
  }

  public long getSummeKandidatListenstimmenNurBriefwahlAusland(Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenNurBriefwahlAusland(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), listenkandidatur));
  }

  public long getSummeKandidatListenstimmenGewichtetMitVoteValues(Stimmabgabeart stimmabgabeart, Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenGewichtetMitVoteValues(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), stimmabgabeart, listenkandidatur));
  }

  public long getSummeKandidatListenstimmenNurGesamtGemeindeGewichtetMitVoteValues(Listenkandidatur any) {
    return getSummeKandidatListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(any) + getSummeKandidatListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(any);
  }

  public long getSummeKandidatListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenNurUrnenwahlGemeindeGewichtetMitVoteValues(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), listenkandidatur));
  }

  public long getSummeKandidatListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenNurBriefwahlGemeindeGewichtetMitVoteValues(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), listenkandidatur));
  }

  public long getSummeKandidatListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues(Listenkandidatur any) {
    return getSummeKandidatListenstimmen(any,
        listenkandidatur -> getSummeListenkandidaturListenstimmenNurBriefwahlAuslandGewichtetMitVoteValues(castTo(listenkandidatur.getGebiet(), Wahldurchfuehrungsgebiet.class), listenkandidatur));
  }

  private long getSummeKandidatListenstimmen(Listenkandidatur any, ToLongFunction< ? super Listenkandidatur> summeListenstimmenFct) {
    return collectKandidatListenkandidaturen(any).stream().mapToLong(summeListenstimmenFct).sum();
  }

  public Set<Listenkandidatur> collectKandidatListenkandidaturen(Listenkandidatur listenkandidatur) {
    Set<Listenkandidatur> result = new HashSet<>();

    if (listenkandidatur != null) {
      result.add(listenkandidatur);
      listenkandidatur.getIdentischVerknuepfteListenkandidaturen().forEach(result::add);
    }

    return result;
  }

  public AbstractErgebnisDatenWahlberechtigte getErgebnisDatenWahlberechtigte() {
    return ergebnisDatenWahlberechtigte;
  }

  public NlErgebnisDatenWaehler getErgebnisDatenWaehler() {
    if (ergebnisDatenWaehler == null) {
      ergebnisDatenWaehler = new NlErgebnisDatenWaehler(this);
    }

    return ergebnisDatenWaehler;
  }

  public NlErgebnisDatenGueltige getErgebnisDatenGueltige() {
    if (ergebnisDatenGueltige == null) {
      ergebnisDatenGueltige = new NlErgebnisDatenGueltige(this, getErgebnisDaten().getErgebnisDatenGueltige());
    }

    return ergebnisDatenGueltige;
  }

  public NlErgebnisDatenStimmzettel getErgebnisDatenStimmzettel() {
    if (ergebnisDatenStimmzettel == null) {
      ergebnisDatenStimmzettel = new NlErgebnisDatenStimmzettel(this);
    }

    return ergebnisDatenStimmzettel;
  }

  private ErgebnisDatenListenkandidaturen getErgebnisDatenListenkandidaturen() {
    return ergebnisDatenListenkandidaturen;
  }

  public List<Sitze> getSitze() {
    return getErgebnisDaten().getSitze();
  }

  public Sitze getSitzeFuerListe(Liste liste) {
    return getErgebnisDaten().getSitzeFuerListe(liste);
  }

  public List<Gewaehlt> getGewaehlte() {
    return getErgebnisDaten().getGewaehlte();
  }

  public Gewaehlt getGewaehltFuerListenkandidatur(Listenkandidatur listenkandidatur) {
    return getErgebnisDaten().getGewaehlteFuerKandidatur(listenkandidatur);
  }

  public List<ErgebnisKonflikt> getKonflikte() {
    return getErgebnisDaten().getKonflikte().stream().filter(k -> k.getCustomData() instanceof OsvLegacyKonfliktCustomData).collect(toList());
  }

  private ErgebnisDaten getErgebnisDaten() {
    return ergebnisDaten;
  }

  protected StimmenPool getStimmen() {
    return getErgebnisDaten().getStimmen();
  }

  protected StimmenKumulierer getStimmenKumulierer() {
    return getErgebnisDaten().getStimmenKumulierer();
  }

  protected Supplier<Set<Gebiet>> collectGebieteErfasstFct() {
    return getErgebnisDaten()::getErfassteGebiete;
  }

  protected Function<Wahldurchfuehrungsgebiet, List<Wahldurchfuehrungsgebiet>> collectGebieteUntergeordnetFct() {
    return getErgebnisDaten()::getUntergeordneteGebiete;
  }

  public Optional<Wahldurchfuehrungsgebiet> findWahldurchfuehrungsgebietById(Long id) {
    if (id != null) {
      if (wahldurchfuehrungsgebietPerId == null) {
        wahldurchfuehrungsgebietPerId = getGebiete().stream().collect(toMap(Gebiet::getId, Function.identity()));
      }

      return Optional.ofNullable(wahldurchfuehrungsgebietPerId.get(id));
    }

    return Optional.empty();
  }

  public Optional<Gruppe> findGruppeBySchluessel(String schluessel) {
    if (!StringUtils.isBlank(schluessel)) {
      if (gruppePerSchluessel == null) {
        gruppePerSchluessel = getGruppen().stream().collect(toMap(Gruppe::getSchluessel, Function.identity()));
      }

      return Optional.ofNullable(gruppePerSchluessel.get(schluessel));
    }

    return Optional.empty();
  }

  public Optional<Gruppe> findSystemGruppe(NlSystemGruppe expected) {
    return getErgebnisDaten().getSystemgruppen().stream().filter(gruppe -> expected.equalsSystemGruppe(gruppe, getErgebnisDaten().getSystemGruppeEigenschaften())).findFirst();
  }

  public List<PolitischeGruppe> getPolitischeGruppenSortiertNachPosition() {
    if (sortiertePolitischeGruppeListe == null) {
      sortiertePolitischeGruppeListe = getGruppen().stream().sorted(Comparator.comparingLong(Gruppe::getRelativePosition)).filter(AbstractGruppe::isPolitischeGruppe)
          .map(g -> g.castTo(PolitischeGruppe.class).getEntity()).collect(toList());
    }

    return sortiertePolitischeGruppeListe;
  }

  private List<Gruppe> getGruppen() {
    return getErgebnisDaten().getGruppen();
  }

  public List<Wahldurchfuehrungsgebiet> getGebiete() {
    return getErgebnisDaten().getGebiete();
  }

  public List<Wahldurchfuehrungsgebiet> getVoteValueGebiete() {
    return getNlStimmenGewichtetKumulierer().getVoteValueGebiete();
  }

  public NlStimmenGewichtetKumulierer getNlStimmenGewichtetKumulierer() {
    return nlStimmenGewichtetKumulierer;
  }

  public List<Listenkandidatur> getListenkandidaturen() {
    return getErgebnisDaten().getKandidaturen().stream().filter(Kandidatur::isListenkandidatur).map(kandidatur -> castTo(kandidatur, Listenkandidatur.class)).collect(toList());
  }

  public List<Liste> getListenDerGruppe(Gruppe gruppe) {
    return getErgebnisDaten().getListenDerGruppe(gruppe);
  }

  public List<Liste> getListen() {
    return getErgebnisDaten().getListen();
  }

  public NlPolitischeGruppeArt getNlPolitischeGruppeArt(Gruppe gruppe) {
    if (!gruppe.isInstanceOf(PolitischeGruppe.class)) {
      return NlPolitischeGruppeArt.UNBEKANNT;
    }

    return NlPolitischeGruppe.bestimmeEinreichungsart(getListenDerGruppe(gruppe), getEntityAufStimmzettelDaten());
  }

  static GebietUndStimmabgabeart createGebietUndStimmabgabeartNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.URNE, GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN, ERFASSUNGSGEBIET_URNENWAHL_GEMEINDE);
  }

  static GebietUndStimmabgabeart createGebietUndStimmabgabeartNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.BRIEF, GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN, ERFASSUNGSGEBIET_BRIEFWAHL_GEMEINDE);
  }

  static GebietUndStimmabgabeart createGebietUndStimmabgabeartNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.BRIEF, GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN, ERFASSUNGSGEBIET_BRIEFWAHL_AUSLAND);
  }
}
