
package de.ivu.elect.dokumente.ergebnistabelle.referendum;

import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.CELL_STYLE_BORDERLESS;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.CELL_STYLE_DEFAULT;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.COLUMN_STYLE_HEADLINE_MID;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.COLUMN_STYLE_HEADLINE_SIDE;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.TEXT_STYLE_DEFAULT;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.TEXT_STYLE_HEADLINE;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.TEXT_STYLE_HEADLINE_LEFT;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.TEXT_STYLE_HEADLINE_RIGHT;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.TEXT_STYLE_SUB_HEADLINE;
import static de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate.TEXT_STYLE_UNTERSCHRIFT;
import static de.ivu.elect.util.ListUtil.bildeBloeckeAlsListen;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.inject.Inject;

import de.ivu.document.generator.template.OdtDocumentBuilder;
import de.ivu.document.generator.template.odt.builder.OdtTableBuilder;
import de.ivu.document.generator.template.odt.elements.OdtTextParagraph;
import de.ivu.document.generator.template.odt.elements.OdtTextTabulator;
import de.ivu.document.generator.template.odt.styles.DocumentStyleTemplate;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportContext;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportObject;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietDescriptor;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolverContext;
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.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeReferendumOptionStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.dokumente.DokumentFormatter;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleErfassungsgebietDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleStatistischeGemeindekennzifferDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerGesamtDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinBriefwahlInUrneErfassbarDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteGesamtDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteMitWahlscheinDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteNichtImWaehlerverzeichnisDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteOhneWahlscheinDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleExportMapper;
import de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleCsvDTO;
import de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleOdtKopfzeileBuilder;
import de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleSpreadsheetStyleTemplate;
import de.ivu.elect.was.exchange.ergebnis.ErgebnisExportMapper;
import de.ivu.elect.was.exchange.ergebnis.gesamtergebnis.ErgebnisGesamtergebnisDocumentExportMapper;
import de.ivu.elect.was.exchange.ergebnis.gesamtergebnis.ErgebnisGesamtergebnisExchange;

/**
 * @see ReferendumErgebnisGesamtergebnisTabellarischDocumentExportMapper!!!
 */
