
package de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Collections.emptySet;
import static java.util.Collections.sort;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import de.ivu.document.generator.template.annotations.Document;
import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentRepeat;
import de.ivu.document.generator.template.annotations.DocumentSubTemplate;
import de.ivu.elect.business.dokumente.entity.DokumentVorlageArt;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.ErgebnisLogEintrag;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.niederschrift.boundary.NiederschriftFormatter;
import de.ivu.elect.business.niederschrift.entity.Niederschrift;
import de.ivu.elect.business.niederschrift.entity.NiederschriftArt;
import de.ivu.elect.business.niederschrift.entity.NiederschriftByGebietNummerAndNiederschriftArtComparator;
import de.ivu.elect.business.person.control.EntityMitPersonendatenByNamenComparator;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.dokumente.BaseElectContext;
import de.ivu.elect.dokumente.WasElectContext;
import de.ivu.elect.dokumente.kandidatur.KandidaturCoreDaten;
import de.ivu.elect.dokumente.wahlausschuss.WahlausschussDokument;
import de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis.dhondt.DHondtQuotientenDocument;

@Document(DokumentVorlageArt.GESAMT_ERGEBNIS_DOKUMENT_PREFIX)
public class GesamtErgebnisDokument extends BaseErgebnisDokument {

  private static final long serialVersionUID = 1L;

  @DocumentRepeat(value = "GebieteMitListen", elementClass = GebietMitListenDocument.class, itemName = "gebietMitListen")
  public final List<GebietMitListenDocument> gebieteMitListen;

  @DocumentRepeat(value = "GebieteMitDirektkandidaten", elementClass = GebietMitDirektkandidatenDocument.class, itemName = "gebietMitDirektkandidaten")
  public final List<GebietMitDirektkandidatenDocument> gebieteMitDirektkandidaten;

  @DocumentRepeat(value = "GebieteMitReferendumVorlagen", elementClass = GebietMitReferendumVorlagenDocument.class, itemName = "gebietMitReferendumVorlagen")
  public final List<GebietMitReferendumVorlagenDocument> gebieteMitReferendumVorlagen;

  @DocumentRepeat(value = "GebieteMitDetails", elementClass = GebietMitDetails.class, itemName = "gebietMitDetails")
  public final List<GebietMitDetails> gebieteMitDetails;

  @DocumentRepeat(value = "NiederschriftÜbersichten", elementClass = NiederschriftUebersichtDocument.class, itemName = "niederschriftUebersicht")
  public final List<NiederschriftUebersichtDocument> niederschriftUebersichten;

