
package de.ivu.elect.was.exchange.kerg;

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

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;

import org.jsefa.csv.CsvSerializer;

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.ErgebnisExportContext;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportObject;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
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.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.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietByVorgegebenePositionComparator;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.control.PraesentationErgebnisArtBestimmer;
import de.ivu.elect.business.internet.entity.PraesentationErgebnisArt;
import de.ivu.elect.business.kandidatur.entity.AbstractKandidatur;
import de.ivu.elect.business.wahl.entity.CommonWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.exchange.csv.raw.AbstractRawCsvExportMapper;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.was.exchange.ergebnis.ErgebnisExportMapper;

public class ErgebnisKergExportMapper extends AbstractRawCsvExportMapper implements ErgebnisExportMapper {

  private static final long serialVersionUID = 1L;

  public static final String CSV_FILE_FORMAT_NAME = "file.format.ergebnis.kerg"; // SIEHE DestatisErgebnisDateiTyp (!)

  public static final CsvZeichensatz CSV_FILE_FORMAT_ZEICHENSATZ = CsvZeichensatz.UTF_8_BOM;

  @Inject
  protected transient Messages messages;

  @Inject
  protected transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  protected transient PraesentationErgebnisArtBestimmer praesentationErgebnisArtBestimmer;

  @Inject
  protected transient GebietNummerFormatter gebietNummerFormatter;

  public ErgebnisKergExportMapper() {
    super();
  }

  @Override
  public boolean isAcceptContext(ErgebnisExportContext context) {
    if (context.hasWahl()) {
      return isAcceptContext(context.getWahl());
    }

    return false;
  }

  protected boolean isAcceptContext(Wahl wahl) {
    return CommonWahlart.BUNDESTAGSWAHL.equalsWahlart(wahl) || CommonWahlart.EUROPAWAHL.equalsWahlart(wahl);
  }

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

  @Override
  protected String getFileFormatName() {
    return CSV_FILE_FORMAT_NAME;
  }

  @Override
  protected CsvZeichensatz getCsvZeichensatz() {
    return CSV_FILE_FORMAT_ZEICHENSATZ; // ELECTSL-99, cf. AbstractDestatisCsvKopfdatenExportMapper#createDefaultCsvExportFileKonfiguration!!!
  }

  @Override
  protected boolean closeOutputStreamOnCloseSerializer() {
    return true;
  }

  @Override
  public void serialize(ErgebnisExportObject exportObject, OutputStream os, ErgebnisExportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    ErgebnisDaten ergebnisDaten = exportObject.getErgebnisDaten();
    Stimmabgabeart stimmabgabeart = exportObject.getBetrachtetStimmabgabeart();

    CsvSerializer serializer = openSerializer(os);

    writeIntro(ergebnisDaten, serializer);

    List<Gruppe> systemGruppenUndParteienWaehlergruppenVereinigungen = collectSystemGruppenUndParteienWaehlergruppenVereinigungenMitListeOderDirektkandidat(ergebnisDaten);
    writeHeader(serializer, systemGruppenUndParteienWaehlergruppenVereinigungen, ergebnisDaten);

    List<Wahldurchfuehrungsgebiet> gebiete = bestimmeGebiete(ergebnisDaten);

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : gebiete) {
      if (wahldurchfuehrungsgebiet != null) {
        List<AbstractKergGruppeContainer> kergGruppeContainers = erstelleGebietGruppenContainer(systemGruppenUndParteienWaehlergruppenVereinigungen, wahldurchfuehrungsgebiet, ergebnisDaten,
            stimmabgabeart);

        writeField(serializer, gebietNummerFormatter.getNummer(wahldurchfuehrungsgebiet));
        writeField(serializer, wahldurchfuehrungsgebiet.getName());

        Gebiet uebergeordnetesGebiet = wahldurchfuehrungsgebiet.getUebergeordnetesGebiet();

        if (uebergeordnetesGebiet == null) {
          writeField(serializer, "");
        } else {
          writeField(serializer, gebietNummerFormatter.getNummer(uebergeordnetesGebiet));
        }

        for (AbstractKergGruppeContainer kergGruppeContainer : kergGruppeContainers) {
          writeFields(serializer, kergGruppeContainer.getValues());
        }
      }

      nextLine(serializer);
    }