public class ReferendumErgebnistabelleDocumentExportMapper extends AbstractErgebnistabelleExportMapper<ReferendumErgebnistabelleCsvDTO>
    implements
      ErgebnisExportMapper,
      ErgebnisGesamtergebnisDocumentExportMapper<ErgebnisExportObject, ErgebnisExportContext>,
      ErgebnisGesamtergebnisExchange {

  public static final String FILE_FORMAT_NAME = "file.format.ergebnistabelle.referendum";

  public static final FileFormat FILE_FORMAT = new FileFormat(FILE_FORMAT_NAME, FileType.ODT);

  @Inject
  protected transient ErgebnistabelleOdtKopfzeileBuilder ergebnistabelleOdtKopfzeileBuilder;

  @Inject
  protected transient StimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder stimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder;

  @Inject
  protected transient ReferendumErgebnistabelleCsvDTOFactory referendumErgebnistabelleCsvDTOFactory;

  public ReferendumErgebnistabelleDocumentExportMapper() {
    super();
  }

  @Override
  public FileFormat getFileFormat() {
    return FILE_FORMAT;
  }

  @Override
  protected boolean isAcceptContext(Wahl wahl) {
    // TODO Aufgrund verschiedener Quick-Fixes für ELECTBB-284, erstmal nur für BB freigeschaltet, cf. BbBerReferendumErgebnistabelleDocumentExportMapper#isAcceptContext!
    return false; // wahl.isWahlMitReferendengebieten();
  }

  @Override
  protected void serialize(ErgebnisDaten ergebnisDaten, OutputStream os, ErgebnisExportContext context, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenPerSystemGruppePerGebiet,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, FileKonfiguration fileKonfiguration) throws MappingException {
    int anzahlZeilenProSeite = getZeilenProSeite(ergebnisDaten);
    int anzahlSpaltenProSeite = getSpaltenProSeite(ergebnisDaten);

    OdtDocumentBuilder documentBuilder = OdtDocumentBuilder.create(os).insertBlankPage();
    ErgebnistabelleSpreadsheetStyleTemplate.addAllStylesTo(documentBuilder);

    documentBuilder.removeFirstElement();

    List<ReferendumErgebnistabelleDocumentSeitenSchluessel> seiten = createSeitenSchluesselSortiert(context, ergebnisDaten, anzahlZeilenProSeite, anzahlSpaltenProSeite);

    long seitenNummerMaximal = getSeitenAnzahl(seiten);
    long seitenNummer = 1;

    for (ReferendumErgebnistabelleDocumentSeitenSchluessel seite : seiten) {
      WahlverwaltungGebietDescriptor wahldurchfuehrungsgebietDescriptor = createWahlverwaltungGebietDescriptor(wahlverwaltungGebietInformationResolverContext, seite.getWahlgebiet());

      addUeberschrift(documentBuilder, ergebnisDaten, context, wahldurchfuehrungsgebietDescriptor, seitenNummer, seitenNummerMaximal);

      OdtTableBuilder tableBuilder = OdtTableBuilder.create().setDefaultCellStyle(CELL_STYLE_DEFAULT);

      addTabelleKopfzeilen(tableBuilder, documentBuilder, seite);
      addTabelleZeilen(tableBuilder, seite.getZeilen(), wahldurchfuehrungsgebietDescriptor, ergebnisDaten, stimmenPerSystemGruppePerGebiet, seite.getSpalten());

      documentBuilder.addElement(tableBuilder.build());

      if (seitenNummer < seitenNummerMaximal) {
        documentBuilder.insertPageBreak();
      }

      seitenNummer++;
    }

    addUnterschriftenFelder(documentBuilder, ergebnisDaten, context, wahlverwaltungGebietInformationResolverContext, seitenNummer, seitenNummerMaximal);

    documentBuilder.save();
  }

  protected long getSeitenAnzahl(List<ReferendumErgebnistabelleDocumentSeitenSchluessel> seiten) {
    return seiten.size();
  }

  protected void addUeberschrift(OdtDocumentBuilder documentBuilder, ErgebnisDaten ergebnisDaten, ErgebnisExportContext context, WahlverwaltungGebietDescriptor wahldurchfuehrungsgebietDescriptor,
      long seitenNummer, long seitenNummerMaximal) {
    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();
    WahlartErgebnisart ergebnisart = ergebnisDaten.getErgebnis().getErgebnisart();

    OdtTableBuilder tableBuilder = OdtTableBuilder.create();
    tableBuilder.setDefaultCellStyle(CELL_STYLE_BORDERLESS);

    tableBuilder.addColumn().withColumnStyle(getUeberschriftStyleLinks()); // links
    tableBuilder.addColumn().withColumnStyle(getUeberschriftStyleMitte()); // mitte
    tableBuilder.addColumn().withColumnStyle(getUeberschriftStyleRechts()); // rechts

    tableBuilder.addRow();
    tableBuilder.addCell(getUeberschriftInhaltLinks(wahldurchfuehrung, ergebnisart, wahldurchfuehrungsgebietDescriptor)).withRowSpan(2).withCellTextStyle(TEXT_STYLE_HEADLINE_LEFT);
    tableBuilder.addCell(getUeberschriftInhaltMitte(wahldurchfuehrung, ergebnisart, wahldurchfuehrungsgebietDescriptor)).withCellTextStyle(TEXT_STYLE_HEADLINE);
    tableBuilder.addCell(getUeberschriftInhaltRechts(seitenNummer, seitenNummerMaximal)).withRowSpan(2).withCellTextStyle(TEXT_STYLE_HEADLINE_RIGHT);

    tableBuilder.addRow();
    tableBuilder.addCell(getMessage("ergebnistabelle.referendum.document.ueberschrift.wahl.3", DokumentFormatter.formatDateMedium(wahldurchfuehrung.getWahldatum(), context.getLocale())))
        .withCellTextStyle(TEXT_STYLE_SUB_HEADLINE);

    documentBuilder.addElement(tableBuilder.build());
    documentBuilder.addElement(new OdtTextParagraph()); // Leerzeile zur Tabelle
  }

  protected DocumentStyleTemplate getUeberschriftStyleRechts() {
    return COLUMN_STYLE_HEADLINE_SIDE;
  }

  protected DocumentStyleTemplate getUeberschriftStyleMitte() {
    return COLUMN_STYLE_HEADLINE_MID;
  }

  protected DocumentStyleTemplate getUeberschriftStyleLinks() {
    return COLUMN_STYLE_HEADLINE_SIDE;
  }

  protected String[] getUeberschriftInhaltLinks(Wahldurchfuehrung wahldurchfuehrung, WahlartErgebnisart ergebnisart, WahlverwaltungGebietDescriptor wahldurchfuehrungsgebietDescriptor) {
    WahlverwaltungGebietInformation stimmzettelgebietInformation = wahldurchfuehrungsgebietDescriptor.getStimmzettelgebiet();

    return new String[] { //
        getMessage("ergebnistabelle.referendum.document.ueberschrift.gebiet.gemeinde", wahldurchfuehrungsgebietDescriptor.getGemeinde().getName()), //
        getMessage("ergebnistabelle.referendum.document.ueberschrift.gebiet.landkreis", wahldurchfuehrungsgebietDescriptor.getKreis().getName()), //
        getMessage("ergebnistabelle.referendum.document.ueberschrift.gebiet.wahlkreis", trimToEmpty(stimmzettelgebietInformation.getNummer() + " " + stimmzettelgebietInformation.getName())) //
    };
  }

  protected String[] getUeberschriftInhaltMitte(Wahldurchfuehrung wahldurchfuehrung, WahlartErgebnisart ergebnisart, WahlverwaltungGebietDescriptor wahldurchfuehrungsgebietDescriptor) {
    String msgKeyUeberschriftWahl1 = "ergebnistabelle.referendum.document.ueberschrift.wahl.1";

    if (existsMessage(msgKeyUeberschriftWahl1 + "." + ergebnisart.getTechnicalName())) {
      msgKeyUeberschriftWahl1 = msgKeyUeberschriftWahl1 + "." + ergebnisart.getTechnicalName();
    }

    return new String[] { //
        getMessage(msgKeyUeberschriftWahl1), //
        getMessage("ergebnistabelle.referendum.document.ueberschrift.wahl.2", wahldurchfuehrung.getName(), getMessage(wahldurchfuehrung.getWahlart().getCommonWahlart())) //
    };
  }

  protected String[] getUeberschriftInhaltRechts(long seitenNummer, long seitenNummerMaximal) {
    return new String[] { //
        getMessage("ergebnistabelle.referendum.document.ueberschrift.seiten", seitenNummer, seitenNummerMaximal) //
    };
  }

  protected void addTabelleKopfzeilen(OdtTableBuilder tableBuilder, OdtDocumentBuilder documentBuilder, ReferendumErgebnistabelleDocumentSeitenSchluessel seite) {
    ergebnistabelleOdtKopfzeileBuilder.addTabellenKopfzeilen(tableBuilder, documentBuilder, seite.getSpalten());
  }

  protected void addTabelleZeilen(OdtTableBuilder tableBuilder, List<ReferendumErgebnistabelleZeilenSchluessel> zeilenSchluessel, WahlverwaltungGebietDescriptor wahldurchfuehrungsgebietDescriptor,
      ErgebnisDaten ergebnisDaten, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenPerSystemGruppePerGebiet, List<ErgebnistabelleDocumentColumn> columns) {
    for (ReferendumErgebnistabelleZeilenSchluessel schluessel : zeilenSchluessel) {
      addZeile(tableBuilder, referendumErgebnistabelleCsvDTOFactory.createReferendumErgebnistabelleCsvDTO(schluessel, ergebnisDaten, stimmenPerSystemGruppePerGebiet), columns);
    }
  }

  protected void addZeile(OdtTableBuilder tableBuilder, ErgebnistabelleCsvDTO dto, List<ErgebnistabelleDocumentColumn> columns) {
    tableBuilder.addRow();

    for (ErgebnistabelleDocumentColumn column : columns) {
      tableBuilder.addCell(column.getValue(dto));
      tableBuilder.withCellTextStyle(column.getTextStyle(dto));
    }
  }

  protected void addUnterschriftenFelder(OdtDocumentBuilder documentBuilder, ErgebnisDaten ergebnisDaten, ErgebnisExportContext context,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, long seitenNummer, long seitenNummerMaximal) {
    OdtTextParagraph paragraph;

    documentBuilder.addElement(OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_UNTERSCHRIFT));
    documentBuilder.addElement(OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_UNTERSCHRIFT));

    paragraph = OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_DEFAULT);
    paragraph.addChild(new OdtTextTabulator()).addContent(getMessage("ergebnistabelle.referendum.document.unterschriften.header"));

    documentBuilder.addElement(paragraph);

    documentBuilder.addElement(OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_UNTERSCHRIFT));
    documentBuilder.addElement(OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_UNTERSCHRIFT));

    paragraph = OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_UNTERSCHRIFT);
    paragraph.addTab();
    paragraph.addTab().addContent(getMessage("ergebnistabelle.referendum.document.unterschriften.placeholder.date"));
    paragraph.addTab();
    paragraph.addTab().addContent(getMessage("ergebnistabelle.referendum.document.unterschriften.placeholder.signature"));

    documentBuilder.addElement(paragraph);
  }

  private List<ReferendumErgebnistabelleDocumentSeitenSchluessel> createSeitenSchluesselSortiert(ErgebnisExportContext context, ErgebnisDaten ergebnisDaten, int anzahlZeilenProSeite,
      int anzahlSpaltenProSeite) throws MappingException {
    List<ReferendumErgebnistabelleDocumentSeitenSchluessel> result = new ArrayList<>();

    for (Wahldurchfuehrungsgebiet auswertungsgebiet : getRelevanteWahlgebieteSortiert(context, ergebnisDaten)) {
      List<List<ReferendumErgebnistabelleZeilenSchluessel>> zeilenSortedAndGroupedByAnzahlProSeite = createZeilenSortedAndGroupedByAnzahlProSeite(ergebnisDaten, auswertungsgebiet,
          anzahlZeilenProSeite);
      List<List<ErgebnistabelleDocumentColumn>> spaltenSortedAndGroupedByAnzahlProSeite = createSpaltenSortedAndGroupedByAnzahlProSeite(context, ergebnisDaten, auswertungsgebiet);

      result.addAll(createSeitenSchluesselSortiertNachSpalten(auswertungsgebiet, zeilenSortedAndGroupedByAnzahlProSeite, spaltenSortedAndGroupedByAnzahlProSeite));
    }

    return result;
  }

  /**
   * @return Die Seiten sortiert nach Spalten. Das heißt bspw., dass auf den ODT-Seiten 1 bis 4 für alle Zeilen 1 bis n die Spalten A1, ..., B, G, H , H1, H2 ausgegeben werden, bevor auf den
   *         ODT-Seiten 5 bis 8 ggf. für alle Zeilen 1 bis n die Spalten I, J, J1, J2 ausgegeben werden.
   */
  private List<ReferendumErgebnistabelleDocumentSeitenSchluessel> createSeitenSchluesselSortiertNachSpalten(Wahldurchfuehrungsgebiet auswertungsgebiet,
      List<List<ReferendumErgebnistabelleZeilenSchluessel>> zeilenSortedAndGroupedByAnzahlProSeite, List<List<ErgebnistabelleDocumentColumn>> spaltenSortedAndGroupedByAnzahlProSeite) {
    List<ReferendumErgebnistabelleDocumentSeitenSchluessel> result = new ArrayList<>();

    for (List<ErgebnistabelleDocumentColumn> spalten : spaltenSortedAndGroupedByAnzahlProSeite) {
      boolean isFolgeseite = false;

      for (List<ReferendumErgebnistabelleZeilenSchluessel> zeilen : zeilenSortedAndGroupedByAnzahlProSeite) {
        result.add(new ReferendumErgebnistabelleDocumentSeitenSchluessel(auswertungsgebiet, spalten, zeilen, isFolgeseite));

        isFolgeseite = true;
      }
    }

    return result;
  }

  protected List<Wahldurchfuehrungsgebiet> getRelevanteWahlgebieteSortiert(ErgebnisExportContext context, ErgebnisDaten ergebnisDaten) throws MappingException {
    return getRelevanteWahlgebieteSortiert(ergebnisDaten);
  }

  private List<List<ReferendumErgebnistabelleZeilenSchluessel>> createZeilenSortedAndGroupedByAnzahlProSeite(ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet,
      int anzahlZeilenProSeite) {
    return bildeBloeckeAlsListen(createZeilenSchluesselSortiert(auswertungsgebiet, ergebnisDaten), anzahlZeilenProSeite);
  }

  protected int getSpaltenProSeite(ErgebnisDaten ergebnisDaten) {
    return ANZAHL_SPALTEN_PRO_SEITE_UNBEGRENZT;
  }

  protected int getZeilenProSeite(ErgebnisDaten ergebnisDaten) {
    return ANZAHL_ZEILEN_PRO_SEITE_DEFAULT_VALUE;
  }

  protected List<ReferendumErgebnistabelleZeilenSchluessel> createZeilenSchluesselSortiert(Wahldurchfuehrungsgebiet auswertungsgebiet, ErgebnisDaten ergebnisDaten) {
    List<ReferendumErgebnistabelleZeilenSchluessel> result = new ArrayList<>();

    for (Stimmabgabeart stimmabgabeart : asList(Stimmabgabeart.URNE, Stimmabgabeart.BRIEF, Stimmabgabeart.GESAMT)) {
      GebietUndStimmabgabeart gebietUndStimmabgabeart = new GebietUndStimmabgabeart(auswertungsgebiet, stimmabgabeart);

      for (Wahldurchfuehrungsgebiet erfassungsgebiet : ergebnisDaten.getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        ReferendumErgebnistabelleZeilenSchluessel zeileFuerErfassungseinheit = ReferendumErgebnistabelleZeilenSchluessel.createFuerErfassungseinheit(stimmabgabeart, erfassungsgebiet);

        if (isZeileExportieren(zeileFuerErfassungseinheit)) {
          result.add(zeileFuerErfassungseinheit);
        }
      }

      ReferendumErgebnistabelleZeilenSchluessel zeileFuerSumme = ReferendumErgebnistabelleZeilenSchluessel.createFuerSumme(stimmabgabeart, auswertungsgebiet);

      if (isZeileExportieren(zeileFuerSumme)) {
        result.add(zeileFuerSumme);
      }
    }

    return result;
  }

  protected List<List<ErgebnistabelleDocumentColumn>> createSpaltenSortedAndGroupedByAnzahlProSeite(ErgebnisExportContext context, ErgebnisDaten ergebnisDaten,
      Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<List<ErgebnistabelleDocumentColumn>> result = new ArrayList<>();

    List<ErgebnistabelleDocumentColumn> columnGroup = getColumnsKopfzahlen(context, ergebnisDaten, auswertungsgebiet);

    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

    if (wahldurchfuehrung.isWahlMitReferendengebieten()) {
      columnGroup.addAll(getColumnsReferendumVorlagen(context, ergebnisDaten, auswertungsgebiet));
    }

    result.add(columnGroup);

    return result;
  }

  protected List<ErgebnistabelleDocumentColumn> getColumnsKopfzahlen(ErgebnisExportContext context, ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<ErgebnistabelleDocumentColumn> result = new ArrayList<>();

    Locale locale = context.getLocale();

    if (isGemeindekennzifferAusgeben(ergebnisDaten)) {
      result.add(new ErgebnistabelleStatistischeGemeindekennzifferDocumentColumn(locale, messages, customGebietInformationen));
    }

    result.add(new ErgebnistabelleErfassungsgebietDocumentColumn(locale, messages, isGebietstextAusgeben(ergebnisDaten)));

    result.add(new ErgebnistabelleWahlberechtigteOhneWahlscheinDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    result.add(new ErgebnistabelleWahlberechtigteMitWahlscheinDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    result.add(new ErgebnistabelleWahlberechtigteNichtImWaehlerverzeichnisDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    result.add(new ErgebnistabelleWahlberechtigteGesamtDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));

    result.add(new ErgebnistabelleWaehlerGesamtDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));

    if (auswertungsgebiet.getWurzelgebiet().isBriefwahlInUrneErfassenInGebietOderDarunter()) {
      result.add(new ErgebnistabelleWaehlerMitWahlscheinBriefwahlInUrneErfassbarDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));

      if (isColumnWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlAnzeigen(ergebnisDaten)) {
        result.add(new ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
      }

      if (isColumnWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlAnzeigen(ergebnisDaten)) {
        result.add(new ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
      }
    } else {
      result.add(new ErgebnistabelleWaehlerMitWahlscheinDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    }

    return result;
  }

  protected List<ErgebnistabelleDocumentColumn> getColumnsReferendumVorlagen(ErgebnisExportContext context, ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<ErgebnistabelleDocumentColumn> result = new ArrayList<>();

    for (ReferendumErgebnistabelleSpaltenSchluessel spaltenSchluessel : referendumErgebnistabelleCsvDTOFactory.getRelevanteSpaltenSchluesselSortiert(auswertungsgebiet, ergebnisDaten,
        Stimmenart.REFERENDUM_STIMME)) {
      result.add(new ReferendumErgebnistabelleDocumentColumn(context.getLocale(), messages, spaltenSchluessel, auswertungsgebiet, ergebnisDaten,
          getSpaltenbezeichnungOhneLabelGenerator(spaltenSchluessel), systemGruppeEigenschaften, getStimmeingabeReferendumOptionStimmenErfassenLabelGenerator(ergebnisDaten)));
    }

    return result;
  }

  protected String getSpaltenbezeichnungOhneLabelGenerator(ReferendumErgebnistabelleSpaltenSchluessel spaltenSchluessel) {
    return null; // Spaltenbezeichnung wird später anhand des Labelgenerators bestimmt.
  }

  private StimmeingabeReferendumOptionStimmenErfassenLabelGenerator getStimmeingabeReferendumOptionStimmenErfassenLabelGenerator(ErgebnisDaten ergebnisDaten) {
    return stimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFinder.getStimmeingabeReferendumOptionStimmenErfassenLabelGeneratorFuerWahl(ergebnisDaten.getWahl());
  }

  protected boolean isGemeindekennzifferAusgeben(ErgebnisDaten ergebnisDaten) {
    return true;
  }

  protected boolean isGebietstextAusgeben(ErgebnisDaten ergebnisDaten) {
    return true;
  }
}
