
package de.ivu.elect.dokumente.ergebnis;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

import org.apache.commons.lang3.StringUtils;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentQueue;
import de.ivu.document.generator.template.annotations.DocumentRepeat;
import de.ivu.document.generator.template.annotations.DocumentSimpleField;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.NlErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.kandidatur.control.StimmzettelFormatter;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.dokumente.wahlvorschlag.GruppeDokument;

public abstract class AbstractNlKandidatenStimmenProListeDocument extends BaseDocument {

  private static final long serialVersionUID = 1L;

  @DocumentComplexField("Gruppe")
  public final GruppeDokument gruppe;
  //Gebietsnummer wird *nur für selbständigen Listen* ganz oben, vor die Kandidatenstimmen Tabelle,
  //in eine Zusammenfassung der Gruppe/Liste angezeigt.
  @DocumentSimpleField("Gebietsnummer")
  public final String gebietsnummer;
  //Die Nummer des Stels wird anstelle der Gebietsnummer angezeigt.
  @DocumentSimpleField("Stelnummer")
  public final Integer stelnummer;
  @DocumentSimpleField("IsEinzelneListe")
  public final Boolean isEinzelneListe;
  @DocumentSimpleField("IsStelOderGruppeIdentischerListe")
  public final Boolean isStelOderGruppeIdentischerListe;
  @DocumentSimpleField("AnzahlStimmenDerListe")
  public final Long anzahlStimmenDerListe;
  @DocumentSimpleField("AnzahlSitzeDerListe")
  public final Long anzahlSitzeDerListe;
  @DocumentSimpleField("AnzahlBeteiligtenWahlbezirke")
  public final Long anzahlBeteiligtenWahlbezirke;
  @DocumentSimpleField("IsMitGeaenderterReihenfolge")
  public final Boolean isMitGeaenderterReihenfolge;
  @DocumentSimpleField("IsMitNichtUeberVorzugGewaehlte")
  public final Boolean isMitNichtUeberVorzugGewaehlte;
  @DocumentSimpleField("IsIdentischerListe")
  public final Boolean isIdentischerListe;

  @DocumentQueue(value = "gebietsnummerKopfzeile", elementClass = String.class)
  public final LinkedList<String> gebietsnummerKopfzeile;

  @DocumentQueue(value = "gebietsnummerKopfzeile2", elementClass = String.class)
  public final LinkedList<String> gebietsnummerKopfzeile2; // ELECT-11985

  @DocumentRepeat(value = "KandidatStimmenNachStimmzettelPosition", elementClass = NlKandidatStimmenDocument.class, itemName = "kandidat")
  public final List<NlKandidatStimmenDocument> kandidatenStimmenzahl;
  @DocumentQueue(value = "GesamtStimmenzahlProGebietFuerGruppe", elementClass = String.class)
  public final LinkedList<Long> gesamtStimmenzahlProGebiet;
  @DocumentRepeat(value = "KandidatStimmenNachRang", elementClass = NlKandidatStimmenDocument.class, itemName = "kandidat")
  public final List<NlKandidatStimmenDocument> kandidatenStimmenzahlNachRang;

  public AbstractNlKandidatenStimmenProListeDocument(GruppeDokument gruppe, LinkedList<String> gebietsnummerKopfzeile, List<NlKandidatStimmenDocument> kandidatenStimmenzahl, Long anzahlSitzeDerListe,
      Long anzahlStimmenDerListe, Integer stelnummer, String gebietsnummer, Boolean mitGeaenderterReihenfolge, Boolean isIdentischerListe) {
    super();

    this.gruppe = gruppe;
    this.isMitGeaenderterReihenfolge = mitGeaenderterReihenfolge;

    this.gebietsnummerKopfzeile = new LinkedList<>(gebietsnummerKopfzeile);
    this.gebietsnummerKopfzeile2 = new LinkedList<>(gebietsnummerKopfzeile);

    this.kandidatenStimmenzahl = kandidatenStimmenzahl;
    this.anzahlSitzeDerListe = anzahlSitzeDerListe;
    this.anzahlStimmenDerListe = anzahlStimmenDerListe;
    this.stelnummer = stelnummer;
    this.isIdentischerListe = isIdentischerListe;
    this.gebietsnummer = gebietsnummer;
    long anzahlGebietsspalten = gebietsnummerKopfzeile.stream().filter(StringUtils::isNotBlank).count();
    this.isEinzelneListe = anzahlGebietsspalten == 0;
    this.isStelOderGruppeIdentischerListe = !this.isEinzelneListe;
    if (anzahlGebietsspalten == 0) {
      this.anzahlBeteiligtenWahlbezirke = 1L;
    } else {
      this.anzahlBeteiligtenWahlbezirke = anzahlGebietsspalten;
    }

    if (isEinzelneListe) {
      gesamtStimmenzahlProGebiet = LongStream.range(0, gebietsnummerKopfzeile.size()).mapToObj(l -> (Long) null).collect(Collectors.toCollection(LinkedList::new));
    } else {
      gesamtStimmenzahlProGebiet = erzeugeGesamtStimmenzahlProGebiet(kandidatenStimmenzahl, anzahlGebietsspalten);
    }

    kandidatenStimmenzahlNachRang = kandidatenStimmenzahl.stream().filter(c -> c.getListenkandidatur().isZugelassen()).sorted(Comparator.comparingLong(k -> k.rang)).collect(Collectors.toList());

    isMitNichtUeberVorzugGewaehlte = kandidatenStimmenzahl.stream().anyMatch(d -> d.isEndgueltigGewaehlt && !d.isUeberVorzugGewaehlt);
  }

