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

import static java.util.Arrays.asList;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipOutputStream;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import de.ivu.elect.business.datei.entity.CsvAnfuehrungszeichen;
import de.ivu.elect.business.datei.entity.CsvMaskierungszeichen;
import de.ivu.elect.business.datei.entity.CsvTrennzeichen;
import de.ivu.elect.business.datei.entity.CsvZeichensatz;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenListenkandidaturen;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportContext;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.exchange.control.CsvFileKonfiguration;
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.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.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleExportMapper;
import de.ivu.elect.exchange.csv.CsvDescriptor;
import de.ivu.elect.exchange.csv.CsvMarshaller;
import de.ivu.elect.exchange.zip.ZipOutputStreamBuilder;
import de.ivu.elect.util.FileUtil;
import de.ivu.elect.was.exchange.ergebnis.ErgebnisExportMapper;

/**
 * ELECTDD-89 Gesamtergebnis; Äquivalent zu V5 in München
 *
 * @see ByV5ZweiteSchnellmeldungErgebnisseExchange!!!
 */
public class ListenkandidaturenErgebnistabelleCsvZipExportMapper extends AbstractErgebnistabelleExportMapper<ListenkandidaturenErgebnistabelleCsvDTO> implements ErgebnisExportMapper {

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

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

  private static final boolean STREAM_NICHT_SCHLIESSEN = false;

  @Inject
  private transient ZipOutputStreamBuilder zipOutputStreamBuilder;

  @Inject
  private transient ListenkandidaturenErgebnistabelleCsvDTOFactory dtoFactory;

  @Inject
  private transient CsvMarshaller csvMarshaller;

  public ListenkandidaturenErgebnistabelleCsvZipExportMapper() {
    super();
  }

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

  @Override
  protected boolean isAcceptContext(@Nonnull Wahl wahl) {
    return wahl.isWahlMitListenkandidatenStimmen();
  }

  @Override
  protected void serialize(ErgebnisDaten ergebnisDaten, OutputStream os, ErgebnisExportContext context, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenPerSystemGruppePerGebiet,
      WahlverwaltungGebietInformationResolverContext wahlverwaltungGebietInformationResolverContext, FileKonfiguration fileKonfiguration) throws MappingException {
    ErgebnisDatenListenkandidaturen listenkandidaturDaten = ergebnisDaten.getErgebnisDatenListenkandidaturen();

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();

    try (ZipOutputStream zos = zipOutputStreamBuilder.createZipOutputStream(os)) {
      for (PolitischeGruppe gruppe : getRelevanteGruppenSortiert(ergebnisDaten)) {
        for (Wahldurchfuehrungsgebiet wahlgebiet : getRelevanteWahlgebieteSortiert(ergebnisDaten)) {
          WahlvorschlagStimmzettelPosition listeStimmzettelPosition = berechneStimmzettelPosition(gruppe, wahlgebiet, ergebnisDaten, entityAufStimmzettelDaten);

          // HINT: Gruppen müssen nicht in allen betrachteten Gebieten zugelassen sein
          if (listeStimmzettelPosition != null && listeStimmzettelPosition.hasPosition()) {
            zipOutputStreamBuilder.beginNewZipEntry(zos, getZippedFileName(gruppe, wahlgebiet));

            serializeToCsvFile(gruppe, wahlgebiet, ergebnisDaten, entityAufStimmzettelDaten, listeStimmzettelPosition, listenkandidaturDaten, stimmenPerSystemGruppePerGebiet, zos);
          }
        }
      }

      zos.finish();
    } catch (IOException ex) {
      throw new MappingException("Fehler beim Serialisieren der ZIP/CSV-Daten.", ex);
    }
  }

  private String getZippedFileName(Gruppe gruppe, Wahldurchfuehrungsgebiet wahlgebiet) {
    // HINT: der Schlüssel ist nicht immer leserlich, der Kurzname nicht immer vorhanden (in DD haben sie manchmal "-" eingetragen...)
    return /* ELECT-7848 */FileUtil.normalizeZipEntryName(String.format("%s-%s-%s", wahlgebiet.getNummer(), gruppe.getSchluessel(), gruppe.getKurzname())) + ".csv";
  }

