
package de.ivu.elect.core.exchange.stimmen.csv.dynamic;

import static java.util.stream.Collectors.toSet;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javax.annotation.Nonnull;

import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietDescriptor;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolver;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolverContext;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGemeindeInformation;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmeingabe.control.ListenwahlStimmeingabeKonfiguration;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;

/**
 * Code nicht schön, in VoteItKomStimmenExportDTOFactory viel doppelt
 */
public class DefaultDynamicStimmenExportDTOFactory extends AbstractDynamicStimmenExportDTOFactory {

  private final StimmenDTOFactoryHelper stimmenDTOFactoryHelper = new StimmenDTOFactoryHelper();

  public static final String HEADER_GEMEINDE = "Gemeinde";

  public static final String HEADER_GEMEINDE_NAME = "Gemeindename";

  public static final String HEADER_WAHLKREISNUMMER = "Wahlkreisnummer";

  public static final String HEADER_STIMBEZIRKSNUMMER = "Stimmbezirksnummer";

  public static final String HEADER_ERFASSUNGSGEBIETS_NUMMER = "Erfassungsgebietsnummer";

  public static final String HEADER_ERFASSUNGSGEBIETS_NAME = "Erfassungsgebietsname";

  public static final String HEADER_BEZIRKSNUMMER = "Bezirksnummer";

  public static final String HEADER_ERFASSUNGSGEBIETSART = "Erfassungsgebietsart";

  public static final String HEADER_STIMBEZIRKSART = "Stimmbezirksart";

  public static final String HEADER_WAHLBERECHTIGTE_GESAMT = "Wahlberechtigte gesamt (A)";

  public static final String HEADER_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN = "Wahlberechtigte ohne Wahlschein (A1)";

  public static final String HEADER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN = "Wahlberechtigte mit Wahlschein (A2)";

  public static final String HEADER_WAHLBERECHTIGTE_NICHT_IM_WVZ = "Wahlberechtigte nicht im WVZ (A3)";

  // ELECT-12397
  public static final String HEADER_WAEHLER_GESAMT = "Waehler gesamt (B)"; // (X1) ELECT-12397

  public static final String HEADER_WAEHLER_OHNE_WAHLSCHEIN = "Waehler ohne Wahlschein"; // X2 ELECT-12397

  public static final String HEADER_WAEHLER_MIT_WAHLSCHEIN = "Waehler mit Wahlschein"; // X3 ELECT-12397

  public static final String HEADER_URNENWAEHLER_MIT_WAHLSCHEIN_IN_URNENWAHLLOKAL = "Urnenwaehler mit Wahlschein in Urnenwahllokal"; // X4 ELECT-12397

  public static final String HEADER_BRIEFWAEHLER_MIT_WAHLSCHEIN_IN_URNENWAHLLOKAL = "Briefwaehler mit Wahlschein in Urnenwahllokal"; // X5 ELECT-12397

  public static final String HEADER_WAEHLER_MIT_WAHLSCHEIN_LEGACY = "Waehler mit Wahlschein (B1)"; // vor ELECT-12397

  public static final String HEADER_STIMMEN_UNGUELTIGE = "Stimmen ungueltige (C)";

  public static final String HEADER_STIMMEN_GUELTIGE = "Stimmen gueltige (D)";

  public static final String HEADER_INDEX_NUR_STIMMEN = "D";

  public static final String HEADER_DIREKTSTIMMEN_UNGUELTIGE = "Direktstimmen ungueltige (C)";

  public static final String HEADER_DIREKTSTIMMEN_GUELTIGE = "Direktstimmen gueltige (D)";

  public static final String HEADER_INDEX_DIREKTSTIMMEN = "D";

  public static final String HEADER_LISTENSTIMMEN_UNGUELTIGE = "Listenstimmen ungueltige (E)";

  public static final String HEADER_LISTENSTIMMEN_GUELTIGE = "Listenstimmen gueltige (F)";