  private LinkedList<Long> erzeugeGesamtStimmenzahlProGebiet(List<NlKandidatStimmenDocument> kandidatenStimmenzahl, long anzahlGebiete) {
    LinkedList<Long> result = new LinkedList<>();

    for (int gebiet = 0; gebiet < anzahlGebiete; gebiet++) {
      long stimmenzahlFuerGebiet = 0;

      for (NlKandidatStimmenDocument nlKandidatStimmenDocument : kandidatenStimmenzahl) {
        stimmenzahlFuerGebiet += nlKandidatStimmenDocument.kandidatStimmenProGebiet.get(gebiet);
      }
      result.add(stimmenzahlFuerGebiet);
    }

    return result;
  }

  protected static LinkedList<String> erzeugeKopfzeilen(List<Liste> listen) {
    return listen.stream().map(l -> l.getGebiet().getNummer()).collect(Collectors.toCollection(LinkedList::new));
  }

  protected static List<NlKandidatStimmenDocument> erzeugeKandidatenStimmenZeilen(List<Liste> listen, NlErgebnisDaten ergebnisDaten, StimmzettelFormatter stimmzettelFormatter,
      Function<PersonendatenPK, Boolean> isMitVorzugsstimmenGewaehltFunction) {
    int anzahlListenkandidaturen = listen.stream().findAny().map(l -> l.getListenkandidaturen().size()).orElse(0);
    List<NlKandidatStimmenDocument> result = new ArrayList<>();

    for (int kandidaturPosition = 0; kandidaturPosition < anzahlListenkandidaturen; kandidaturPosition++) {
      LinkedList<Long> stimmenzahlProGebiet = new LinkedList<>();
      long anzahlStimmenDesKandidats = 0L;
      boolean isUeberVorzugGewaehlt = false;
      for (Liste liste : listen) {
        Listenkandidatur kandidaturDesKandidats = liste.getListenkandidaturenSortiertNachRelativerPosition().get(kandidaturPosition);
        Wahldurchfuehrungsgebiet gebiet = liste.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
        long summeListenkandidaturListenstimmen = ergebnisDaten.getSummeListenkandidaturListenstimmen(gebiet, Stimmabgabeart.GESAMT, kandidaturDesKandidats);
        anzahlStimmenDesKandidats += summeListenkandidaturListenstimmen;
        stimmenzahlProGebiet.add(summeListenkandidaturListenstimmen);
        isUeberVorzugGewaehlt = isMitVorzugsstimmenGewaehltFunction.apply(kandidaturDesKandidats.getPk());
      }

      result.add(new NlKandidatStimmenDocument(getGewaehltFuerKandidat(listen, kandidaturPosition, ergebnisDaten.getErgebnisDaten()), stimmzettelFormatter, anzahlStimmenDesKandidats,
          stimmenzahlProGebiet, isUeberVorzugGewaehlt));
    }

    return result;
  }

  private static Gewaehlt getGewaehltFuerKandidat(List<Liste> listen, int kandidaturPosition, ErgebnisDaten ergebnisdaten) {
    return Objects
        .requireNonNull(listen.stream().findAny().map(l -> ergebnisdaten.getGewaehlteFuerKandidatur(l.getListenkandidaturenSortiertNachRelativerPosition().get(kandidaturPosition))).orElse(null));
  }

  protected static Long getAnzahlSitzeDerListe(List<Liste> listen, NlErgebnisDaten ergebnisDaten) {
    return listen.stream().map(liste -> ergebnisDaten.getErgebnisDaten().getSitzeFuerListe(liste)).filter(Objects::nonNull).findAny().map(Sitze::getAnzahl).orElse(0L);
  }

  protected static Long getAnzahlStimmenDerListe(List<Liste> listen, NlErgebnisDaten ergebnisDaten) {
    long result = 0L;
    for (Liste liste : listen) {
      Wahldurchfuehrungsgebiet entity = liste.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
      result += ergebnisDaten.getSummeListeListenstimmen(entity, Stimmabgabeart.GESAMT, liste);
    }

    return result;
  }
}