  protected void serializeToCsvFile(PolitischeGruppe gruppe, Wahldurchfuehrungsgebiet wahlgebiet, ErgebnisDaten ergebnisDaten, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      WahlvorschlagStimmzettelPosition listeStimmzettelPosition, ErgebnisDatenListenkandidaturen listenkandidaturDaten, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenPerSystemGruppePerGebiet,
      ZipOutputStream zos) throws IOException {
    CsvDescriptor csvDescriptor = CsvDescriptor.withHeader(createCsvFileKonfiguration(), ListenkandidaturenErgebnistabelleCsvDTO.class);

    addListenkandidaturCsvHeaders(gruppe, wahlgebiet, csvDescriptor, listeStimmzettelPosition, listenkandidaturDaten, entityAufStimmzettelDaten);

    List<ListenkandidaturenErgebnistabelleCsvDTO> dtos = new ArrayList<>();

    for (ListenkandidaturenErgebnistabelleZeilenSchluessel schluessel : createZeilenSchluesselSortiert(gruppe, wahlgebiet, ergebnisDaten)) {
      if (isZeileExportieren(schluessel)) {
        dtos.add(dtoFactory.createListenkandidaturenErgebnistabelleCsvDTO(schluessel, listenkandidaturDaten, stimmenPerSystemGruppePerGebiet));
      }
    }

    csvMarshaller.serialize(ListenkandidaturenErgebnistabelleCsvDTO.class, dtos, zos, csvDescriptor, STREAM_NICHT_SCHLIESSEN);
  }

  /**
   * Reihenfolge und Art der zu exportierenden Daten sind besser handhabbar, wenn wir sie getrennt bestimmen
   */
  protected List<ListenkandidaturenErgebnistabelleZeilenSchluessel> createZeilenSchluesselSortiert(PolitischeGruppe gruppe, Wahldurchfuehrungsgebiet wahlgebiet, ErgebnisDaten ergebnisDaten) {
    List<ListenkandidaturenErgebnistabelleZeilenSchluessel> result = new ArrayList<>();

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

      for (Wahldurchfuehrungsgebiet erfassungsgebiet : ergebnisDaten.getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        result.add(ListenkandidaturenErgebnistabelleZeilenSchluessel.createFuerErfassungseinheit(stimmabgabeart, erfassungsgebiet, gruppe));
      }

      result.add(ListenkandidaturenErgebnistabelleZeilenSchluessel.createFuerSumme(stimmabgabeart, wahlgebiet, gruppe));
    }

    return result;
  }

  protected void addListenkandidaturCsvHeaders(Gruppe gruppe, Wahldurchfuehrungsgebiet wahlgebiet, CsvDescriptor csvDescriptor, WahlvorschlagStimmzettelPosition listeStimmzettelPosition,
      ErgebnisDatenListenkandidaturen listenkandidaturDaten, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    List<Listenkandidatur> listenkandidaturenSorted = dtoFactory.getRelevanteListenkandidaturenSortiert(gruppe, wahlgebiet, listenkandidaturDaten);

    for (Listenkandidatur listenkandidatur : listenkandidaturenSorted) {
      ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = listenkandidatur.getStimmzettelPosition(wahlgebiet, entityAufStimmzettelDaten);

      csvDescriptor.getHeader().add(getListenkandidaturCsvHeaderName(listenkandidatur, listeStimmzettelPosition, listenkandidaturStimmzettelPosition));
    }
  }

  protected String getListenkandidaturCsvHeaderName(Listenkandidatur listenkandidatur, WahlvorschlagStimmzettelPosition listeStimmzettelPosition,
      ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition) {
    // NOTE ELECTDD-256, dass 'ein' Name der Listenkandidatur angezeigt werden soll.
    return String.format("%s (%s)", getListenkandidaturstimmeLabelGenerator().formatListenstimmeAnzahlGueltigeKennung(listeStimmzettelPosition, listenkandidaturStimmzettelPosition),
        listenkandidatur.getFamilienname());
  }

  protected static CsvFileKonfiguration createCsvFileKonfiguration() {
    return new CsvFileKonfiguration() {

      @Override
      public CsvZeichensatz getZeichensatz() {
        return CsvZeichensatz.UTF_8;
      }

      @Override
      public Boolean isWithHeader() {
        return true;
      }

      @Override
      public Long getAnzahlKopfzeilen() {
        return 1L;
      }

      @Override
      public CsvTrennzeichen getTrennzeichen() {
        return CsvTrennzeichen.SEMIKOLON;
      }

      @Override
      public CsvAnfuehrungszeichen getAnfuehrungszeichen() {
        return CsvAnfuehrungszeichen.DOPPELT;
      }

      @Override
      public CsvMaskierungszeichen getMaskierungszeichen() {
        return CsvMaskierungszeichen.BACKSLASH;
      }

      @Override
      public FileFormat getFileFormat() {
        return new FileFormat(FILE_FORMAT_NAME, FileType.CSV);
      }

      @Override
      public Boolean isNutzeTrennzeichenNachLetztemFeld() {
        return false;
      }
    };
  }
}