  /**
   * @deprecated
   * @see GesamtErgebnisDokument#gewaehltMarkierteKandidaturenNachRang
   */
  @Deprecated
  @DocumentRepeat(value = "GewähltMarkierteKandidaturen", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "kandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> gewaehltMarkierteKandidaturen;

  @DocumentRepeat(value = "GewähltMarkierteKandidaturenNachRang", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "kandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> gewaehltMarkierteKandidaturenNachRang;

  @DocumentRepeat(value = "GewähltMarkierteKandidaturenNachName", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "kandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> gewaehltMarkierteKandidaturenNachName; // ELECT-10626

  @DocumentRepeat(value = "KandidaturenNachWahlgebietenZulassungVorZulassungsausschussVerloren", elementClass = KandidaturMitStimmergebnissenDocument.class, itemName = "kandidatur")
  public final List<KandidaturMitStimmergebnissenDocument> kandidaturenNachWahlgebietenZulassungVorZulassungsausschussVerloren;

  @DocumentComplexField("ErgebnisGewähltMarkiert")
  public final ErgebnisGewaehltMarkiertDocument ergebnisGewaehltMarkiert; // ELECT-9429

  @DocumentSubTemplate
  @DocumentComplexField("Wahlausschuss")
  public final WahlausschussDokument wahlausschuss;

  /**
   * @deprecated
   * @see GesamtErgebnisDokument#wahlausschuss
   */
  @Deprecated
  @DocumentSubTemplate
  @DocumentComplexField("Wahlauschuss")
  public final WahlausschussDokument wahlauschuss;

  @DocumentSubTemplate
  @DocumentComplexField("DHondtQuotienten")
  public final DHondtQuotientenDocument dhondt;

  @DocumentRepeat(value = "ErgebnisLogEintraege", elementClass = ErgebnisLogEintragDokument.class, itemName = "ergebnisLogEintrag")
  public final List<ErgebnisLogEintragDokument> ergebnisLogEintraege;

  public GesamtErgebnisDokument(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, List<Wahldurchfuehrungsgebiet> gebiete, Stimmabgabeart stimmabgabeart,
      List<Wahldurchfuehrungsgebiet> gebieteMitDetails, KandidaturCoreDaten kandidaturCoreDaten, NiederschriftFormatter niederschriftFormatter, List<Niederschrift> niederschriftenInWahl) {
    super(wasElectContext, ergebnisDaten, gebiete, stimmabgabeart);

    Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> wahlgebietUebergeordnetDokumentPerGebiet = collectWahlgebietUebergeordnetDokumentPerGebiet(wasElectContext, ergebnisDaten, gebiete);

    Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur = new HashMap<>();
    Map<Liste, ListeMitStimmergebnissenDocument> ergebnisDokumentPerListe = new HashMap<>();
    Map<Listenverbindung, WasListenverbindungDokument> ergebnisDokumentPerListenverbindung = new HashMap<>();

    this.gebieteMitListen = erstelleGebieteMitListen(wasElectContext, ergebnisDaten, gebiete, wahlgebietUebergeordnetDokumentPerGebiet, ergebnisDokumentPerListe, ergebnisDokumentPerListenverbindung,
        kandidaturCoreDaten, ergebnisDokumentPerKandidatur);
    this.gebieteMitDirektkandidaten = erstelleGebieteMitDirektkandidaten(wasElectContext, ergebnisDaten, gebiete, wahlgebietUebergeordnetDokumentPerGebiet, ergebnisDokumentPerListe,
        ergebnisDokumentPerListenverbindung, kandidaturCoreDaten, ergebnisDokumentPerKandidatur);
    this.gebieteMitReferendumVorlagen = erstelleGebieteMitReferendumVorlagen(wasElectContext, ergebnisDaten, gebiete, wahlgebietUebergeordnetDokumentPerGebiet);

    this.gebieteMitDetails = erstelleGebieteMitDetails(wasElectContext, ergebnisDaten, gebieteMitDetails, wahlgebietUebergeordnetDokumentPerGebiet, kandidaturCoreDaten, ergebnisDokumentPerKandidatur);

    this.gewaehltMarkierteKandidaturenNachRang = collectGewaehltMarkierteKandidaturenSortedByRang(ergebnisDokumentPerKandidatur);
    this.gewaehltMarkierteKandidaturenNachName = collectGewaehltMarkierteKandidaturenSortedByName(ergebnisDokumentPerKandidatur);
    this.gewaehltMarkierteKandidaturen = this.gewaehltMarkierteKandidaturenNachRang;

    this.ergebnisGewaehltMarkiert = new ErgebnisGewaehltMarkiertDocument(wasElectContext, this.gewaehltMarkierteKandidaturenNachRang);

    this.niederschriftUebersichten = erstelleNiederschriftUebersichten(wasElectContext, niederschriftFormatter, niederschriftenInWahl);

    this.wahlausschuss = new WahlausschussDokument(wasElectContext, ergebnisDaten.getWurzelgebiet(), new HashMap<>(ergebnisDaten.getWahlausschussmitgliederAufGebiet()));
    this.wahlauschuss = this.wahlausschuss;

    this.ergebnisLogEintraege = erzeugeErgebnisLogEintragDokumente(wasElectContext, ergebnisDaten.getErgebnisLogeintraege());
    this.dhondt = new DHondtQuotientenDocument(wasElectContext, ergebnisDaten, ergebnisDokumentPerListe);

    // ELECT-9299
    this.kandidaturenNachWahlgebietenZulassungVorZulassungsausschussVerloren = ergebnisDokumentPerKandidatur.values().stream()
        .filter(ergebnisDokument -> ergebnisDokument.getKandidatur().isZugelassenObwohlNichtZulassungsfaehig()).sorted().collect(Collectors.toList());

    for (KandidaturMitStimmergebnissenDocument kandidaturMitStimmergebnissenDocument : this.kandidaturenNachWahlgebietenZulassungVorZulassungsausschussVerloren) {
      Kandidatur kandidatur = kandidaturMitStimmergebnissenDocument.getKandidatur();
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = kandidaturMitStimmergebnissenDocument.getWahldurchfuehrungsgebiet();

      for (Wahldurchfuehrungsgebiet erfassungsgebiet : ergebnisDaten.getErfassungsgebieteUnterhalbVon(wahldurchfuehrungsgebiet)) {
        kandidaturMitStimmergebnissenDocument.kandidaturstimmenAufErfassungsgebieten.add(new KandidaturMitStimmergebnissenDocument(wasElectContext, ergebnisDaten,
            new GebietUndStimmabgabeart(erfassungsgebiet, stimmabgabeart), kandidaturMitStimmergebnissenDocument.wahlvorschlag, kandidaturCoreDaten, kandidatur, 0L));
      }

      Collections.sort(kandidaturMitStimmergebnissenDocument.kandidaturstimmenAufErfassungsgebieten);
    }
  }

  private List<ErgebnisLogEintragDokument> erzeugeErgebnisLogEintragDokumente(BaseElectContext baseElectContext, List<ErgebnisLogEintrag> ergebnisLogeintraege) {
    return ergebnisLogeintraege.stream().map(ergebnisLogEintrag -> new ErgebnisLogEintragDokument(baseElectContext, ergebnisLogEintrag)).collect(Collectors.toList());
  }

  private List<KandidaturMitStimmergebnissenDocument> collectGewaehltMarkierteKandidaturenSortedByRang(Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    List<KandidaturMitStimmergebnissenDocument> result = new ArrayList<>();

    for (KandidaturMitStimmergebnissenDocument kandidaturMitStimmergebnissenDocument : ergebnisDokumentPerKandidatur.values()) {
      if (kandidaturMitStimmergebnissenDocument.gewaehltVorhanden) {
        result.add(kandidaturMitStimmergebnissenDocument);
      }
    }

    sort(result, KandidaturDocumentByRangComparator.ASCENDING);

    return result;
  }

  private List<KandidaturMitStimmergebnissenDocument> collectGewaehltMarkierteKandidaturenSortedByName(Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    List<KandidaturMitStimmergebnissenDocument> result = new ArrayList<>();

    List<Kandidatur> kandidaturenSorted = new ArrayList<>(ergebnisDokumentPerKandidatur.keySet());
    kandidaturenSorted.sort(EntityMitPersonendatenByNamenComparator.ASCENDING);

    for (Kandidatur kandidatur : kandidaturenSorted) {
      KandidaturMitStimmergebnissenDocument kandidaturMitStimmergebnissenDocument = ergebnisDokumentPerKandidatur.get(kandidatur);

      if (kandidaturMitStimmergebnissenDocument.gewaehltVorhanden) {
        result.add(kandidaturMitStimmergebnissenDocument);
      }
    }

    return result;
  }

  private List<NiederschriftUebersichtDocument> erstelleNiederschriftUebersichten(BaseElectContext baseElectContext, NiederschriftFormatter niederschriftFormatter,
      Collection<Niederschrift> niederschriften) {
    Comparator<Niederschrift> niederschriftComparator = new NiederschriftByGebietNummerAndNiederschriftArtComparator(baseElectContext::getMessage);

    return erstelleNiederschriftUebersichten(baseElectContext, niederschriftFormatter, niederschriftComparator, niederschriften);
  }

  static List<NiederschriftUebersichtDocument> erstelleNiederschriftUebersichten(BaseElectContext baseElectContext, NiederschriftFormatter niederschriftFormatter,
      Comparator<Niederschrift> niederschriftComparator, Collection<Niederschrift> niederschriften) {
    List<NiederschriftUebersichtDocument> result = new ArrayList<>();

    Map<NiederschriftArt, Set<Niederschrift>> niederschriftenPerArt = new HashMap<>();

    for (Niederschrift niederschrift : niederschriften) {
      niederschriftenPerArt.computeIfAbsent(niederschrift.getNiederschriftArt(), x -> new HashSet<>()).add(niederschrift);
    }

    for (NiederschriftArt niederschriftArt : NiederschriftArt.values()) {
      List<Niederschrift> niederschriftenSorted = new ArrayList<>(niederschriftenPerArt.getOrDefault(niederschriftArt, /* ELECTVAPP-583 */ emptySet()));
      niederschriftenSorted.sort(niederschriftComparator);

      result.add(new NiederschriftUebersichtDocument(baseElectContext, niederschriftFormatter, niederschriftArt, niederschriftenSorted));
    }

    return result;
  }

  private List<GebietMitDetails> erstelleGebieteMitDetails(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, List<Wahldurchfuehrungsgebiet> gebieteMitDetails,
      Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> wahlgebietUebergeordnetDokumentPerGebiet, KandidaturCoreDaten kandidaturCoreDaten,
      Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    List<GebietMitDetails> result = new ArrayList<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : gebieteMitDetails) {
      result.add(erstelleGebietMitDetails(wasElectContext, ergebnisDaten, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, getStimmabgabeart()),
          wahlgebietUebergeordnetDokumentPerGebiet.get(wahldurchfuehrungsgebiet), kandidaturCoreDaten, ergebnisDokumentPerKandidatur));
    }

    return result;
  }

  static GebietMitDetails erstelleGebietMitDetails(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart,
      GebietMitStimmenDocument wahlgebietUebergeordnet, KandidaturCoreDaten kandidaturCoreDaten, Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    return new GebietMitDetails(wasElectContext, ergebnisDaten, gebietUndStimmabgabeart, wahlgebietUebergeordnet, kandidaturCoreDaten, ergebnisDokumentPerKandidatur);
  }

  private List<GebietMitDirektkandidatenDocument> erstelleGebieteMitDirektkandidaten(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, Collection<Wahldurchfuehrungsgebiet> gebiete,
      Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> wahlgebietUebergeordnetDokumentPerGebiet, Map<Liste, ListeMitStimmergebnissenDocument> ergebnisDokumentPerListe,
      Map<Listenverbindung, WasListenverbindungDokument> ergebnisDokumentPerListenverbindung, KandidaturCoreDaten kandidaturCoreDaten,
      Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    List<GebietMitDirektkandidatenDocument> result = new ArrayList<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : gebiete) {
      if (wahldurchfuehrungsgebiet.isDirektwahlgebiet()) {
        result.add(new GebietMitDirektkandidatenDocument(wasElectContext, ergebnisDaten, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, getStimmabgabeart()),
            wahlgebietUebergeordnetDokumentPerGebiet.get(wahldurchfuehrungsgebiet), ergebnisDokumentPerListe, ergebnisDokumentPerListenverbindung, kandidaturCoreDaten, ergebnisDokumentPerKandidatur));
      }
    }

