
package de.ivu.elect.business.ergebnis.control;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.gebietsbaum.control.GebietByNummerComparator;
import de.ivu.elect.business.gebietsbaum.entity.AbstractGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.NlGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.AbstractGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppeArt;
import de.ivu.elect.business.gruppe.entity.NlSystemGruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.internet.control.HashablePredicate;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyKonfliktCustomData;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;


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

public class NlErgebnisDaten {

  private static final HashablePredicate<Gebiet> GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN = GebietUndStimmabgabeart.FALSE_TEST;

  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_URNENWAHL_GEMEINDE = new HashablePredicate<Gebiet>() {
    @Override
    protected int hashCodeInternal() {
      return 0;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return NlGebiet.isUrnenwahlgebietGemeinde(gebiet);
    }
  };


  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_BRIEFWAHL_GEMEINDE = new HashablePredicate<Gebiet>() {
    @Override
    protected int hashCodeInternal() {
      return 0;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return NlGebiet.isBriefwahlgebietGemeinde(gebiet);
    }
  };





  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_BRIEFWAHL_AUSLAND = new HashablePredicate<Gebiet>() {
    @Override
    protected int hashCodeInternal() {
      return 1188;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return NlGebiet.isBriefwahlgebietAusland(gebiet);
    }
  } ;

  public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_GEMEINDE = new HashablePredicate<Gebiet>() {
    @Override
    protected int hashCodeInternal() {
      return 0;
    }

    @Override
    protected boolean equalsInternal(Object other) {
      return this == other;
    }

    @Override
    public boolean test(Gebiet gebiet) {
      return ERFASSUNGSGEBIET_URNENWAHL_GEMEINDE.test(gebiet) || ERFASSUNGSGEBIET_BRIEFWAHL_GEMEINDE.test(gebiet);
    }
  };

  // public static final HashablePredicate<Gebiet> ERFASSUNGSGEBIET_AUSLAND = ERFASSUNGSGEBIET_BRIEFWAHL_AUSLAND;

  private final ErgebnisDaten ergebnisDaten;

  private final AbstractErgebnisDatenWahlberechtigte ergebnisDatenWahlberechtigte;

  private final ErgebnisDatenListenkandidaturen ergebnisDatenListenkandidaturen;

  private NlErgebnisDatenWaehler ergebnisDatenWaehler;

  private NlErgebnisDatenGueltige ergebnisDatenGueltige;

  private NlErgebnisDatenStimmzettel ergebnisDatenStimmzettel;

  private Map<Long, Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietPerId;

  private Map<String, Gruppe> gruppePerSchluessel;

  private List<Wahldurchfuehrungsgebiet> listwahlgebieteSortiertNachNummer;

  private List<PolitischeGruppe> sortiertePolitischeGruppeListe;

  public NlErgebnisDaten(ErgebnisDaten ergebnisDaten) {
    super();

    this.ergebnisDaten = ergebnisDaten;
    this.ergebnisDatenWahlberechtigte = ergebnisDaten.getErgebnisDatenWahlberechtigte();
    this.ergebnisDatenListenkandidaturen = ergebnisDaten.getErgebnisDatenListenkandidaturen();
  }

  public long getSummeWahlberechtigteGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWahlberechtigteGesamt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWahlberechtigteGesamtNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamtNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet) + getSummeWahlberechtigteGesamtNurBriefwahlGemeinde(wahldurchfuehrungsgebiet);
  }

  public long getSummeWahlberechtigteGesamtNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  // JA, auch bei BRIEFWAHL!
  public long getSummeWahlberechtigteGesamtNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  // JA, auch bei BRIEFWAHL!
  public long getSummeWahlberechtigteGesamtNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWahlberechtigteGesamt(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet));
  }

  public long getSummeWahlberechtigteGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWahlberechtigte().getSummeWahlberechtigteGesamt(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerOhneWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerOhneWahlschein(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerOhneWahlscheinNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerOhneWahlscheinNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet); // Urnenwahl-spezifisch...
  }

  public long getSummeWaehlerOhneWahlscheinNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerOhneWahlschein(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerOhneWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerOhneWahlschein(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerMitWahlschein(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerMitWahlschein(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerMitWahlscheinNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitWahlscheinNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet); // Urnenwahl-spezifisch...
  }

  public long getSummeWaehlerMitWahlscheinNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitWahlschein(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerMitWahlschein(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerMitWahlschein(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerMitVollmacht(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerMitVollmacht(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerMitVollmachtNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitVollmachtNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet); // Urnenwahl-spezifisch...
  }

  public long getSummeWaehlerMitVollmachtNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerMitVollmacht(NlErgebnisDaten.createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerMitVollmacht(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerMitVollmacht(gebietUndStimmabgabeart);
  }

  public long getSummeWaehlerGesamt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getSummeWaehlerGesamt(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getSummeWaehlerGesamtNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamtNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet) + getSummeWaehlerGesamtNurBriefwahlGemeinde(wahldurchfuehrungsgebiet);
  }

  public long getSummeWaehlerGesamtNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamt(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerGesamtNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamt(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerGesamtNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getSummeWaehlerGesamt(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet));
  }

  public long getSummeWaehlerGesamt(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getErgebnisDatenWaehler().getSummeWaehlerGesamt(gebietUndStimmabgabeart);
  }

  public long getSummeListeListenstimmen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, Liste liste) {
    return getSummeListeListenstimmen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart), liste);
  }

  public long getSummeListeListenstimmenNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmenNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet, liste) + getSummeListeListenstimmenNurBriefwahlGemeinde(wahldurchfuehrungsgebiet, liste);
  }

  public long getSummeListeListenstimmenNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmen(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmen(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmenNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Liste liste) {
    return getSummeListeListenstimmen(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet), liste);
  }

  public long getSummeListeListenstimmen(GebietUndStimmabgabeart gebietUndStimmabgabeart, Liste liste) {
    Long result = getErgebnisDaten().getZweitstimmenAktuell(liste.getGruppe(), gebietUndStimmabgabeart);

    if (result != null) {
      return result;
    }

    return 0L;
  }

  public long getSummeListenkandidaturListenstimmen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurGesamtGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmenNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet, listenkandidatur)
        + getSummeListenkandidaturListenstimmenNurBriefwahlGemeinde(wahldurchfuehrungsgebiet, listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(createGebietUndStimmabgabeartNurUrnenwahlGemeinde(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(createGebietUndStimmabgabeartNurBriefwahlGemeinde(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmenNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Listenkandidatur listenkandidatur) {
    return getSummeListenkandidaturListenstimmen(createGebietUndStimmabgabeartNurBriefwahlAusland(wahldurchfuehrungsgebiet), listenkandidatur);
  }

  public long getSummeListenkandidaturListenstimmen(GebietUndStimmabgabeart gebietUndStimmabgabeart, Listenkandidatur listenkandidatur) {
    return getErgebnisDatenListenkandidaturen().getStimmenFuerListenkandidatur(listenkandidatur, gebietUndStimmabgabeart);
  }

  public AbstractErgebnisDatenWahlberechtigte getErgebnisDatenWahlberechtigte() {
    return ergebnisDatenWahlberechtigte;
  }

  public NlErgebnisDatenWaehler getErgebnisDatenWaehler() {
    if (ergebnisDatenWaehler == null) {
      ergebnisDatenWaehler = new NlErgebnisDatenWaehler(this);
    }

    return ergebnisDatenWaehler;
  }

  public NlErgebnisDatenGueltige getErgebnisDatenGueltige() {
    if (ergebnisDatenGueltige == null) {
      ergebnisDatenGueltige = new NlErgebnisDatenGueltige(this);
    }

    return ergebnisDatenGueltige;
  }

  public NlErgebnisDatenStimmzettel getErgebnisDatenStimmzettel() {
    if (ergebnisDatenStimmzettel == null) {
      ergebnisDatenStimmzettel = new NlErgebnisDatenStimmzettel(this);
    }

    return ergebnisDatenStimmzettel;
  }

  public ErgebnisDatenListenkandidaturen getErgebnisDatenListenkandidaturen() {
    return ergebnisDatenListenkandidaturen;
  }

  public List<ErgebnisKonflikt> getKonflikte() {
    return getErgebnisDaten().getKonflikte().stream().filter(k -> k.getCustomData() instanceof OsvLegacyKonfliktCustomData).collect(Collectors.toList());
  }

  public ErgebnisDaten getErgebnisDaten() {
    return ergebnisDaten;
  }

  protected StimmenPool getStimmen() {
    return getErgebnisDaten().getStimmen();
  }

  protected StimmenKumulierer getStimmenKumulierer() {
    return getErgebnisDaten().getStimmenKumulierer();
  }

  protected Supplier<Set<Gebiet>> collectGebieteErfasstFct() {
    return getErgebnisDaten()::getErfassteGebiete;
  }

  protected Function<Wahldurchfuehrungsgebiet, List<Wahldurchfuehrungsgebiet>> collectGebieteUntergeordnetFct() {
    return getErgebnisDaten()::getUntergeordneteGebiete;
  }

  public Optional<Wahldurchfuehrungsgebiet> findWahldurchfuehrungsgebietById(Long id) {
    if (id != null) {
      if (wahldurchfuehrungsgebietPerId == null) {
        wahldurchfuehrungsgebietPerId = getErgebnisDaten().getGebiete().stream().collect(toMap(Gebiet::getId, Function.identity()));
      }

      return Optional.ofNullable(wahldurchfuehrungsgebietPerId.get(id));
    }

    return Optional.empty();
  }

  public Optional<Gruppe> findGruppeBySchluessel(String schluessel) {
    if (!StringUtils.isBlank(schluessel)) {
      if (gruppePerSchluessel == null) {
        gruppePerSchluessel = getErgebnisDaten().getGruppen().stream().collect(toMap(Gruppe::getSchluessel, Function.identity()));
      }

      return Optional.ofNullable(gruppePerSchluessel.get(schluessel));
    }

    return Optional.empty();
  }

  public Optional<Gruppe> findSystemGruppe(NlSystemGruppe expected) {
    return getErgebnisDaten().getSystemgruppen().stream().filter(gruppe -> expected.equalsSystemGruppe(gruppe, getErgebnisDaten().getSystemGruppeEigenschaften())).findFirst();
  }

  public List<PolitischeGruppe> getPolitischeGruppenSortiertNachPosition() {
    if (sortiertePolitischeGruppeListe == null) {
      sortiertePolitischeGruppeListe = getErgebnisDaten().getGruppen().stream().sorted(Comparator.comparingLong(Gruppe::getRelativePosition)).filter(AbstractGruppe::isPolitischeGruppe)
          .map(g -> g.castTo(PolitischeGruppe.class).getEntity()).collect(Collectors.toList());
    }

    return sortiertePolitischeGruppeListe;
  }

  public Optional<Wahldurchfuehrungsgebiet> findListenwahlgebietByGebietsnummer(String gebietsnummer) {
    return getErgebnisDaten().getGebiete(AbstractGebiet::isListenwahlgebiet).stream().filter(g -> g.getNummer().equals(gebietsnummer)).findAny();
  }

  public List<Wahldurchfuehrungsgebiet> getListenwahlgebieteSortiertNachNummer() {
    if (listwahlgebieteSortiertNachNummer == null) {
      listwahlgebieteSortiertNachNummer = getErgebnisDaten().getGebiete().stream().filter(AbstractGebiet::isListenwahlgebiet).sorted(GebietByNummerComparator.ASCENDING).collect(Collectors.toList());
    }

    return listwahlgebieteSortiertNachNummer;
  }

  public List<Liste> getListenDerGruppe(PolitischeGruppe gruppe) {
    return getErgebnisDaten().getListenDerGruppe(gruppe);
  }

  public NlPolitischeGruppeArt getNlPolitischeGruppeArt(Gruppe gruppe) {
    if (!gruppe.isInstanceOf(PolitischeGruppe.class)) {
      return NlPolitischeGruppeArt.UNBEKANNT;
    }

    return NlPolitischeGruppe.bestimmeEinreichungsart(getErgebnisDaten().getListenDerGruppe(gruppe.castTo(PolitischeGruppe.class).getEntity()), getErgebnisDaten().getEntityAufStimmzettelDaten());
  }

  static GebietUndStimmabgabeart createGebietUndStimmabgabeartNurUrnenwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.URNE, GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN, ERFASSUNGSGEBIET_URNENWAHL_GEMEINDE);
  }

  static GebietUndStimmabgabeart createGebietUndStimmabgabeartNurBriefwahlGemeinde(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.BRIEF, GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN, ERFASSUNGSGEBIET_BRIEFWAHL_GEMEINDE);
  }

  static GebietUndStimmabgabeart createGebietUndStimmabgabeartNurBriefwahlAusland(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.BRIEF, GEBIET_IMMER_ALS_ERFASST_UND_RELEVANT_WERTEN, ERFASSUNGSGEBIET_BRIEFWAHL_AUSLAND);
  }
}
