
package de.ivu.elect.business.stimmzettel.boundary;

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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;

import javax.annotation.Nullable;
import javax.inject.Inject;

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

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturByRelativePositionComparator;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPositionFormatter;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPositionFormatterKonfiguration;
import de.ivu.elect.business.pruefung.boundary.ZulassungPruefer;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.control.WahlvorschlagReihenfolgeVorsortierenComparator;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPositionFormatter;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPositionFormatterKonfiguration;
import de.ivu.elect.util.TriFunction;

public class EntityAufStimmzettelDatenStimmzettelPositionBerechnung implements Serializable {

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

  private static final long serialVersionUID = 1L;

  @Inject
  private transient AnzahlStimmenProWaehlerBestimmer anzahlStimmenProWaehlerBestimmer;

  public EntityAufStimmzettelDatenStimmzettelPositionBerechnung() {
    super();
  }

  public WahlvorschlagStimmzettelPosition calculateWahlvorschlagStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, @Nullable Gebiet gebiet,
      @Nullable Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag == null || wahlvorschlag.getWahl() == null) {
      WahlvorschlagStimmzettelPosition result = emptyWahlvorschlagStimmzettelPosition();
      info("Betrachteter Wahlvorschlag ist NICHT gültig", result, wahlvorschlag);

      return result;
    }

    Gebiet gebietBetrachtet = gebiet;

    if (gebietBetrachtet == null) {
      gebietBetrachtet = wahlvorschlag.getGebiet(); // Alles wie vorher...
    }

    // (!)  Ungeprüfte sollen erstmal eine Stimmzettelposition haben, bis sie explizit abgelehnt werden, bei Änderungen UND informierem(!)
    // ELECT-12000 (Stimmzettelposition anzeigen)
    // ELECT-9421, ungeprüfte Wahlvorschläge landen nicht auf dem Stimmzettel...
    //    if (wahlvorschlag.isUngeprueft()) {
    //      return emptyWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag);
    //    }

    // Nicht zugelassene Wahlvorschläge landen nicht auf dem Stimmzettel...
    if (wahlvorschlag.isNichtZugelassenImSinneAbgelehnt()) {
      WahlvorschlagStimmzettelPosition result = emptyWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag);
      info("Betrachteter Wahlvorschlag ist NICHT zugelassen (abgelehnt)", result, wahlvorschlag);

      return result;
    }

    // Nicht zulassungswürdige Wahlvorschläge landen nicht auf dem Stimmzettel...
    //    if (!entityAufStimmzettelDaten.getZulassungPruefer().isZulassenErlaubt(wahlvorschlag)) {
    //      return emptyWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag);
    //    }

    // Stimmzettelpositionen oberhalb des Gebietes des Wahlvorschlages ist nicht sinnvoll...
    if (!gebietBetrachtet.equals(wahlvorschlag.getGebiet()) && gebietBetrachtet.isInGebietshierarchieUebergeordnetesGebietVon(wahlvorschlag.getGebiet())) {
      WahlvorschlagStimmzettelPosition result = emptyWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag);
      info("Betrachtetes Gebiet ist in übergeordneter Gebietshierarchie des betrachteten Wahlvorschlages", result, wahlvorschlag);

      return result;
    }

    // Wenn Direktkandidat und falls es einen Wahlvorschlag (Liste) derselben Gruppe in einem höheren Gebiet gibt, nimm die Stimmzettelposition von diesem Wahlvorschlag (konkret: Position des Direktkandidaten = Position der Liste)
    if (wahlvorschlag.isDirektkandidat()) {
      Long berechnetePosition = wahlvorschlag.getGruppeImGebiet().getVorgegebenePosition();

      if (berechnetePosition != null) {
        WahlvorschlagStimmzettelPosition result = createWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag, berechnetePosition);
        info("Betrachteter Wahlvorschlag hat vorgegebene Position an GruppeImGebiet (1)", result, wahlvorschlag);

        return result;
      }

      Liste listeInGebietshierarchieUebergeordnet = bestimmeListeImGleichenOderHoeremGebiet(entityAufStimmzettelDaten, wahlvorschlag);

      if (listeInGebietshierarchieUebergeordnet != null) {
        WahlvorschlagStimmzettelPosition result = listeInGebietshierarchieUebergeordnet.getStimmzettelPosition(gebietBetrachtet, entityAufStimmzettelDaten);

        if (result != null && result.hasPosition()) { // WASDE-2152
          info("Betrachteter Direktkandidat hat Liste MIT Stimmzettelposition in übergeordneter Gebietshierarchie", result, wahlvorschlag);

          return result;
        } else {
          LOG.warn("Betrachteter Direktkandidat hat Liste OHNE Stimmzettelposition in übergeordneter Gebietshierarchie [kennwort = {}, wahlvorschlag = {}]", wahlvorschlag.getKurznameOderKennwort(),
              wahlvorschlag);
        }
      }
    } else {
      if (!wahlvorschlag.getWahl().isWahlMitMehrerenListenGebietsarten()) {
        Long berechnetePosition = wahlvorschlag.getGruppeImGebiet().getVorgegebenePosition();

        if (berechnetePosition != null) {
          WahlvorschlagStimmzettelPosition result = createWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag, berechnetePosition);
          info("Betrachteter Wahlvorschlag hat vorgegebene Position an GruppeImGebiet (2)", result, wahlvorschlag);

          return result;
        }
      }
    }

    Gebiet gebietStimmzettelPositionBerechenbar = wahlvorschlag.getGebiet();

    if (wahlvorschlag.isListe() && wahlvorschlag.getWahl().isWahlMitMehrerenListenGebietsarten()) {
      Liste liste = castTo(wahlvorschlag, Liste.class);

      gebietStimmzettelPositionBerechenbar = liste.getGebietStimmzettelPositionBerechenbar(gebietBetrachtet);

      if (gebietStimmzettelPositionBerechenbar == null) {
        gebietStimmzettelPositionBerechenbar = wahlvorschlag.getGebiet();
      }

      // Wenn es Listen unterhalb des zu betrachtenden Gebietes gibt...
      if (existsListeInGebietshierarchieUntergeordnet(entityAufStimmzettelDaten, gebietStimmzettelPositionBerechenbar, wahlvorschlag)) {
        WahlvorschlagStimmzettelPosition result = emptyWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag);
        info("Betrachtete Liste hat Liste in untergeordneter Gebietshierarchie", result, wahlvorschlag);

        return result;
      }
    }

    if (gebietStimmzettelPositionBerechenbar.equals(wahlvorschlag.getGebiet())) {
      // Wenn Position fixiert, dann diese zurückgeben, aber nur, wenn betrachtendes Gebiet dem Gebiet des Wahlvorschlages entspricht...Destatis Europawahl...
      Long berechnetePosition = wahlvorschlag.getGruppeImGebiet().getVorgegebenePosition();

      if (berechnetePosition != null) {
        WahlvorschlagStimmzettelPosition result = createWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag, berechnetePosition);
        info("Betrachteter Wahlvorschlag hat vorgegebene Position an GruppeImGebiet (3)", result, wahlvorschlag);

        return result;
      }
    }

    if (!gebietStimmzettelPositionBerechenbar.equals(wahlvorschlag.getGebiet())) {
      GruppeImGebiet gruppeImGebietStimmzettelPositionBerechenbar = WahlvorschlagReihenfolgeVorsortierenComparator.bestimmeGruppeImGebietFuerGebiet(wahlvorschlag,
          gebietStimmzettelPositionBerechenbar);

      // Wenn Position fixiert, dann diese zurückgeben
      Long berechnetePosition = gruppeImGebietStimmzettelPositionBerechenbar.getVorgegebenePosition();

      if (berechnetePosition != null) {
        WahlvorschlagStimmzettelPosition result = createWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag, berechnetePosition);
        info("Betrachteter Wahlvorschlag hat vorgegebene Position an GruppeImGebiet (4)", result, wahlvorschlag);

        return result;
      }
    }

    List<Wahlvorschlag> wahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnetSortedByDefault = collectWahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnetSortedByDefault(
        entityAufStimmzettelDaten, gebietStimmzettelPositionBerechenbar, wahlvorschlag);

    Map<Wahlvorschlag, Long> stimmzettelPositionVergebenPerWahlvorschlagInGebietshierarchieUebergeordnet = new HashMap<>();
    Map<Liste, Long> stimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet = collectStimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet(entityAufStimmzettelDaten,
        gebietBetrachtet, wahlvorschlag);

    for (Wahlvorschlag wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet : wahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnetSortedByDefault) {
      if (!wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet.equals(wahlvorschlag)) {
        Long wahlvorschlagStimmzettelPosition = wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet.getGruppeImGebiet().getVorgegebenePosition();

        if (wahlvorschlagStimmzettelPosition != null) {
          stimmzettelPositionVergebenPerWahlvorschlagInGebietshierarchieUebergeordnet.put(wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet, wahlvorschlagStimmzettelPosition);
        }

        if (wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet.isDirektkandidat()) {
          Liste listeInGebietshierarchieUebergeordnet = bestimmeListeImGleichenOderHoeremGebiet(entityAufStimmzettelDaten, wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet);

          if (listeInGebietshierarchieUebergeordnet != null) {
            if (stimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet.containsKey(listeInGebietshierarchieUebergeordnet)) {
              Long listePosition = stimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet.get(listeInGebietshierarchieUebergeordnet);

              stimmzettelPositionVergebenPerWahlvorschlagInGebietshierarchieUebergeordnet.put(wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet, listePosition);
            } else {
              Long listePosition = listeInGebietshierarchieUebergeordnet.getStimmzettelPosition(gebietBetrachtet, entityAufStimmzettelDaten).getPosition();

              if (listePosition != null) {
                stimmzettelPositionVergebenPerWahlvorschlagInGebietshierarchieUebergeordnet.put(wahlvorschlagAufStimmzettelInGebietshierarchieUebergeordnet, listePosition);
              }
            }
          }
        }
      }
    }

    Set<Long> stimmzettelPositionenVergeben = new HashSet<>();
    stimmzettelPositionenVergeben.addAll(stimmzettelPositionVergebenPerWahlvorschlagInGebietshierarchieUebergeordnet.values());
    stimmzettelPositionenVergeben.addAll(stimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet.values());

    List<Wahlvorschlag> wahlvorschlaegeOhneStimmzettelPositionSortedByDefault = new ArrayList<>();
    wahlvorschlaegeOhneStimmzettelPositionSortedByDefault.addAll(wahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnetSortedByDefault);
    wahlvorschlaegeOhneStimmzettelPositionSortedByDefault.removeAll(stimmzettelPositionVergebenPerWahlvorschlagInGebietshierarchieUebergeordnet.keySet());
    wahlvorschlaegeOhneStimmzettelPositionSortedByDefault.removeAll(stimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet.keySet());

    long berechnetePosition = 0;

    for (Wahlvorschlag wahlvorschlagOhneStimmzettelPosition : wahlvorschlaegeOhneStimmzettelPositionSortedByDefault) {
      berechnetePosition++;

      while (stimmzettelPositionenVergeben.contains(berechnetePosition)) {
        berechnetePosition++;
      }

      if (wahlvorschlagOhneStimmzettelPosition.equals(wahlvorschlag)) {
        WahlvorschlagStimmzettelPosition result = createWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag, berechnetePosition);
        info("Betrachteter Wahlvorschlag hat KEINE vorgegebene Position und wird EINSORTIERT", result, wahlvorschlag);

        return result;
      }
    }

    LOG.error("Kann nicht passieren, dieser Wahlvorschlag muss in der übergebenen Liste vorhanden sein! [gebiet = " + gebiet + ", wahlvorschlag = " + wahlvorschlag
        + ", wahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnet = " + wahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnetSortedByDefault
        + ", wahlvorschlaegeOhneStimmzettelPosition = " + wahlvorschlaegeOhneStimmzettelPositionSortedByDefault + "]");

    return emptyWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag);
  }

  private boolean existsListeInGebietshierarchieUntergeordnet(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Gebiet gebiet, Wahlvorschlag wahlvorschlag) {
    Collection<Wahlvorschlag> wahlvorschlaegeInWahl = collectWahlvorschlaegeInWahl(entityAufStimmzettelDaten, wahlvorschlag);

    for (Wahlvorschlag another : wahlvorschlaegeInWahl) {
      if (another.isListe() && !another.getGebiet().equals(gebiet) && another.getGebiet().isUntergebietVon(gebiet)) {
        return true;
      }
    }

    return false;
  }

  @Nullable
  private Liste bestimmeListeImGleichenOderHoeremGebiet(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Wahlvorschlag template) {
    return collectWahlvorschlaegeInWahl(entityAufStimmzettelDaten, template).stream() //
        .filter(wahlvorschlag -> wahlvorschlag.isListe() && wahlvorschlag.getGruppe().equals(template.getGruppe()) && !wahlvorschlag.equals(template)) //
        .filter(wahlvorschlag -> template.getGebiet().isEqualOderUntergebietVon(wahlvorschlag.getGebiet())) //
        .findFirst() //
        .map(wahlvorschlag -> wahlvorschlag.castTo(Liste.class).getEntity()) //
        .orElse(null);
  }

  private Map<Liste, Long> collectStimmzettelPositionVergebenPerListeInGebietshierarchieUebergeordnet(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Gebiet gebiet, Wahlvorschlag wahlvorschlag) {
    Map<Liste, Long> result = new HashMap<>();

    if (wahlvorschlag.isDirektkandidat()) {
      for (Wahlvorschlag another : collectWahlvorschlaegeInWahl(entityAufStimmzettelDaten, wahlvorschlag)) {
        if (another.isListe() && another.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(wahlvorschlag.getGebiet())) {
          Long stimmzettelPosition = another.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten).getPosition();

          if (stimmzettelPosition != null) {
            result.put(another.castTo(Liste.class).getEntity(), stimmzettelPosition);
          }
        }
      }
    }

    return result;
  }

  private List<Wahlvorschlag> collectWahlvorschlaegeAufStimmzettelInGebietshierarchieUebergeordnetSortedByDefault(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Gebiet gebiet,
      Wahlvorschlag wahlvorschlag) {
    ZulassungPruefer zulassungPruefer = entityAufStimmzettelDaten.getZulassungPruefer();

    List<Wahlvorschlag> result = new ArrayList<>(collectWahlvorschlaegeInGebietshierarchieUebergeordnet(entityAufStimmzettelDaten, gebiet, wahlvorschlag).stream() //
        .filter(another -> isWahlvorschlagAufStimmzettel(zulassungPruefer, another)) //
        .collect(toList()));

    // WASDE-1184 alle laden...
    Collection<Wahlvorschlag> wahlvorschlaegeInWahl = collectWahlvorschlaegeInWahl(entityAufStimmzettelDaten, wahlvorschlag);

    if (wahlvorschlaegeInWahl == null) {
      wahlvorschlaegeInWahl = Collections.emptyList();
    }

    result.sort(new WahlvorschlagReihenfolgeVorsortierenComparator(entityAufStimmzettelDaten.getGruppeImGebietReihenfolgeVorsortierer(), wahlvorschlaegeInWahl,
        entityAufStimmzettelDaten.getStimmenPoolFactory(), gebiet));

    return result;
  }

  private boolean isWahlvorschlagAufStimmzettel(ZulassungPruefer zulassungPruefer, Wahlvorschlag wahlvorschlag) {
    // Nur Wahlvorschläge, die nicht bereits abgelehnt sind /*und auch noch potentiell zugelassen werden können*/
    return !wahlvorschlag.isNichtZugelassenImSinneAbgelehnt()/* && zulassungPruefer.isZulassenErlaubt(wahlvorschlag) */;
  }

  private Collection<Wahlvorschlag> collectWahlvorschlaegeInWahl(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Wahlvorschlag wahlvorschlag) {
    return entityAufStimmzettelDaten.collectWahlvorschlaegeInWahl(wahlvorschlag.getWahl());
  }

  private Collection<Wahlvorschlag> collectWahlvorschlaegeInGebietshierarchieUebergeordnet(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Gebiet gebiet, Wahlvorschlag wahlvorschlag) {
    Map<Gebiet, Collection<Wahlvorschlag>> wahlvorschlaegePerGebiet = entityAufStimmzettelDaten.collectWahlvorschlaegeInWahlPerGebiet(wahlvorschlag.getWahl());

    if (wahlvorschlag.isDirektkandidat()) {
      return wahlvorschlaegePerGebiet.get(wahlvorschlag.getGebiet());
    }

    Set<Wahlvorschlag> result = new HashSet<>();

    Gebiet gebietInGebietshierarchieUebergeordnet = gebiet;

    do {
      Collection<Wahlvorschlag> wahlvorschlaegeImGebiet = wahlvorschlaegePerGebiet.get(gebietInGebietshierarchieUebergeordnet);

      if (wahlvorschlaegeImGebiet != null) {
        for (Wahlvorschlag wahlvorschlagImGebiet : wahlvorschlaegeImGebiet) {
          if (wahlvorschlagImGebiet.isListe()) {
            result.add(wahlvorschlagImGebiet);
          }
        }
      }

      gebietInGebietshierarchieUebergeordnet = gebietInGebietshierarchieUebergeordnet.getUebergeordnetesGebiet();
    } while (gebietInGebietshierarchieUebergeordnet != null);

    return result;
  }

  private WahlvorschlagStimmzettelPosition emptyWahlvorschlagStimmzettelPosition() {
    return WahlvorschlagStimmzettelPosition.EMPTY;
  }

  private WahlvorschlagStimmzettelPosition emptyWahlvorschlagStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Wahlvorschlag wahlvorschlag) {
    return createWahlvorschlagStimmzettelPosition(entityAufStimmzettelDaten, wahlvorschlag, null); // ELECTMUC-2790
  }

  private WahlvorschlagStimmzettelPosition createWahlvorschlagStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Wahlvorschlag wahlvorschlag, Long berechnetePosition) {
    if (wahlvorschlag != null) {
      GruppeImGebiet gruppeImGebiet = wahlvorschlag.getGruppeImGebiet();

      if (gruppeImGebiet != null) {
        return new WahlvorschlagStimmzettelPosition(wahlvorschlag.getPk(), gruppeImGebiet, berechnetePosition, new WahlvorschlagStimmzettelPositionFormatter(
            formatWahlvorschlagPositionFct(wahlvorschlag), formatWahlvorschlagKennzahlFct(wahlvorschlag), isWaehlerBenannteWahlvorschlaegeErfassbar(entityAufStimmzettelDaten, wahlvorschlag)));
      }
    }

    return emptyWahlvorschlagStimmzettelPosition();
  }

  public BiFunction<WahlvorschlagStimmzettelPositionFormatterKonfiguration, Long, String> formatWahlvorschlagPositionFct(Wahlvorschlag wahlvorschlag) {
    return this::formatWahlvorschlagPositionDefault;
  }

  protected String formatWahlvorschlagPositionDefault(WahlvorschlagStimmzettelPositionFormatterKonfiguration formatterKonfiguration, Long wahlvorschlagPosition) {
    if (wahlvorschlagPosition != null) {
      return String.format("%d", wahlvorschlagPosition);
    }

    return "";
  }

  public BiFunction<WahlvorschlagStimmzettelPositionFormatterKonfiguration, Long, String> formatWahlvorschlagKennzahlFct(Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag.isListe()) {
      return this::formatWahlvorschlagKennzahlDefaultListenwahl;
    }

    return this::formatWahlvorschlagKennzahlDefault;
  }

  protected String formatWahlvorschlagKennzahlDefaultListenwahl(WahlvorschlagStimmzettelPositionFormatterKonfiguration formatterKonfiguration, Long listePosition) {
    if (listePosition != null) {
      if (formatterKonfiguration.isWaehlerBenannteWahlvorschlaegeErfassbar()) {// ELECTKOM-871
        return String.format("%02d", listePosition);
      }

      return String.format("%d00", listePosition);
    }

    return formatWahlvorschlagKennzahlDefault(formatterKonfiguration, listePosition);
  }

  protected String formatWahlvorschlagKennzahlDefault(WahlvorschlagStimmzettelPositionFormatterKonfiguration formatterKonfiguration, Long wahlvorschlagPosition) {
    if (wahlvorschlagPosition != null) {
      return String.format("%d", wahlvorschlagPosition);
    }

    return "";
  }

  public ListenkandidaturStimmzettelPosition calculateListenkandidaturStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, @Nullable Gebiet gebiet,
      @Nullable Listenkandidatur listenkandidatur) {
    if (listenkandidatur == null) {
      return emptyListenkandidaturStimmzettelPosition();
    }

    Liste liste = listenkandidatur.getListe();
    List<Listenkandidatur> listenkandidaturenAufStimmzettelSortedByDefault = collectListenkandidaturenAufStimmzettelSortedByDefault(entityAufStimmzettelDaten, liste);

    if (!listenkandidaturenAufStimmzettelSortedByDefault.contains(listenkandidatur)) {
      return emptyListenkandidaturStimmzettelPosition(entityAufStimmzettelDaten, liste, listenkandidatur);
    }

    Gebiet gebietBetrachtet = gebiet;

    if (gebietBetrachtet == null) {
      gebietBetrachtet = listenkandidatur.getGebiet();
    }

    return calculateListenkandidaturStimmzettelPosition(entityAufStimmzettelDaten, gebietBetrachtet, liste, listenkandidatur, listenkandidaturenAufStimmzettelSortedByDefault);
  }

  protected List<Listenkandidatur> collectListenkandidaturenAufStimmzettelSortedByDefault(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Liste liste) {
    Collection<Listenkandidatur> listenkandidaturenAufListe = liste.getListenkandidaturen();

    return collectListenkandidaturenAufStimmzettel(entityAufStimmzettelDaten, listenkandidaturenAufListe, new ListenkandidaturByRelativePositionComparator());
  }

  public List<Listenkandidatur> collectListenkandidaturenAufStimmzettel(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<Listenkandidatur> listenkandidaturen,
      Comparator<Listenkandidatur> comparator) {
    ZulassungPruefer zulassungPruefer = entityAufStimmzettelDaten.getZulassungPruefer();

    return listenkandidaturen.stream() //
        .filter(listenkandidatur -> isListenkandidaturAufStimmzettel(zulassungPruefer, listenkandidatur)) //
        .sorted(comparator) //
        .collect(toList());
  }

  private boolean isListenkandidaturAufStimmzettel(ZulassungPruefer zulassungPruefer, Listenkandidatur listenkandidatur) {
    return listenkandidatur.isAufStimmzettel(); // ELECT-8948, ELECT-9421
  }

  protected boolean isListenkandidaturStimmzettelPositionNennungenIncluded(Wahl wahl) {
    return false;
  }

  private ListenkandidaturStimmzettelPosition calculateListenkandidaturStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Gebiet gebiet, Liste liste,
      Listenkandidatur listenkandidatur, List<Listenkandidatur> listenkandidaturenAufStimmzettelSortedByDefault) {
    long berechnetePositionFortlaufend = 0;

    long berechnetePositionNennungenIncluded = 0;
    long berechnetePositionNennungenExcluded = 0;

    for (Listenkandidatur listenkandidaturAufStimmzettel : listenkandidaturenAufStimmzettelSortedByDefault) {
      berechnetePositionFortlaufend += 1L;

      long berechnetePositionNennungenIncludedIncremented = berechnetePositionNennungenIncluded + 1;
      long berechnetePositionNennungenExcludedIncremented = berechnetePositionNennungenExcluded + 1;

      Long vorgegebenePosition = listenkandidaturAufStimmzettel.getVorgegebenePosition();

      long naechstePositionNennungenIncluded = fixNullToDefault(vorgegebenePosition, berechnetePositionNennungenIncludedIncremented);
      long naechstePositionNennungenExcluded = fixNullToDefault(vorgegebenePosition, berechnetePositionNennungenExcludedIncremented);

      berechnetePositionNennungenIncluded = Math.max(naechstePositionNennungenIncluded, berechnetePositionNennungenIncludedIncremented);
      berechnetePositionNennungenExcluded = naechstePositionNennungenExcluded; // Wenn Position vorgegeben, dann immer verwenden!!!

      if (listenkandidaturAufStimmzettel.equals(listenkandidatur)) {
        break; // Done!
      }

      long anzahlNennungen = fixNullToDefault(listenkandidaturAufStimmzettel.getAnzahlNennungen(), 1L);

      berechnetePositionNennungenIncluded += Math.max(1L, anzahlNennungen) - 1;
    }

    return createListenkandidaturStimmzettelPosition(entityAufStimmzettelDaten, liste, listenkandidatur, berechnetePositionFortlaufend, berechnetePositionNennungenExcluded,
        berechnetePositionNennungenIncluded);
  }

  private ListenkandidaturStimmzettelPosition emptyListenkandidaturStimmzettelPosition() {
    return ListenkandidaturStimmzettelPosition.EMPTY;
  }

  private ListenkandidaturStimmzettelPosition emptyListenkandidaturStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Liste liste, Listenkandidatur listenkandidatur) {
    return createListenkandidaturStimmzettelPosition(entityAufStimmzettelDaten, liste, listenkandidatur, null, null, null); // ELECTMUC-2790
  }

  protected ListenkandidaturStimmzettelPosition createListenkandidaturStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Liste liste, Listenkandidatur listenkandidatur,
      Long berechnetePositionFortlaufend, Long berechnetePositionNennungenExcluded, Long berechnetePositionNennungenIncluded) {
    Long berechnetePosition = berechnetePositionNennungenExcluded;

    if (isListenkandidaturStimmzettelPositionNennungenIncluded(liste.getWahl())) { // ELECT-8784
      berechnetePosition = berechnetePositionNennungenIncluded;
    }

    return createListenkandidaturStimmzettelPosition(entityAufStimmzettelDaten, liste, listenkandidatur, berechnetePosition, berechnetePositionFortlaufend, berechnetePositionNennungenExcluded,
        berechnetePositionNennungenIncluded);
  }

  protected ListenkandidaturStimmzettelPosition createListenkandidaturStimmzettelPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Liste liste, Listenkandidatur listenkandidatur,
      Long berechnetePosition, Long berechnetePositionFortlaufend, Long berechnetePositionNennungenExcluded, Long berechnetePositionNennungenIncluded) {
    return new ListenkandidaturStimmzettelPosition(listenkandidatur, berechnetePosition, fixNullToDefault(listenkandidatur.getAnzahlNennungen(), 1L), berechnetePositionFortlaufend,
        berechnetePositionNennungenExcluded, berechnetePositionNennungenIncluded, new ListenkandidaturStimmzettelPositionFormatter(formatListenkandidaturPositionFct(listenkandidatur),
            formatListenkandidaturKennzahlFct(listenkandidatur), isWaehlerBenannteWahlvorschlaegeErfassbar(entityAufStimmzettelDaten, liste)));
  }

  public TriFunction<ListenkandidaturStimmzettelPositionFormatterKonfiguration, Long, Long, String> formatListenkandidaturPositionFct(Listenkandidatur listenkandidatur) {
    return this::formatListenkandidaturPositionDefault;
  }

  protected String formatListenkandidaturPositionDefault(ListenkandidaturStimmzettelPositionFormatterKonfiguration formatterKonfiguration, @Nullable Long listePosition,
      Long listenkandidaturPosition) {
    if (listenkandidaturPosition != null) {
      return String.format("%d", listenkandidaturPosition);
    }

    return "";
  }

  public TriFunction<ListenkandidaturStimmzettelPositionFormatterKonfiguration, Long, Long, String> formatListenkandidaturKennzahlFct(Listenkandidatur listenkandidatur) {
    return this::formatListenkandidaturKennzahlDefault;
  }

  protected String formatListenkandidaturKennzahlDefault(ListenkandidaturStimmzettelPositionFormatterKonfiguration formatterKonfiguration, @Nullable Long listePosition,
      Long listenkandidaturPosition) {
    if (listenkandidaturPosition != null) {
      if (listePosition == null || formatterKonfiguration.isWaehlerBenannteListenkandidaturenErfassbar()) { // ELECTKOM-871
        return String.format("%02d", listenkandidaturPosition);
      }

      return String.format("%d%02d", listePosition, listenkandidaturPosition);
    }

    return "";
  }

  private boolean isWaehlerBenannteWahlvorschlaegeErfassbar(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag != null) {
      Gebiet wahlvorschlagGebiet = wahlvorschlag.getGebiet();

      return getAnzahlStimmenProWaehlerBestimmer().isWaehlerBenannteWahlvorschlaegeErfassbar(wahlvorschlagGebiet,
          entityAufStimmzettelDaten.collectWahlvorschlaegeInWahlPerGebiet(wahlvorschlag.getWahl()).getOrDefault(wahlvorschlagGebiet, new ArrayList<>()));
    }

    return false;
  }

  protected AnzahlStimmenProWaehlerBestimmer getAnzahlStimmenProWaehlerBestimmer() {
    return anzahlStimmenProWaehlerBestimmer;
  }

  private long fixNullToDefault(Long value, long defaultValue) {
    if (value == null) {
      return defaultValue;
    }

    return value;
  }

  private void info(String cause, WahlvorschlagStimmzettelPosition position, @Nullable Wahlvorschlag wahlvorschlag) {
    String format = cause + " [position = {}, kennwort = {}, wahlvorschlag = {}]";

    // LOG.info(format, position == null || !position.hasPosition() ? "KEINE" : position.getPosition(), wahlvorschlag == null ? "" : wahlvorschlag.getKurznameOderKennwort(), wahlvorschlag);
  }
}