    closeSerializer(serializer);
  }

  private List<AbstractKergGruppeContainer> erstelleGebietGruppenContainer(List<Gruppe> systemGruppenUndParteienWaehlergruppenVereinigungen, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      ErgebnisDaten ergebnisDaten, Stimmabgabeart stimmabgabeart) {
    Map<Gruppe, AbstractKergGruppeContainer> kergGruppeContainerPerGruppe = new HashMap<>();

    Gruppe systemGruppeUebrige = null;

    GueltigeKergGruppeContainer gueltigeKergGruppeContainer = null;
    List<PolitischeGruppeKergGruppeContainer> politischeGruppeKergGruppeContainers = new ArrayList<>();

    for (Gruppe gruppe : systemGruppenUndParteienWaehlergruppenVereinigungen) {
      final AbstractKergGruppeContainer kergGruppeContainer;

      if (gruppe.isPolitischeGruppe()) {
        PolitischeGruppe politischeGruppe = castTo(gruppe, PolitischeGruppe.class);

        PolitischeGruppeKergGruppeContainer politischeGruppeKergGruppeContainer = AbstractKergGruppeContainer.fromPolitischeGruppe(ergebnisDaten, politischeGruppe, wahldurchfuehrungsgebiet,
            stimmabgabeart, isGruppeTrittInWahlAn(ergebnisDaten, politischeGruppe), isGruppeDirektstimmenInGebietshierachieErfassbar(ergebnisDaten, politischeGruppe, wahldurchfuehrungsgebiet),
            isGruppeListenstimmenInGebietshierachieErfassbar(ergebnisDaten, politischeGruppe, wahldurchfuehrungsgebiet));
        politischeGruppeKergGruppeContainers.add(politischeGruppeKergGruppeContainer);

        kergGruppeContainer = politischeGruppeKergGruppeContainer;
      } else {
        if (isUebrige(gruppe)) {
          systemGruppeUebrige = gruppe;

          continue; // UEBRIGE später...
        }

        kergGruppeContainer = AbstractKergGruppeContainer.fromGruppe(ergebnisDaten, gruppe, wahldurchfuehrungsgebiet, stimmabgabeart, systemGruppeEigenschaften);

        if (isGueltige(gruppe)) {
          gueltigeKergGruppeContainer = (GueltigeKergGruppeContainer) kergGruppeContainer;
        }
      }

      kergGruppeContainerPerGruppe.put(gruppe, kergGruppeContainer);
    }

    if (systemGruppeUebrige == null) {
      throw new IllegalStateException("System-Gruppe UEBRIGE nicht gefunden");
    }

    if (gueltigeKergGruppeContainer == null) {
      throw new IllegalStateException("System-Gruppe GUELTIGE nicht gefunden");
    }

    /*
     * Da vorher nur Systemgruppen bzw. politische Gruppen OHNE Einzelbewerber betrachtet wurden, ist die Definition von UEBRIGE an dieser Stelle gleich kumulierte Einzelbewerber...
     */

    boolean existsUebrigeInGebietshierarchie = ergebnisDaten.getPolitischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie(wahldurchfuehrungsgebiet).stream()
        .anyMatch(PolitischeGruppe::isEinzelbewerber); // ELECT-12977

    UebrigeKergGruppeContainer uebrigeKergGruppeContainer = new UebrigeKergGruppeContainer(ergebnisDaten, systemGruppeUebrige, wahldurchfuehrungsgebiet, stimmabgabeart,
        existsUebrigeInGebietshierarchie, gueltigeKergGruppeContainer, politischeGruppeKergGruppeContainers);
    kergGruppeContainerPerGruppe.put(systemGruppeUebrige, uebrigeKergGruppeContainer);

    return systemGruppenUndParteienWaehlergruppenVereinigungen.stream().map(kergGruppeContainerPerGruppe::get).collect(toList());
  }

  // WASDE-1290
  protected boolean isGruppeTrittInWahlAn(ErgebnisDaten ergebnisDaten, PolitischeGruppe gruppe) {
    Wahldurchfuehrungsgebiet wurzelgebiet = ergebnisDaten.getWurzelgebiet();

    return ergebnisDaten.getPolitischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie(wurzelgebiet).contains(gruppe)
        || ergebnisDaten.getPolitischeGruppeMitListenInGebietshierarchie(wurzelgebiet).contains(gruppe);
  }

  protected boolean isGruppeDirektstimmenInGebietshierachieErfassbar(ErgebnisDaten ergebnisDaten, PolitischeGruppe gruppe, Wahldurchfuehrungsgebiet gebiet) {
    return ergebnisDaten.getPolitischeGruppeMitZugelassenenDirektkandidatenInGebietshierarchie(gebiet).contains(gruppe);
  }

  protected boolean isGruppeListenstimmenInGebietshierachieErfassbar(ErgebnisDaten ergebnisDaten, PolitischeGruppe gruppe, Wahldurchfuehrungsgebiet gebiet) {
    return ergebnisDaten.getPolitischeGruppeMitListenInGebietshierarchie(gebiet).contains(gruppe);
  }

  private boolean isGueltige(Gruppe gruppe) {
    return SystemGruppe.GUELTIGE.equalsGruppe(gruppe, systemGruppeEigenschaften);
  }

  private boolean isUebrige(Gruppe gruppe) {
    return SystemGruppe.UEBRIGE.equalsGruppe(gruppe, systemGruppeEigenschaften);
  }

  private List<Wahldurchfuehrungsgebiet> bestimmeGebiete(ErgebnisDaten ergebnisDaten) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();

    addGebiet(result, ergebnisDaten.getWurzelgebiet());

    while (!result.isEmpty() && result.get(0) == null) {
      result.remove(0);
    }

    return result;
  }

  private void addGebiet(List<Wahldurchfuehrungsgebiet> result, Wahldurchfuehrungsgebiet gebiet) {
    Wahldurchfuehrungsgebiet gebietLeerzeile = null;

    List<Gebiet> untergeordneteGebieteSortiertNachPosition = gebiet.getUntergeordneteGebieteSortiertNachPosition();

    if (!untergeordneteGebieteSortiertNachPosition.isEmpty()) {
      result.add(gebietLeerzeile);

      for (Gebiet untergebiet : untergeordneteGebieteSortiertNachPosition) {
        addGebiet(result, untergebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity());
      }
    }

    if (gebiet.isWurzelgebiet()) {
      result.add(gebietLeerzeile);
    }

    if (isBetrachteGebiet(gebiet)) {
      result.add(gebiet);
    }
  }

  protected boolean isBetrachteGebiet(Wahldurchfuehrungsgebiet gebiet) {
    return true; // WASDE-1290
  }

  private void writeHeader(CsvSerializer serializer, List<Gruppe> systemGruppenUndParteienWaehlergruppenVereinigungen, ErgebnisDaten ergebnisDaten) {
    List<HeaderKergGruppeContainer<Gruppe>> gruppenContainers = erstelleHeaderGruppenContainer(systemGruppenUndParteienWaehlergruppenVereinigungen, ergebnisDaten);

    // erste Zeile header
    writeField(serializer, "Nr");
    writeField(serializer, "Gebiet");
    writeField(serializer, "gehört zu");

    for (HeaderKergGruppeContainer<Gruppe> gruppenContainer : gruppenContainers) {
      writeFields(serializer, gruppenContainer.getHeaderErsteZeile());
    }
    nextLine(serializer);

    // zweite Zeile header
    writeField(serializer, "");
    writeField(serializer, "");
    writeField(serializer, "");

    for (HeaderKergGruppeContainer<Gruppe> gruppenContainer : gruppenContainers) {
      writeFields(serializer, gruppenContainer.getHeaderZweiteZeile());
    }

    nextLine(serializer);

    // dritte Zeile header
    writeField(serializer, "");
    writeField(serializer, "");
    writeField(serializer, "");

    for (HeaderKergGruppeContainer<Gruppe> gruppenContainer : gruppenContainers) {
      writeFields(serializer, gruppenContainer.getHeaderDritteZeile());
    }

    nextLine(serializer);
  }

  private List<HeaderKergGruppeContainer<Gruppe>> erstelleHeaderGruppenContainer(List<Gruppe> gruppen, ErgebnisDaten ergebnisDaten) {
    return gruppen.stream().map(gruppe -> new HeaderKergGruppeContainer<>(ergebnisDaten, gruppe)).collect(toList());
  }

  private List<Gruppe> collectSystemGruppenUndParteienWaehlergruppenVereinigungenMitListeOderDirektkandidat(ErgebnisDaten ergebnisDaten) {
    Wahldurchfuehrungsgebiet wurzelgebiet = ergebnisDaten.getWurzelgebiet();

    List<GruppeImGebiet> gruppenImWurzelgebiet = new ArrayList<>(ergebnisDaten.getGruppenImGebietZuGebiet(wurzelgebiet));
    gruppenImWurzelgebiet.sort(new GruppeImGebietByVorgegebenePositionComparator()); // im WAS sind alle Positionen fixiert...

    Set<Gruppe> gruppenMitListeOderDirektkandidat = new HashSet<>(ergebnisDaten.getDirektkandidaturen().stream().map(AbstractKandidatur::getGruppe).collect(toSet()));

    for (Liste liste : ergebnisDaten.getListen()) {
      gruppenMitListeOderDirektkandidat.add(liste.getGruppe());
    }

    return gruppenImWurzelgebiet.stream().map(GruppeImGebiet::getGruppe)
        .filter(gruppe -> gruppe.isSystemGruppe() || gruppe.isParteiWaehlergruppeOderVereinigung() && gruppenMitListeOderDirektkandidat.contains(gruppe)).collect(toList());
  }

  private void writeIntro(ErgebnisDaten ergebnisDaten, CsvSerializer serializer) {
    Ergebnis ergebnis = ergebnisDaten.getErgebnis();
    writeField(serializer, ergebnis.getWahl().getName());
    nextLine(serializer);

    // WASDE-1218
    PraesentationErgebnisArt praesentationErgebnisArt = praesentationErgebnisArtBestimmer.bestimmeErgebnisartPraesentation(ergebnisDaten.getErgebnis(), ergebnisDaten.getWurzelgebiet());

    writeField(serializer, messages.lokalisiere(praesentationErgebnisArt));
    nextLine(serializer);
  }
}