  public static final String HEADER_INDEX_LISTENSTIMMEN = "F";

  public static final String HEADER_SEPARATOR_LISTENKANDIDATURSTIMMEN = "_";

  public static final String HEADER_SUFFIX_LISTENSTIMMEN_UNVERAENDERT = "_unveraendert";

  public static final String HEADER_SUFFIX_LISTENSTIMMEN_VERAENDERT = "_veraendert";

  public static final String HEADER_SUFFIX_LISTENSTIMMEN_PANASCHIERT = "_panaschiert"; // TODO Name noch zu klären...

  public static final String HEADER_SEPARATOR_REFEREUNDUMSTIMMEN = "_";

  public static final String HEADER_INDEX_GUELTIGE_REFERENDUMSTIMMEN = "H";

  public static final String HEADER_INDEX_UNGUELTIGE_REFERENDUMSTIMMEN = "C";

  public static final String HEADER_AUSZAEHLBEZIRK_STIMBEZIRKSNUMMER = "Ausgezaehlt abweichend Gebiet";

  public static final String HEADER_AUSZAEHLBEZIRK_GEMEINDE = "Ausgezaehlt abweichend AGS";

  public static final String HEADER_ERFASSUNG_NICHT_ERFASST = "Ausbleibend";

  private final Map<Gebiet, WahlverwaltungGebietDescriptor> wahlverwaltungGebietDescriptorPerGebietInGebietsbaum = new HashMap<>();

  private final boolean gebietsbaumContainsGemeinde;

  protected final boolean gebietsbaumContainsWahlkreis;

  private final WahlverwaltungGebietInformationResolver wahlverwaltungGebietInformationResolver;

  private final boolean ausbleibendAusgeben;

  protected List<Gruppe> gruppenZugelassenSortiertNachPositionAufGebiet;

  // protected List<Gruppe> gruppennMitDirektkandidatZugelassenSortiertNachPositionAufGebiet;
  protected List<Gruppe> gruppennMitListeZugelassenSortiertNachPositionAufGebiet;

  //  protected List<Long> besetzteListenPositionenAufsteigendSortiert; ELECT-11956

  protected List<Integer> maxAnzahlSpaltenFuerListenKandidaturStimmenProListe;

  protected List<Liste> zugelasseneListen;

  private final WahlverwaltungGebietInformationResolverContext context;