    return result;
  }

  private List<GebietMitReferendumVorlagenDocument> erstelleGebieteMitReferendumVorlagen(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, Collection<Wahldurchfuehrungsgebiet> gebiete,
      Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> wahlgebietUebergeordnetDokumentPerGebiet) {
    List<GebietMitReferendumVorlagenDocument> result = new ArrayList<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : gebiete) {
      if (wahldurchfuehrungsgebiet.isReferendumgebiet()) {
        result.add(new GebietMitReferendumVorlagenDocument(wasElectContext, ergebnisDaten, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, getStimmabgabeart()),
            wahlgebietUebergeordnetDokumentPerGebiet.get(wahldurchfuehrungsgebiet)));
      }
    }

    return result;
  }

  private List<GebietMitListenDocument> erstelleGebieteMitListen(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, Collection<Wahldurchfuehrungsgebiet> gebiete,
      Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> wahlgebietUebergeordnetDokumentPerGebiet, Map<Liste, ListeMitStimmergebnissenDocument> ergebnisDokumentPerListe,
      Map<Listenverbindung, WasListenverbindungDokument> ergebnisDokumentPerListenverbindung, KandidaturCoreDaten kandidaturCoreDaten,
      Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    List<GebietMitListenDocument> result = new ArrayList<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : gebiete) {
      if (wahldurchfuehrungsgebiet.isListenwahlgebiet()) {
        result.add(new GebietMitListenDocument(wasElectContext, ergebnisDaten, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, getStimmabgabeart()),
            wahlgebietUebergeordnetDokumentPerGebiet.get(wahldurchfuehrungsgebiet), ergebnisDokumentPerListe, ergebnisDokumentPerListenverbindung, kandidaturCoreDaten, ergebnisDokumentPerKandidatur));
      }
    }

    return result;
  }

  private Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> collectWahlgebietUebergeordnetDokumentPerGebiet(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten,
      Collection<Wahldurchfuehrungsgebiet> gebiete) {
    return collectWahlgebietUebergeordnetDokumentPerGebiet(wasElectContext, ergebnisDaten, gebiete, getStimmabgabeart());
  }

  static Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> collectWahlgebietUebergeordnetDokumentPerGebiet(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten,
      Collection<Wahldurchfuehrungsgebiet> gebiete, Stimmabgabeart stimmabgabeart) {
    Map<Wahldurchfuehrungsgebiet, GebietMitStimmenDocument> result = new HashMap<>();

    Map<Gebiet, GebietMitStimmenDocument> dokumentPerWahlgebietUebergeordnet = new HashMap<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : gebiete) {
      Gebiet wahlgebietUebergeordnet = wahldurchfuehrungsgebiet.getUebergeordnetesWahlgebiet();
      GebietMitStimmenDocument wahlgebietUebergeordnetDokument = null;

      if (wahlgebietUebergeordnet != null) {
        wahlgebietUebergeordnetDokument = dokumentPerWahlgebietUebergeordnet.computeIfAbsent(wahlgebietUebergeordnet,
            wahlgebiet -> new GebietMitStimmenDocument(wasElectContext, ergebnisDaten, new GebietUndStimmabgabeart(castTo(wahlgebiet, Wahldurchfuehrungsgebiet.class), stimmabgabeart)));
      }

      result.put(wahldurchfuehrungsgebiet, wahlgebietUebergeordnetDokument);
    }

    return result;
  }
}
