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

import java.util.Collections;
import java.util.Map;
import java.util.function.Predicate;

import javax.annotation.Nullable;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentSimpleField;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
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.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis.berechner.GueltigeErstUndZweitstimmenAktuellMitVorperiodeBerechner;
import de.ivu.elect.was.exchange.ergebnis.dokumente.util.WahlbuchNumberUtil;

/**
 *  on 14.02.2017.
 */
public abstract class PolitischeGruppeErgebnisAufGebietDokument extends BaseDocument {

  @DocumentComplexField("Gruppe")
  public final WahlbuchGruppeDokument gruppe;

  // Wird für die Zusammenfassung von Einzelbewerbern und Parteien, die nur um Erststimmen konkurrieren, benötigt.
  @DocumentSimpleField("IstKonkurrenzNurUmErststimmen")
  public final boolean konkurrenzNurUmErststimmen;

  @DocumentSimpleField("IstKonkurrenzNurUmZweitstimmen")
  public final boolean konkurrenzNurUmZweitstimmen;

  @DocumentSimpleField("HatStimmen")
  public final boolean hatStimmen;

  @DocumentSimpleField("HatErststimmenInGebietshierarchie")
  public final boolean hatErststimmenInGebietshierarchie;

  @DocumentSimpleField("HatZweitstimmenInGebietshierarchie")
  public final boolean hatZweitstimmenInGebietshierarchie;

  public final Long anzahlSitzeGesamt;
  public final Long anzahlSitzeVorperiode;

  @DocumentSimpleField("AnzahlSitzeGesamtFormatiert")
  public final String anzahlSitzeGesamtFormatiert;

  @DocumentSimpleField("AnzahlSitzeVorperiodeFormatiert")
  public final String anzahlSitzeVorperiodeFormatiert;

  @DocumentSimpleField("AnzahlSitzeDifferenzFormatiert")
  public final String anzahlSitzeDifferenzFormatiert;

  // Im Sinne des SonstigeParteienPruefer's: Wenn isTeilnehmerAnVerteilung == false ist, dann wird die Partei unter "Sonstige" geführt.
  @DocumentSimpleField("IstTeilnehmerAnVerteilung")
  public final boolean isTeilnehmerAnVerteilung;

  protected Direktkandidat direktkandidatAufGebiet;

  @DocumentSimpleField("HatDirektkandidatAufGebiet")
  public final boolean hasDirektkandidatAufGebiet;

  public PolitischeGruppeErgebnisAufGebietDokument(WahlbuchParameter parameter,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, @Nullable Gebietsgruppierung gebietsgruppierung,
      PolitischeGruppe politischeGruppe, boolean isKeineSonstigePartei) {

    ErgebnisDaten ergebnisDaten = parameter.getErgebnisDaten();
    GebietUndStimmabgabeart gebietUndStimmabgabeart = createGebietUndStimmabgabeart(wahldurchfuehrungsgebiet,
        gebietsgruppierung);

    this.gruppe = new WahlbuchGruppeDokument(politischeGruppe);
    this.isTeilnehmerAnVerteilung = isKeineSonstigePartei;

    hatErststimmenInGebietshierarchie = hatErststimmenInGebietshierarchie(politischeGruppe,
        gebietUndStimmabgabeart, ergebnisDaten.getStimmen());
    hatZweitstimmenInGebietshierarchie = hatZweitstimmenInGebietshierarchie(politischeGruppe,
        gebietUndStimmabgabeart, ergebnisDaten.getStimmen());

    boolean hatDirektkandidatInGebietshierarchie = hatDirektkandidatInGebietshierarchie(politischeGruppe,
        wahldurchfuehrungsgebiet, ergebnisDaten);
    boolean hatListeInGebietshierarchie = hatListeInGebietshierarchie(politischeGruppe, wahldurchfuehrungsgebiet,
        ergebnisDaten);

    // Note: "Konkurrenz" um Stimmen heißt nicht, dass diese erfasst sein müssen. Das Kriterium ist deshalb die Existenz der Kandidatur, nicht der Stimmen. gab
    // WASDE-2084: Die Ergebnisse der Parteien, die nur mit Erststimmen angetreten sind, sollten im Heft auch einzeln aufgeführt und nicht zu Übrige gezählt werden.
    konkurrenzNurUmErststimmen = hatDirektkandidatInGebietshierarchie && !hatListeInGebietshierarchie && !politischeGruppe.isPartei();
    konkurrenzNurUmZweitstimmen = !hatDirektkandidatInGebietshierarchie && hatListeInGebietshierarchie;
    hatStimmen = hatErststimmenInGebietshierarchie || hatZweitstimmenInGebietshierarchie;

    this.anzahlSitzeGesamt = bestimmeSitzeFuerGruppeAufGebietUndDarunter(parameter, politischeGruppe,
        wahldurchfuehrungsgebiet);
    this.anzahlSitzeGesamtFormatiert = parameter.getFormatter().formatMandatszahl(this.anzahlSitzeGesamt);

    this.anzahlSitzeVorperiode = bestimmeSitzeVorperiodeFuerGruppeAufGebietUndDarunter(parameter, politischeGruppe,
        wahldurchfuehrungsgebiet);
    this.anzahlSitzeVorperiodeFormatiert = parameter.getFormatter().formatMandatszahl(this.anzahlSitzeVorperiode);
    
    this.anzahlSitzeDifferenzFormatiert = bestimmeSitzeDifferenz(parameter, this.anzahlSitzeGesamt, this.anzahlSitzeVorperiode);

    direktkandidatAufGebiet = bestimmeDirektkandidatAufGebiet(politischeGruppe, wahldurchfuehrungsgebiet);
    if (direktkandidatAufGebiet != null) {
      this.hasDirektkandidatAufGebiet = true;
    } else {
      this.hasDirektkandidatAufGebiet = false;
    }
  }