  public DefaultDynamicStimmenExportDTOFactory(Wahldurchfuehrungsgebiet wurzelgebiet, DynamicStimmenExchangeErgebnisDaten ergebnisDaten,
      ListenwahlStimmeingabeKonfiguration listenwahlStimmeingabeKonfiguration, WahlverwaltungGebietInformationResolver wahlverwaltungGebietInformationResolver, boolean ausbleibendAusgeben) {
    //ELECT-11808

    super(wurzelgebiet, ergebnisDaten, listenwahlStimmeingabeKonfiguration);
    this.wahlverwaltungGebietInformationResolver = wahlverwaltungGebietInformationResolver;
    this.ausbleibendAusgeben = ausbleibendAusgeben;
    zugelasseneListen = getZugelasseneListen();

    gruppenZugelassenSortiertNachPositionAufGebiet = getErgebnisDaten().getGruppenZugelassenSortiertNachPositionAufGebiet(getWurzelgebiet());
    // gruppennMitDirektkandidatZugelassenSortiertNachPositionAufGebiet = getErgebnisDaten().getGruppenMitDirektkandidatZugelassenSortiertNachPositionAufGebiet(getWurzelgebiet());
    gruppennMitListeZugelassenSortiertNachPositionAufGebiet = getErgebnisDaten().getGruppenMitListeZugelassenSortiertNachPositionAufGebiet(getWurzelgebiet());

    //    // das hier sieht extrem merkwürdig aus...
    //    besetzteListenPositionenAufsteigendSortiert = zugelasseneListen.stream()
    //        .map(liste -> stimmenDTOFactoryHelper.checkNull(liste.getGruppeImGebiet().getRelativePositionAbgeleitet())).
    //            distinct().sorted(Comparator.comparingLong(value -> value)).collect(Collectors.toList());
    //
    //    maxAnzahlSpaltenFuerListenKandidaturStimmenProListe = IntStream.range(0, besetzteListenPositionenAufsteigendSortiert.size())
    //        .mapToObj(index -> getAnzahlSpaltenFuerListenKandidaturStimmenProListe(besetzteListenPositionenAufsteigendSortiert.get(index)))
    //        .collect(Collectors.toList());

    // ELECT-11956
    maxAnzahlSpaltenFuerListenKandidaturStimmenProListe = IntStream.range(0, getStimmzettelPositionenAllerListen().size())
        .mapToObj(index -> getAnzahlSpaltenFuerListenKandidaturStimmenProListe(getStimmzettelPositionenAllerListen().get(index))).collect(Collectors.toList());

    //ELECT-11808
    boolean gebietsbaumContainsGemeindeToSet = false;
    boolean gebietsbaumContainsWahlkreisToSet = false;

    Collection<Gebiet> gebieteInGebietsbaum = getWurzelgebiet().getInGebietshierarchieUntergeordneteGebiete();

    context = getErgebnisDaten().getWahlverwaltungGebietInformationResolverContext();

    for (Gebiet gebiet : gebieteInGebietsbaum) {
      WahlverwaltungGebietDescriptor wahlverwaltungGebietDescriptor = wahlverwaltungGebietInformationResolver.createDescriptor(context, gebiet);

      wahlverwaltungGebietDescriptorPerGebietInGebietsbaum.put(gebiet, wahlverwaltungGebietDescriptor);

      gebietsbaumContainsGemeindeToSet |= !wahlverwaltungGebietDescriptor.getGemeinde().isUnbekannt();
      gebietsbaumContainsWahlkreisToSet |= !wahlverwaltungGebietDescriptor.getWahlkreis().isUnbekannt();
    }
    gebietsbaumContainsGemeinde = gebietsbaumContainsGemeindeToSet;
    gebietsbaumContainsWahlkreis = gebietsbaumContainsWahlkreisToSet;
  }

  public List<String> createKopfZahlenOhneStimmen() {
    List<String> result = new ArrayList<>();

    if (isGebietsbaumContainsGemeinde()) {
      result.add(HEADER_GEMEINDE);
    }
    if (isGemeindeNameAnzeigen()) {
      result.add(HEADER_GEMEINDE_NAME);
    }

    if (isGebietsbaumContainsWahlkreis()) {
      result.add(HEADER_WAHLKREISNUMMER);
    }
    if (isErfassungsgebietAusgeben()) {
      result.add(getHeaderGebietsNummer());
      result.add(getHeaderGebietsart());
      if (isErfassungsgebietNameAusgaben()) {
        result.add(getHeaderGebietsName());
      }
      if (isBezirksnummerAusgeben()) {
        result.add(getHeaderBezirksnummer());
      }
    }

    if (isAusbleibendAusgeben()) {
      // ELECTKOM-1770
      result.add(HEADER_AUSZAEHLBEZIRK_STIMBEZIRKSNUMMER);
      result.add(HEADER_AUSZAEHLBEZIRK_GEMEINDE);
      result.add(HEADER_ERFASSUNG_NICHT_ERFASST);
    }

    result.add(HEADER_WAHLBERECHTIGTE_GESAMT);
    result.add(HEADER_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN);
    result.add(HEADER_WAHLBERECHTIGTE_MIT_WAHLSCHEIN);
    result.add(HEADER_WAHLBERECHTIGTE_NICHT_IM_WVZ);
    result.add(HEADER_WAEHLER_GESAMT);
    if (isWaehlerOhneWahlscheinVerwenden()) {
      result.add(HEADER_WAEHLER_OHNE_WAHLSCHEIN);
    }
    if (isWaehlerMitWahlscheinVerwenden()) {
      result.add(HEADER_WAEHLER_MIT_WAHLSCHEIN);
    }
    if (isUrnenwaehlerMitWahlscheinInUrnenWahllokalVerwenden()) {
      result.add(HEADER_URNENWAEHLER_MIT_WAHLSCHEIN_IN_URNENWAHLLOKAL);
    }
    if (isBriefwaehlerMitWahlscheinInUrnenWahllokalVerwenden()) {
      result.add(HEADER_BRIEFWAEHLER_MIT_WAHLSCHEIN_IN_URNENWAHLLOKAL);
    }
    return result;
  }

