package de.ivu.elect.dokumente.ergebnis;

import static java.util.stream.Collectors.toCollection;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.document.generator.template.annotations.DocumentQueue;
import de.ivu.document.generator.template.annotations.DocumentSimpleField;
import de.ivu.elect.business.ergebnis.control.NlErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

public class ListenstimmenProGruppeZeileDocument extends BaseDocument {

  private static final long serialVersionUID = 1L;

  @DocumentSimpleField("Position")
  public final Long position;

  @DocumentSimpleField("Anzahl-Stimmen-Der-Gruppe")
  public final Long anzahlStimmenDerGruppe;

  @DocumentSimpleField("Anzahl-Stimmen-Der-Gruppe-Gewichtet")
  public final Long anzahlStimmenDerGruppeGewichtetMitVoteValues;

  @DocumentQueue(value = "Anzahl-Stimmen-Pro-Liste", elementClass = Long.class)
  public final LinkedList<String> gruppeStimmenProListe;

  @DocumentQueue(value = "Anzahl-Stimmen-Pro-Liste-Gewichtet", elementClass = Long.class)
  public final LinkedList<String> gruppeStimmenProListeGewichtetMitVoteValues;

  public ListenstimmenProGruppeZeileDocument(NlErgebnisDatenCustomDataDocumentContext context, List< Wahldurchfuehrungsgebiet> wahlkreise, PolitischeGruppe politischeGruppe) {
    super();

    NlErgebnisDaten nlErgebnisDaten = context.getNlErgebnisDaten();
    Stimmabgabeart stimmabgabeart = context.getStimmabgabeart();

    this.position = politischeGruppe.getRelativePosition();

    this.anzahlStimmenDerGruppe = nlErgebnisDaten.getSummeGruppeListenstimmen(politischeGruppe, stimmabgabeart);
    this.anzahlStimmenDerGruppeGewichtetMitVoteValues = nlErgebnisDaten.getSummeGruppeListenstimmenGewichtetMitVoteValues(politischeGruppe, stimmabgabeart);

    Set<Liste> listenDerGruppe = context.getListen(politischeGruppe);

    this.gruppeStimmenProListe = collectGruppeStimmenInWahlkreisFormatted(wahlkreise, listenDerGruppe, (wahldurchfuehrungsgebiet, liste) -> nlErgebnisDaten.getSummeListeListenstimmen(wahldurchfuehrungsgebiet, stimmabgabeart, liste));
    this.gruppeStimmenProListeGewichtetMitVoteValues = collectGruppeStimmenInWahlkreisFormatted(wahlkreise, listenDerGruppe,  (wahldurchfuehrungsgebiet, liste) -> nlErgebnisDaten.getSummeListeListenstimmenGewichtetMitVoteValues(wahldurchfuehrungsgebiet, stimmabgabeart, liste));
  }

  private static LinkedList<String> collectGruppeStimmenInWahlkreisFormatted(List<Wahldurchfuehrungsgebiet> wahlkreise, Set<Liste> listenDerGruppe, BiFunction<Wahldurchfuehrungsgebiet, Liste, Long> summeListeListenstimmenFct) {
    Map<String, Long> stimmenzahlProGebietsnummerMap = collectGruppeStimmenPerWahlkreisNummer(wahlkreise, listenDerGruppe, summeListeListenstimmenFct);

    return wahlkreise.stream()
            .map(g -> {
              Long stimmenzahlFuerGebiet = stimmenzahlProGebietsnummerMap.get(g.getNummer());

              if (stimmenzahlFuerGebiet != null) {
                return stimmenzahlFuerGebiet.toString();
              }

              return ".";
            }).collect(toCollection(LinkedList::new));
  }

  private static Map<String, Long> collectGruppeStimmenPerWahlkreisNummer(List<Wahldurchfuehrungsgebiet> wahlkreise, Set<Liste> listenDerGruppe, BiFunction<Wahldurchfuehrungsgebiet, Liste, Long> summeListeListenstimmenFct) {
    return wahlkreise.stream()
            .map(wahlkreis -> listenDerGruppe.stream()
                    .filter(liste -> liste.isInGebietshierarchie(wahlkreis))
                    .map(liste -> new SimpleImmutableEntry<>(wahlkreis.getNummer(), summeListeListenstimmenFct.apply(wahlkreis, liste)))
                    .findAny()
                    .orElse(new SimpleImmutableEntry<>(wahlkreis.getNummer(), null)))
            .collect(HashMap::new, (map, entry)->map.put(entry.getKey(), entry.getValue()), HashMap::putAll); // https://bugs.openjdk.java.net/browse/JDK-8148463
  }
}
