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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.document.generator.template.annotations.Document;
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.dokumente.entity.DokumentVorlageArt;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.control.GruppeBySchluesselComparator;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.business.wahlvorschlag.entity.ListenverbindungPK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.dokumente.CoreElectContext;
import de.ivu.elect.dokumente.gebiet.GebietDokument;
import de.ivu.elect.dokumente.wahlvorschlag.CoreListenverbindungDokument;
import de.ivu.elect.dokumente.wahlvorschlag.CoreWahlvorschlagsdatenDocument;
import de.ivu.elect.dokumente.wahlvorschlag.GruppeDokument;

@Document(value = DokumentVorlageArt.LOS_SUB_TEMPLATE, isSubTemplate = true)
public class ErgebnisKonfliktDocument extends BaseDocument {

  private static final long serialVersionUID = 1L;

  private static final Comparator<GruppeDokument> POLITISCHE_GRUPPE_COMPARATOR = (one, another) -> new GruppeBySchluesselComparator().compare(one.schluessel, another.schluessel);

  private static final Comparator<CoreListenverbindungDokument> LISTENVERBINDUNG_COMPARATOR = ListenverbindungDocumentByPositionComparator.ASCENDING;

  private static final Comparator<CoreWahlvorschlagsdatenDocument> LISTE_COMPARATOR = WahlvorschlagDocumentByPositionComparator.ASCENDING;

  private static final Comparator<KandidaturMitStimmergebnissenDocument> KANDIDATUR_COMPARATOR = KandidaturDocumentByPositionComparator.ASCENDING;

  @DocumentSimpleField("Art")
  public final String konfliktArt;

  @DocumentSimpleField("Status")
  public final String konfliktStatus;

  @DocumentSimpleField("IstGelöst")
  public final boolean geloest;

  @DocumentSimpleField("Beschreibung")
  public final String konfliktBeschreibung;

  @DocumentSimpleField("Lösung")
  public final String konfliktBeschreibungLoesung;

  @DocumentSimpleField("GelöstVon")
  public final String konfliktGeloestVon;

  @DocumentSimpleField("GelöstZeitpunkt")
  public final String konfliktGeloestZeitpunkt;

  @DocumentSimpleField("IstPolitischeGruppenBeteiligt")
  public final boolean politischeGruppenBeteiligt;

  @DocumentSimpleField("IstPolitischeGruppenGelost")
  public final boolean politischeGruppenGelost;

  @DocumentRepeat(value = "BeteiligtePolitischeGruppen", elementClass = GruppeDokument.class, itemName = "politischeGruppen")
  public final List<GruppeDokument> beteiligtePolitischeGruppen;

  @DocumentRepeat(value = "GelostePolitischeGruppen", elementClass = GruppeDokument.class, itemName = "politischeGruppen")
  public final List<GruppeDokument> gelostePolitischeGruppen;

  @DocumentSimpleField("IstListenverbindungenBeteiligt")
  public final boolean listenverbindungenBeteiligt;

  @DocumentSimpleField("IstListenverbindungenGelost")
  public final boolean listenverbindungenGelost;

  @DocumentRepeat(value = "BeteiligteListenverbindungen", elementClass = WasListenverbindungDokument.class, itemName = "listenverbindung")
  public final List<WasListenverbindungDokument> beteiligteListenverbindungen;

  @DocumentRepeat(value = "GelosteListenverbindungen", elementClass = WasListenverbindungDokument.class, itemName = "listenverbindung")
  public final List<WasListenverbindungDokument> gelosteListenverbindungen;

  @DocumentSimpleField("IstListenBeteiligt")
  public final boolean listenBeteiligt;

  @DocumentSimpleField("IstListengelost")
  public final boolean listenGelost;

  @DocumentRepeat(value = "BeteiligteListen", elementClass = ListeMitStimmergebnissenDocument.class, itemName = "liste")
  public final List<ListeMitStimmergebnissenDocument> beteiligteListen;

  @DocumentRepeat(value = "GelosteListen", elementClass = ListeMitStimmergebnissenDocument.class, itemName = "liste")
  public final List<ListeMitStimmergebnissenDocument> gelosteListen;

  @DocumentSimpleField("IstKandidaturenBeteiligt")
  public final boolean kandidaturenBeteiligt;

  @DocumentSimpleField("IstKandidaturenGelost")
  public final boolean kandidaturenGelost;

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

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

  @DocumentSimpleField("IstOptionenBeteiligt")
  public final boolean optionenBeteiligt;

  @DocumentSimpleField("IstOptionenGelost")
  public final boolean optionenGelost;

  @DocumentQueue(value = "BeteiligteOptionen", elementClass = Object.class)
  public final List<Object> beteiligteOptionen;