  protected boolean isErfassungsgebietNameAusgaben() {
    return false;
  }

  protected boolean isBezirksnummerAusgeben() {
    return false;
  }

  protected boolean isUrnenwaehlerMitWahlscheinInUrnenWahllokalVerwenden() {
    return true;
  }

  protected boolean isWaehlerMitWahlscheinVerwenden() {
    return true;
  }

  protected boolean isWaehlerOhneWahlscheinVerwenden() {
    return true;
  }

  protected boolean isBriefwaehlerMitWahlscheinInUrnenWahllokalVerwenden() {
    return true;
  }

  @Nonnull
  protected String getHeaderGebietsart() {
    return HEADER_ERFASSUNGSGEBIETSART;
  }

  @Nonnull
  protected String getHeaderGebietsNummer() {
    return HEADER_ERFASSUNGSGEBIETS_NUMMER;
  }

  protected String getHeaderGebietsName() {
    return HEADER_ERFASSUNGSGEBIETS_NAME;
  }

  protected String getHeaderBezirksnummer() {
    return HEADER_BEZIRKSNUMMER;
  }

  public List<String> schreibeDatenOhneStimmen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    List<String> result = new ArrayList<>();

    if (isGebietsbaumContainsGemeinde()) {
      WahlverwaltungGemeindeInformation gemeinde = getWahlverwaltungGebietDescriptor(wahldurchfuehrungsgebiet).getGemeinde();
      result.add(gemeinde.getNummer());
    }
    if (isGemeindeNameAnzeigen()) {
      WahlverwaltungGemeindeInformation gemeinde = getWahlverwaltungGebietDescriptor(wahldurchfuehrungsgebiet).getGemeinde();
      result.add(gemeinde.getName());
    }

    if (isGebietsbaumContainsWahlkreis()) {
      WahlverwaltungGebietDescriptor gebietDescriptor = getWahlverwaltungGebietDescriptor(wahldurchfuehrungsgebiet);
      if (gebietDescriptor == null) {
        result.add("");
      } else {
        result.add(gebietDescriptor.getWahlkreis().getNummer());
      }
    }

    if (isErfassungsgebietAusgeben()) {
      result.add(wahldurchfuehrungsgebiet.getNummer());
      result.add(wahldurchfuehrungsgebiet.getGebietsart().getGebietsartName());
      if (isErfassungsgebietNameAusgaben()) {
        result.add(wahldurchfuehrungsgebiet.getName());
      }
      if (isBezirksnummerAusgeben()) {
        if (wahldurchfuehrungsgebiet.isErfassungsgebietLautGebietsart()) {
          result.add(wahldurchfuehrungsgebiet.getNummerBezirk());

        } else {
          result.add("");
        }
      }
    }

    if (isAusbleibendAusgeben()) {
      // ELECTKOM-1770
      if (wahldurchfuehrungsgebiet.getErfasstIn() == null) {
        result.add(""); // auszaehlbezirkNr
        result.add(""); // auszaehlbehoerdeAGS
      } else {
        if (isBezirksnummerAusgeben()) {
          result.add(wahldurchfuehrungsgebiet.getErfasstIn().getNummerBezirkFallsGesetzt());
        } else {
          result.add(wahldurchfuehrungsgebiet.getErfasstIn().getNummer());
        }
        result.add(getGemeindeNummer(wahldurchfuehrungsgebiet.getErfasstIn()));
      }
      if (isAusbleibend(wahldurchfuehrungsgebiet)) {
        result.add("J");
      } else {
        result.add("N");
      }
    }