  protected GebietUndStimmabgabeart createGebietUndStimmabgabeart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      @Nullable Gebietsgruppierung gebietsgruppierung) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, gebietsgruppierung, Stimmabgabeart.GESAMT);
  }

  protected StimmenAktuellMitVorperiode getStimmenGesamt(ErgebnisDaten ergebnisDaten,
      GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return new GueltigeErstUndZweitstimmenAktuellMitVorperiodeBerechner(ergebnisDaten, gebietUndStimmabgabeart)
        .getStimmenAktuellMitVorperiode();
  }

  protected StimmenAktuellMitVorperiode getStimmenGruppe(ErgebnisDaten ergebnisDaten,
      GebietUndStimmabgabeart gebietUndStimmabgabeart, PolitischeGruppe politischeGruppe) {
    // TODO: ELECT-4376 Wenn Gruppe auf Gebiet nicht antritt, oder nicht um eine Stimmart (Erst- oder Zweit-) konkurriert "null" statt "0" einsetzen. gab

    Long anzahlErststimmenGruppe = null;
    if (hatErststimmenInGebietshierarchie(politischeGruppe, gebietUndStimmabgabeart, ergebnisDaten.getStimmen())) {
      anzahlErststimmenGruppe = ergebnisDaten.getErststimmenAktuell(gebietUndStimmabgabeart).get(politischeGruppe);
    }

    Long anzahlErststimmenGruppeVorperiode = null;
    if (hatErststimmenInGebietshierarchie(politischeGruppe, gebietUndStimmabgabeart,
        ergebnisDaten.getStimmenVorperiodeAlsPool())) {
      anzahlErststimmenGruppeVorperiode = ergebnisDaten.getErststimmenVorperiode(gebietUndStimmabgabeart)
          .get(politischeGruppe);
    }

    Long anzahlZweitstimmenGruppe = null;
    if (hatZweitstimmenInGebietshierarchie(politischeGruppe, gebietUndStimmabgabeart, ergebnisDaten.getStimmen())) {
      anzahlZweitstimmenGruppe = ergebnisDaten.getZweitstimmenAktuell(gebietUndStimmabgabeart).get(politischeGruppe);
    }

    Long anzahlZweitstimmenGruppeVorperiode = null;
    if (hatZweitstimmenInGebietshierarchie(politischeGruppe, gebietUndStimmabgabeart,
        ergebnisDaten.getStimmenVorperiodeAlsPool())) {
      anzahlZweitstimmenGruppeVorperiode = ergebnisDaten.getZweitstimmenVorperiode(gebietUndStimmabgabeart)
          .get(politischeGruppe);
    }

    return new StimmenAktuellMitVorperiode(anzahlErststimmenGruppe, anzahlErststimmenGruppeVorperiode,
        anzahlZweitstimmenGruppe, anzahlZweitstimmenGruppeVorperiode);
  }

  private boolean hatErststimmenInGebietshierarchie(PolitischeGruppe politischeGruppe,
      GebietUndStimmabgabeart gebietUndStimmabgabeart, StimmenPool stimmenPool) {

    Predicate<Gebiet> gebietPredicate = gebietUndStimmabgabeart.filterGebiete(() -> Collections.emptySet());
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();
    for (Stimmen stimmen : stimmenPool.getDirektstimmen(politischeGruppe)) {
      if (stimmen.getGebiet().isEqualOderUntergebietVon(wahldurchfuehrungsgebiet)
          && gebietPredicate.test(stimmen.getGebiet())) {
        return true;
      }
    }
    return false;
  }

  private boolean hatZweitstimmenInGebietshierarchie(PolitischeGruppe politischeGruppe,
      GebietUndStimmabgabeart gebietUndStimmabgabeart, StimmenPool stimmenPool) {

    Predicate<Gebiet> gebietPredicate = gebietUndStimmabgabeart.filterGebiete(() -> Collections.emptySet());
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();
    for (Stimmen stimmen : stimmenPool.getListenstimmenDerGruppe(politischeGruppe)) {
      if (stimmen.getGebiet().isEqualOderUntergebietVon(wahldurchfuehrungsgebiet)
          && gebietPredicate.test(stimmen.getGebiet())) {
        return true;
      }
    }
    return false;
  }

  private boolean hatDirektkandidatInGebietshierarchie(PolitischeGruppe politischeGruppe,
      Wahldurchfuehrungsgebiet gebiet, ErgebnisDaten ergebnisDaten) {
    return ergebnisDaten.hatDirektkandidatInGebietshierarchie(politischeGruppe, gebiet);
  }

  private boolean hatListeInGebietshierarchie(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet gebiet,
      ErgebnisDaten ergebnisDaten) {
    return ergebnisDaten.hatListeInGebietshierarchie(politischeGruppe, gebiet);
  }

  protected Long bestimmeSitzeFuerGruppeAufGebietUndDarunter(WahlbuchParameter parameter,
      PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {

    Map<Gruppe, Long> sitzeAnzahlFuerGebietUndDarunter = parameter.getErgebnisDaten()
        .getAnzahlSitzeFuerGebietUndDarunter(wahldurchfuehrungsgebiet);
    return sitzeAnzahlFuerGebietUndDarunter.get(politischeGruppe);
  }

  protected Long bestimmeSitzeVorperiodeFuerGruppeAufGebietUndDarunter(WahlbuchParameter parameter,
      PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {

    Map<Gruppe, Long> sitzeVorperiodeAnzahlFuerGebietUndDarunter = parameter.getErgebnisDaten()
            .getAnzahlSitzeVorperiodeFuerGebietUndDarunter(wahldurchfuehrungsgebiet);
    return sitzeVorperiodeAnzahlFuerGebietUndDarunter.get(politischeGruppe);
  }

  private String bestimmeSitzeDifferenz(WahlbuchParameter parameter, Long anzahlSitzeGesamt,
      Long anzahlSitzeVorperiode) {
    Long anzahlSitzeDifferenz = WahlbuchNumberUtil.subtractLongNull(anzahlSitzeGesamt, anzahlSitzeVorperiode);
    return parameter.getFormatter().formatMandatszahlDifferenz(anzahlSitzeDifferenz);
  }

  private Direktkandidat bestimmeDirektkandidatAufGebiet(PolitischeGruppe politischeGruppe,
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    for (Direktkandidat direktkandidat : politischeGruppe.getDirektkandidaten()) {
      if (direktkandidat.getGebiet().equals(wahldurchfuehrungsgebiet)) {
        return direktkandidat;
      }
    }
    return null;
  }

  public abstract StimmenMitVorperiodeAufGebiet getStimmen();

  public boolean equalsGruppe(Gruppe gruppe) {
    return this.gruppe.equalsGruppe(gruppe);
  }

}