  @DocumentQueue(value = "GelosteOptionen", elementClass = Object.class)
  public final List<Object> gelosteOptionen;

  @DocumentComplexField("Gebiet")
  public final GebietDokument gebiet;

  public ErgebnisKonfliktDocument(CoreElectContext coreElectContext, ErgebnisDaten ergebnisDaten, ErgebnisKonflikt ergebnisKonflikt,
      Map<Liste, ListeMitStimmergebnissenDocument> ergebnisDokumentPerListe, Map<Listenverbindung, WasListenverbindungDokument> ergebnisDokumentPerListenverbindung,
      Map<Kandidatur, KandidaturMitStimmergebnissenDocument> ergebnisDokumentPerKandidatur) {
    super();

    this.konfliktArt = coreElectContext.getMessage(ergebnisKonflikt.getErgebnisKonfliktArt());
    this.konfliktStatus = coreElectContext.getMessage(ergebnisKonflikt.getErgebnisKonfliktStatus());

    this.konfliktBeschreibung = ergebnisKonflikt.getKonfliktBeschreibung();
    this.konfliktBeschreibungLoesung = ergebnisKonflikt.getLoesungBeschreibung();

    this.geloest = ergebnisKonflikt.isGeloest();
    this.konfliktGeloestVon = ergebnisKonflikt.getLoesungVon();
    this.konfliktGeloestZeitpunkt = coreElectContext.formatDateTimeMedium(ergebnisKonflikt.getLoesungZeitpunkt());

    ErgebnisKonfliktCustomData customData = ergebnisKonflikt.getCustomData(ErgebnisKonfliktCustomData.class);

    Collection<PolitischeGruppe> politischeGruppenInWahl = ergebnisDaten.getPolitischeGruppen();
    Map<Gruppe, GruppeDokument> ergebnisDokumentPerGruppe = new HashMap<>();

    this.politischeGruppenBeteiligt = customData.isPolitischeGruppenBeteiligt();
    this.politischeGruppenGelost = customData.isPolitischeGruppenGelost();

    this.beteiligtePolitischeGruppen = new ArrayList<>();

    for (PolitischeGruppe politischeGruppe : customData.getBeteiligtePolitischeGruppen(politischeGruppenInWahl)) {
      this.beteiligtePolitischeGruppen.add(ergebnisDokumentPerGruppe.computeIfAbsent(politischeGruppe, GruppeDokument::new));
    }

    this.beteiligtePolitischeGruppen.sort(POLITISCHE_GRUPPE_COMPARATOR);

    this.gelostePolitischeGruppen = new ArrayList<>();

    for (PolitischeGruppe politischeGruppe : customData.getGelostePolitischeGruppen(politischeGruppenInWahl)) {
      this.gelostePolitischeGruppen.add(ergebnisDokumentPerGruppe.computeIfAbsent(politischeGruppe, GruppeDokument::new));
    }

    this.gelostePolitischeGruppen.sort(POLITISCHE_GRUPPE_COMPARATOR);

    Collection<Listenverbindung> listenverbindungenInWahl = ergebnisDaten.getListenverbindungen();

    this.listenverbindungenBeteiligt = customData.isListenverbindungenBeteiligt();
    this.listenverbindungenGelost = customData.isListenverbindungenGelost();

    this.beteiligteListenverbindungen = new ArrayList<>();

    for (Listenverbindung listenverbindung : customData.getBeteiligteListenverbindungen(listenverbindungenInWahl)) {
      if (ergebnisDokumentPerListenverbindung.containsKey(listenverbindung)) {
        this.beteiligteListenverbindungen.add(ergebnisDokumentPerListenverbindung.get(listenverbindung));
      }
    }

    this.beteiligteListenverbindungen.sort(LISTENVERBINDUNG_COMPARATOR);

    this.gelosteListenverbindungen = new ArrayList<>();

    for (Listenverbindung listenverbindung : customData.getGelosteListenverbindungen(listenverbindungenInWahl)) {
      if (ergebnisDokumentPerListenverbindung.containsKey(listenverbindung)) {
        this.gelosteListenverbindungen.add(ergebnisDokumentPerListenverbindung.get(listenverbindung));
      }
    }

    this.gelosteListenverbindungen.sort(LISTENVERBINDUNG_COMPARATOR);

    Collection<Liste> listenInWahl = ergebnisDaten.getListen();

    this.listenBeteiligt = customData.isListenBeteiligt();
    this.listenGelost = customData.isListenGelost();

    this.beteiligteListen = new ArrayList<>();

    for (Liste liste : customData.getBeteiligteListen(listenInWahl)) {
      if (ergebnisDokumentPerListe.containsKey(liste)) {
        this.beteiligteListen.add(ergebnisDokumentPerListe.get(liste));
      }
    }

    this.beteiligteListen.sort(LISTE_COMPARATOR);

    this.gelosteListen = new ArrayList<>();

    for (Liste liste : customData.getGelosteListen(listenInWahl)) {
      if (ergebnisDokumentPerListe.containsKey(liste)) {
        this.gelosteListen.add(ergebnisDokumentPerListe.get(liste));
      }
    }

    this.gelosteListen.sort(LISTE_COMPARATOR);

    Collection<Kandidatur> kandidaturenInWahl = ergebnisDaten.getKandidaturen();

    this.kandidaturenBeteiligt = customData.isKandidaturenBeteiligt();
    this.kandidaturenGelost = customData.isKandidaturenGelost();

    this.beteiligteKandidaturen = new ArrayList<>();

    for (Kandidatur kandidatur : customData.getBeteiligteKandidaturen(kandidaturenInWahl)) {
      if (ergebnisDokumentPerKandidatur.containsKey(kandidatur)) {
        this.beteiligteKandidaturen.add(ergebnisDokumentPerKandidatur.get(kandidatur));
      }
    }

    this.beteiligteKandidaturen.sort(KANDIDATUR_COMPARATOR);

    this.gelosteKandidaturen = new ArrayList<>();

    for (Kandidatur kandidatur : customData.getGelosteKandidaturen(kandidaturenInWahl)) {
      if (ergebnisDokumentPerKandidatur.containsKey(kandidatur)) {
        this.gelosteKandidaturen.add(ergebnisDokumentPerKandidatur.get(kandidatur));
      }
    }

    this.gelosteKandidaturen.sort(KANDIDATUR_COMPARATOR);

    List<Gebiet> gebieteInWahl = new ArrayList<>(ergebnisDaten.getGebiete());
    Gebiet gebietEntity = ergebnisKonflikt.getGebiet(gebieteInWahl, kandidaturenInWahl, listenInWahl, listenverbindungenInWahl).orElse(null);

    if (gebietEntity == null) {
      this.gebiet = GebietDokument.EMPTY_OBJECT;
    } else {
      this.gebiet = new GebietDokument(coreElectContext, gebietEntity);
    }

    this.beteiligteOptionen = new ArrayList<>();
    this.gelosteOptionen = new ArrayList<>();

    if (customData instanceof SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData) {
      SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData optionenCustomData = (SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData) customData;

      this.beteiligteOptionen.addAll(optionenCustomData.getBeteiligteOptionen());
      this.gelosteOptionen.addAll(optionenCustomData.getGelosteOptionen());

      for (Object entityPK : optionenCustomData.getBeteiligteEntityPKs()) {
        if (entityPK instanceof GruppePK) {
          PolitischeGruppe politischeGruppe = Gruppe.mappeNachPk(politischeGruppenInWahl).get(entityPK);

          if (politischeGruppe != null && beteiligtePolitischeGruppen.isEmpty()) {
            this.beteiligtePolitischeGruppen.add(ergebnisDokumentPerGruppe.computeIfAbsent(politischeGruppe, GruppeDokument::new));
          }
        } else if (entityPK instanceof WahlvorschlagPK) {
          Wahlvorschlag wahlvorschlag = Wahlvorschlag.mappeNachPk(ergebnisDokumentPerListe.keySet()).get(entityPK);

          if (wahlvorschlag != null && beteiligteListen.isEmpty() && ergebnisDokumentPerListe.containsKey(wahlvorschlag)) {
            this.beteiligteListen.add(ergebnisDokumentPerListe.get(wahlvorschlag));
          }
        } else if (entityPK instanceof ListenverbindungPK) {
          Listenverbindung listenverbindung = Listenverbindung.mappeNachPk(ergebnisDokumentPerListenverbindung.keySet()).get(entityPK);

          if (listenverbindung != null && beteiligteListenverbindungen.isEmpty() && ergebnisDokumentPerListenverbindung.containsKey(listenverbindung)) {
            this.beteiligteListenverbindungen.add(ergebnisDokumentPerListenverbindung.get(listenverbindung));
          }
        } else if (entityPK instanceof PersonendatenPK) {
          Personendaten kandidatur = Personendaten.mappeNachPk(ergebnisDokumentPerKandidatur.keySet()).get(entityPK);

          if (kandidatur != null && beteiligteKandidaturen.isEmpty() && ergebnisDokumentPerKandidatur.containsKey(kandidatur)) {
            this.beteiligteKandidaturen.add(ergebnisDokumentPerKandidatur.get(kandidatur));
          }
        }
      }
    }

    this.optionenBeteiligt = !this.beteiligteOptionen.isEmpty();
    this.optionenGelost = !this.gelosteOptionen.isEmpty();
  }
}