    result.add(getWahlberechtigteGesamt(wahldurchfuehrungsgebiet));
    result.add(getWahlberechtigteOhneWahlschein(wahldurchfuehrungsgebiet));
    result.add(getWahlberechtigteMitWahlschein(wahldurchfuehrungsgebiet));
    result.add(getWahlberechtigteNichtImWaehlerverzeichnis(wahldurchfuehrungsgebiet));

    result.add(getWaehlerGesamt(wahldurchfuehrungsgebiet));

    if (isWaehlerOhneWahlscheinVerwenden()) {
      result.add(getWaehlerOhneWahlschein(wahldurchfuehrungsgebiet));
    }
    if (isWaehlerMitWahlscheinVerwenden()) {
      result.add(getWaehlerMitWahlschein(wahldurchfuehrungsgebiet));
    }
    if (isUrnenwaehlerMitWahlscheinInUrnenWahllokalVerwenden()) {
      result.add(getUrnenwaehlerMitWahlscheinInUrnenwahllokal(wahldurchfuehrungsgebiet));
    }

    if (isBriefwaehlerMitWahlscheinInUrnenWahllokalVerwenden()) {
      result.add(getBriefwaehlerMitWahlscheinInUrnenwahllokal(wahldurchfuehrungsgebiet));
    }

