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

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 org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
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.Stimmen;
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.AbstractWahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
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.wahl.entity.CommonWahlart;
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.AbstractErgebnistabelleCsvDTO;
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.presentation.ergebnis.exchange.ErgebnisExportView;
import de.ivu.elect.was.exchange.ergebnis.gesamtergebnis.ErgebnisGesamtergebnisDocumentExportMapper;
import de.ivu.elect.was.exchange.ergebnis.gesamtergebnis.ErgebnisGesamtergebnisExchange;

public class GruppenErgebnistabelleDocumentExportMapper extends AbstractGruppenErgebnistabelleExportMapper
    implements
      ErgebnisGesamtergebnisDocumentExportMapper<ErgebnisExportObject, ErgebnisExportContext>,
      ErgebnisGesamtergebnisExchange {

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

  @Inject
  protected transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  protected transient ErgebnistabelleOdtKopfzeileBuilder ergebnistabelleOdtKopfzeileBuilder;

  public GruppenErgebnistabelleDocumentExportMapper() {
    super();
  }

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

  @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 = getAnzahlZeilenProSeite(ergebnisDaten);
    int anzahlSpaltenProSeite = getAnzahlSpaltenProSeite(ergebnisDaten);

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

    documentBuilder.removeFirstElement();

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

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

    for (GruppenErgebnistabelleDocumentSeitenSchluessel seite : seiten) {
      WahlverwaltungGebietDescriptor auswertungsgebietDescriptor = createWahlverwaltungGebietDescriptor(wahlverwaltungGebietInformationResolverContext, seite.getWahlgebiet());

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

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

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

      documentBuilder.addElement(tableBuilder.build());

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

      seitenNummer++;
    }

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

    documentBuilder.save();
  }

  /**
   * Welche Seiten in welcher Reihenfolge ausgegeben werden sollen, wird hier dediziert bestimmt.
   */
  private List<GruppenErgebnistabelleDocumentSeitenSchluessel> createSeitenSchluesselSortiert(ErgebnisExportContext context, ErgebnisDaten ergebnisDaten,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, int anzahlZeilenProSeite, int anzahlSpaltenProSeite) throws MappingException {
    List<GruppenErgebnistabelleDocumentSeitenSchluessel> result = new ArrayList<>();

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

      if (ANZAHL_SPALTEN_PRO_SEITE_UNBEGRENZT == anzahlSpaltenProSeite) {
        result.addAll(createSeitenSchluesselSortiertNachSpalten(auswertungsgebiet, zeilenSortedAndGroupedByAnzahlProSeite, spaltenSortedAndGroupedByAnzahlProSeite));
      } else {
        result.addAll(createSeitenSchluesselSortiertNachZeilen(auswertungsgebiet, zeilenSortedAndGroupedByAnzahlProSeite, spaltenSortedAndGroupedByAnzahlProSeite));
      }
    }

    return result;
  }

  /**
   * @return Die Seiten sortiert nach Zeilen. Das heißt bspw., dass auf den ODT-Seiten 1 bis 4 für die Zeilen 1 bis 25 alle Spalten A1, ..., B, ..., C, D, D1, ... Dx ausgegeben werden, bevor auf den
   *         ODT-Seiten 5 bis 8 für die Zeilen 26 bis 50 alle Spalten A1, ..., B, ..., C, D, D1, ... Dx ausgegeben werden, usw.
   */
  private List<GruppenErgebnistabelleDocumentSeitenSchluessel> createSeitenSchluesselSortiertNachZeilen(Wahldurchfuehrungsgebiet auswertungsgebiet,
      List<List<GruppenErgebnistabelleZeilenSchluessel>> zeilenSortedAndGroupedByAnzahlProSeite, List<List<ErgebnistabelleDocumentColumn>> spaltenSortedAndGroupedByAnzahlProSeite) {
    List<GruppenErgebnistabelleDocumentSeitenSchluessel> result = new ArrayList<>();

    for (List<GruppenErgebnistabelleZeilenSchluessel> zeilen : zeilenSortedAndGroupedByAnzahlProSeite) {
      boolean isFolgeseite = false;

      for (List<ErgebnistabelleDocumentColumn> spalten : spaltenSortedAndGroupedByAnzahlProSeite) {
        result.add(new GruppenErgebnistabelleDocumentSeitenSchluessel(auswertungsgebiet, spalten, zeilen, isFolgeseite));

        isFolgeseite = true;
      }
    }

    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 (Kopfzahl-) Spalten A1, ..., B ausgegeben werden, bevor auf den ODT-Seiten 5
   *         bis 8 für alle Zeilen 1 bis n die (Stimmen-) Spalten C, D, D1, ... Dx ausgegeben werden.
   */
  private List<GruppenErgebnistabelleDocumentSeitenSchluessel> createSeitenSchluesselSortiertNachSpalten(Wahldurchfuehrungsgebiet auswertungsgebiet,
      List<List<GruppenErgebnistabelleZeilenSchluessel>> zeilenSortedAndGroupedByAnzahlProSeite, List<List<ErgebnistabelleDocumentColumn>> spaltenSortedAndGroupedByAnzahlProSeite) {
    List<GruppenErgebnistabelleDocumentSeitenSchluessel> result = new ArrayList<>();

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

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

        isFolgeseite = true;
      }
    }

    return result;
  }

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

  protected int getAnzahlSpaltenProSeite(ErgebnisDaten ergebnisDaten) {
    if (ergebnisDaten.getWahlvorschlaege().size() > 24) { // ELECTBB-416, 24 Listen (D1 + D2 + ... + D24) sahen bei der EUW 2014 gerade noch OK aus...
      return ANZAHL_SPALTEN_PRO_SEITE_DEFAULT_VALUE;
    }

    return ANZAHL_SPALTEN_PRO_SEITE_UNBEGRENZT;
  }

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

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

  protected void addTabelleZeilen(OdtTableBuilder tableBuilder, List<GruppenErgebnistabelleZeilenSchluessel> zeilenSchluessel, WahlverwaltungGebietDescriptor wahldurchfuehrungsgebietDescriptor,
      ErgebnisDaten ergebnisDaten, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenPerSystemGruppePerGebiet, List<ErgebnistabelleDocumentColumn> columns) {
    for (GruppenErgebnistabelleZeilenSchluessel schluessel : zeilenSchluessel) {
      addTabelleZeile(tableBuilder, gruppenErgebnistabelleCsvDTOFactory.createGruppenErgebnistabelleCsvDTO(schluessel, ergebnisDaten, stimmenPerSystemGruppePerGebiet), columns);
    }
  }

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

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

  protected void addUeberschrift(OdtDocumentBuilder documentBuilder, ErgebnisDaten ergebnisDaten, ErgebnisExportContext context, WahlverwaltungGebietDescriptor auswertungsgebietDescriptor,
      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());
    tableBuilder.addColumn().withColumnStyle(getUeberschriftStyleMitte());
    tableBuilder.addColumn().withColumnStyle(getUeberschriftStyleRechts());

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

    tableBuilder.addRow();
    tableBuilder.addCell(getMessage("ergebnistabelle.gruppen.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 long getSeitenAnzahl(List<GruppenErgebnistabelleDocumentSeitenSchluessel> seiten) {
    return seiten.size();
  }

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

  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 auswertungsgebietDescriptor) {
    WahlverwaltungGebietInformation kreisInformation = auswertungsgebietDescriptor.getKreis();
    WahlverwaltungGebietInformation gemeindeInformation = auswertungsgebietDescriptor.getGemeinde();
    WahlverwaltungGebietInformation stimmzettelgebietInformation = auswertungsgebietDescriptor.getStimmzettelgebiet();

    if (CommonWahlart.EUROPAWAHL.equalsWahlart(wahldurchfuehrung)) {
      return new String[] { //
          getMessage("ergebnistabelle.gruppen.document.ueberschrift.gebiet.gemeinde", gemeindeInformation.getName()), //
          getMessage("ergebnistabelle.gruppen.document.ueberschrift.gebiet.landkreis", kreisInformation.getName()) //
      };
    }

    return new String[] { //
        getMessage("ergebnistabelle.gruppen.document.ueberschrift.gebiet.gemeinde", gemeindeInformation.getName()), //
        getMessage("ergebnistabelle.gruppen.document.ueberschrift.gebiet.landkreis", kreisInformation.getName()), //
        getMessage("ergebnistabelle.gruppen.document.ueberschrift.gebiet.wahlkreis", trimToEmpty(stimmzettelgebietInformation.getNummer() + " " + stimmzettelgebietInformation.getName())) //
    };
  }

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

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

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

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

  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.gruppen.document.unterschriften.header"));

    documentBuilder.addElement(paragraph);

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

    String placeholderSignature = getMessage("ergebnistabelle.gruppen.document.unterschriften.placeholder.signature");

    paragraph = OdtTextParagraph.createParagraphWithStyle(TEXT_STYLE_UNTERSCHRIFT);
    paragraph.addTab();
    paragraph.addTab().addContent(placeholderSignature);
    paragraph.addTab().addContent(placeholderSignature);
    paragraph.addTab().addContent(placeholderSignature);
    paragraph.addTab().addContent(placeholderSignature);

    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(placeholderSignature);
    paragraph.addTab().addContent(placeholderSignature);
    paragraph.addTab().addContent(placeholderSignature);
    paragraph.addTab().addContent(placeholderSignature);

    documentBuilder.addElement(paragraph);
  }

  protected Wahldurchfuehrungsgebiet extractGebietSelected(ErgebnisExportContext context) {
    return extractGebietSelected(context, wahldurchfuehrungsgebietDAO);
  }

  protected static Wahldurchfuehrungsgebiet extractGebietSelected(ErgebnisExportContext context, AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO) {
    return wahldurchfuehrungsgebietDAO.attach(context.get(ErgebnisExportView.GEBIET_SELECTED_PARAMETER_NAME, Wahldurchfuehrungsgebiet.class));
  }
}