    return result;
  }

  private WahlverwaltungGebietDescriptor getWahlverwaltungGebietDescriptor(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return wahlverwaltungGebietDescriptorPerGebietInGebietsbaum.computeIfAbsent(wahldurchfuehrungsgebiet,
        x -> wahlverwaltungGebietInformationResolver.createDescriptor(context, wahldurchfuehrungsgebiet));
  }

  protected boolean isGemeindeNameAnzeigen() {
    return false;
  }

  protected boolean isErfassungsgebietAusgeben() {
    return true;
  }

  @Override
  public List<List<String>> createHeader() {
    List<String> result = createKopfZahlenOhneStimmen();

    // int anzahlZugelassenerGruppen = gruppenZugelassenSortiertNachPositionAufGebiet.size();
    // int anzahlZugelassenerGruppenMitDirektkandidaten = gruppennMitDirektkandidatZugelassenSortiertNachPositionAufGebiet.size();
    // int anzahlZugelassenerGruppenMitListen = gruppennMitListeZugelassenSortiertNachPositionAufGebiet.size();
    if (isNurDirektWahl() || isNurListenWahl()) {
      result.add(HEADER_STIMMEN_UNGUELTIGE);
      result.add(HEADER_STIMMEN_GUELTIGE);

      if (isNurDirektWahl()) {
        if (getDirektkandidaten().isEmpty()) { // ELECT-10674 Direktkandidaten haengen am Wahlkreis
          stimmenDTOFactoryHelper.addHeaderDirektFuerWahlkreis(result, HEADER_INDEX_NUR_STIMMEN,
              getStimmzettelPositionenAllerDirektkandidaten() /*vor ELECT-12183 anzahlZugelassenerGruppenMitDirektkandidaten */ /*vor ELECT-12019
                                                                                                                                anzahlZugelassenerGruppen*/);
        } else {
          stimmenDTOFactoryHelper.addHeaderDirekt(result, HEADER_INDEX_NUR_STIMMEN, getWurzelgebiet(), getDirektkandidaten(), getErgebnisDaten().getEntityAufStimmzettelDaten());
        }
      }
      if (isNurListenWahl()) {
        addHeaderListen(result, HEADER_INDEX_NUR_STIMMEN);
      }
    } else {
      if (isWahlMitDirektstimmen()) {
        result.add(HEADER_DIREKTSTIMMEN_UNGUELTIGE);
        result.add(HEADER_DIREKTSTIMMEN_GUELTIGE);
        if (getDirektkandidaten().isEmpty()) { // ELECT-10674 Direktkandidaten haengen am Wahlkreis
          stimmenDTOFactoryHelper.addHeaderDirektFuerWahlkreis(result, HEADER_INDEX_DIREKTSTIMMEN,
              getStimmzettelPositionenAllerDirektkandidaten() /*vor ELECT-12183 anzahlZugelassenerGruppenMitDirektkandidaten */ /*vor ELECT-12019
                                                                                                                                anzahlZugelassenerGruppen*/);
        } else {
          stimmenDTOFactoryHelper.addHeaderDirekt(result, HEADER_INDEX_DIREKTSTIMMEN, getWurzelgebiet(), getDirektkandidaten(), getErgebnisDaten().getEntityAufStimmzettelDaten());
        }
      }
      if (isWahlMitListenstimmen()) {
        result.add(HEADER_LISTENSTIMMEN_UNGUELTIGE);
        result.add(HEADER_LISTENSTIMMEN_GUELTIGE);
        addHeaderListen(result, HEADER_INDEX_LISTENSTIMMEN);
      }
      if (isReferendum()) {
        //ELECT-11522
        addReferendumHeader(result);
      }
    }

    return Collections.singletonList(result);

  }

  private Integer getAnzahlSpaltenFuerListenKandidaturStimmenProListe(Long positionListe) {
    List<Liste> collectListen = zugelasseneListen.stream().filter(liste -> positionListe.equals(stimmenDTOFactoryHelper.checkNull(liste.getGruppeImGebiet().getRelativePositionAbgeleitet())))
        .collect(Collectors.toList());
    if (collectListen.isEmpty()) {
      return 0;
    } else {
      return Collections.max(collectListen.stream().map(liste -> getListenKandidaten(liste).size()).collect(Collectors.toList()));
    }
  }

  public void addHeaderListen(List<String> result, String headerPrefix) {
    for (int i = 0; i < getStimmzettelPositionenAllerListen().size(); i++) {
      Long positionListe = getStimmzettelPositionenAllerListen().get(i);

      if (isWahlMitListenstimmenUnveraendert()) {
        result.add(headerPrefix + positionListe + HEADER_SUFFIX_LISTENSTIMMEN_UNVERAENDERT);
      }

      if (isWahlMitListenstimmenVeraendert()) {
        result.add(headerPrefix + positionListe + HEADER_SUFFIX_LISTENSTIMMEN_VERAENDERT);
      }

      if (isWahlMitListenstimmenPanaschiert()) {
        result.add(headerPrefix + positionListe + HEADER_SUFFIX_LISTENSTIMMEN_PANASCHIERT);
      }

      result.add(headerPrefix + positionListe);

      if (isWahlMitListenkandidatenstimmen()) {

        for (int j = 1; j <= maxAnzahlSpaltenFuerListenKandidaturStimmenProListe.get(i); j++) {
          result.add(headerPrefix + positionListe + HEADER_SEPARATOR_LISTENKANDIDATURSTIMMEN + j);
        }

      }
    }
  }

  @Override
  public List<String> schreibeDaten(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    // TODO Reihenfolge des Headers nutzen
    List<String> result = schreibeDatenOhneStimmen(wahldurchfuehrungsgebiet);

    if (isWahlMitDirektstimmen()) {
      result.add(getDirektStimmenUnGueltig(wahldurchfuehrungsgebiet));
      result.add(getDirektStimmenGueltig(wahldurchfuehrungsgebiet));

      fillInDirektStimmenFuerKandidaten(wahldurchfuehrungsgebiet, result);
    }

    if (isWahlMitListenstimmen()) {
      result.add(getListenStimmenUnGueltig(wahldurchfuehrungsgebiet));
      result.add(getListenStimmenGueltig(wahldurchfuehrungsgebiet));
      schreibeDatenProListenKandidaten(wahldurchfuehrungsgebiet, result);
    }

    if (isReferendum()) {
      //ELECT-11522
      result.addAll(schreibeReferendumDaten(wahldurchfuehrungsgebiet));
    }

    return result;
  }

  public void schreibeDatenProListenKandidaten(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, List<String> result) {
    Map<Long, List<Liste>> sortedMap = stimmenDTOFactoryHelper.getNachStimmzettelpositionSortierteWahlvorschlaege(wahldurchfuehrungsgebiet, zugelasseneListen, getErgebnisDaten());

    for (int i = 0; i < getStimmzettelPositionenAllerListen().size(); i++) {

      List<Liste> listen = sortedMap.getOrDefault(getStimmzettelPositionenAllerListen().get(i), Collections.emptyList());
      Integer maxIndex = maxAnzahlSpaltenFuerListenKandidaturStimmenProListe.get(i);

      if (listen.isEmpty() || !isListenEinerGruppeMitGleicherAnzahlKandidaturen(listen)) {
        if (isWahlMitListenstimmenUnveraendert()) {
          result.add("");
        }

        if (isWahlMitListenstimmenVeraendert()) {
          result.add("");
        }

        if (isWahlMitListenstimmenPanaschiert()) {
          result.add("");
        }

        result.add("");

        if (isWahlMitListenkandidatenstimmen()) {
          for (int k = 1; k <= maxIndex; k++) {
            result.add("");
          }
        }
      } else {
        if (isWahlMitListenstimmenUnveraendert()) {
          result.add(getListenstimmenUnveraendert(wahldurchfuehrungsgebiet, listen));
        }

        if (isWahlMitListenstimmenVeraendert()) {
          result.add(getListenstimmenVeraendert(wahldurchfuehrungsgebiet, listen));
        }

        if (isWahlMitListenstimmenPanaschiert()) {
          result.add(getListenstimmenPanaschiert(wahldurchfuehrungsgebiet, listen));
        }

        result.add(getListenStimmen(wahldurchfuehrungsgebiet, listen));

        if (isWahlMitListenkandidatenstimmen()) {
          List<Listenkandidatur> listenKandidaten;

          if (listen.size() == 1) {
            listenKandidaten = Collections.emptyList();
          } else {
            listenKandidaten = getListenKandidaten(listen.get(0));
          }

          for (Listenkandidatur listenkandidatur : listenKandidaten) {
            result.add(getListenkandidaturStimmen(wahldurchfuehrungsgebiet, listenkandidatur));
          }

          for (int h = 1; h <= maxIndex - listenKandidaten.size(); h++) {
            result.add("");
          }
        }

      }
    }
  }

  private void fillInDirektStimmenFuerKandidaten(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, List<String> result) {
    Map<Long, List<Direktkandidat>> sortedMap = stimmenDTOFactoryHelper.getNachStimmzettelpositionSortierteWahlvorschlaege(wahldurchfuehrungsgebiet, getErgebnisDaten().getDirektkandidaten(),
        getErgebnisDaten());

    for (Long stimmzettelPosition : getStimmzettelPositionenAllerDirektkandidaten()) {
      // for (Gruppe gruppe : gruppennMitDirektkandidatZugelassenSortiertNachPositionAufGebiet) {

      //         for (int i = 1; i <= gruppennMitDirektkandidatZugelassenSortiertNachPositionAufGebiet/* vor ELECT-12019
      //         gruppenZugelassenSortiertNachPositionAufGebiet*/.size(); i++) {

      // Direktkandidat direktkandidat = sortedMap.getOrDefault(Long.valueOf(i), null);
      // Direktkandidat direktkandidat = sortedMap.values().stream().filter(k->k.getGruppe().equals(gruppe)).findFirst().orElse(null);
      List<Direktkandidat> direktkandidaten = sortedMap.getOrDefault(stimmzettelPosition, Collections.emptyList());

      if (direktkandidaten.isEmpty() || isVerschiedeneGruppen(direktkandidaten)) {
        result.add("");
      } else /* vor ELECT-12019 if (getDirektkandidaten().isEmpty())*/ {// der header listet die spalte auf
        result.add(getDirektStimmen(wahldurchfuehrungsgebiet, direktkandidaten));
      }
      //}
    }
  }

  private boolean isVerschiedeneGruppen(List<Direktkandidat> direktkandidaten) {
    return direktkandidaten.stream().map(Direktkandidat::getGruppe).distinct().count() > 1;
  }

  public boolean isListenEinerGruppeMitGleicherAnzahlKandidaturen(List<Liste> listen) {
    return listen.stream().map(Liste::getGruppe).distinct().count() < 2 && isListenHabenGleicheAnzahlListenkandidaturen(listen);
  }

  private boolean isListenHabenGleicheAnzahlListenkandidaturen(List<Liste> listen) {
    return listen.stream().map(l -> l.getListenkandidatursByListe().size()).collect(toSet()).size() == 1;
  }

  public void setMaxAnzahlSpaltenFuerListenKandidaturStimmenProListe(List<Integer> maxAnzahlSpaltenFuerListenKandidaturStimmenProListe) {
    this.maxAnzahlSpaltenFuerListenKandidaturStimmenProListe = maxAnzahlSpaltenFuerListenKandidaturStimmenProListe;
  }

  protected List<String> schreibeReferendumDaten(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    ArrayList<String> result = new ArrayList<>();

    for (Gruppe vorlage : getReferendumVorlagenSortiertNachPosition()) {
      result.add(notNull(getErgebnisDaten().getUngueltigeFuerReferendumVorlage(vorlage, wahldurchfuehrungsgebiet)));
      result.add(notNull(getErgebnisDaten().getGueltigeFuerReferendumVorlage(vorlage, wahldurchfuehrungsgebiet)));

      List<ReferendumOption> optionen = getErgebnisDaten().getReferendumOptionenEinerVorlageSortiertNachPosition(vorlage);

      for (ReferendumOption option : optionen) {
        result.add(notNull(getErgebnisDaten().getStimmenFuerReferendumOption(option, wahldurchfuehrungsgebiet)));
      }
    }

    return result;
  }

  protected List<Gruppe> getReferendumVorlagenSortiertNachPosition() {
    return getErgebnisDaten().getReferendumOptionenNachReferendumVorlage().keySet().stream()
        .sorted(Comparator.comparingLong(gruppe -> Optional.ofNullable(gruppe).map(Gruppe::getRelativePosition).orElse(0L))).collect(Collectors.toList());
  }

  private void addReferendumHeader(List<String> headers) {
    for (Gruppe vorlage : getReferendumVorlagenSortiertNachPosition()) {
      Long vorlagePosition = vorlage.getRelativePosition();

      headers.add(HEADER_INDEX_UNGUELTIGE_REFERENDUMSTIMMEN + vorlagePosition);

      String vorlageStimmenHeaderPrefix = HEADER_INDEX_GUELTIGE_REFERENDUMSTIMMEN + vorlagePosition;
      headers.add(vorlageStimmenHeaderPrefix);

      for (ReferendumOption referendumOption : getErgebnisDaten().getReferendumOptionenEinerVorlageSortiertNachPosition(vorlage)) {
        headers.add(vorlageStimmenHeaderPrefix + HEADER_SEPARATOR_REFEREUNDUMSTIMMEN + referendumOption.getPosition());
      }
    }
  }

  public boolean isGebietsbaumContainsGemeinde() {
    return gebietsbaumContainsGemeinde;
  }

  public boolean isGebietsbaumContainsWahlkreis() {
    return gebietsbaumContainsWahlkreis;
  }

  public boolean isAusbleibendAusgeben() {
    return ausbleibendAusgeben;
  }
}
