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

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.AbstractGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.hochrechnung.entity.WahlteilnehmerAnzahl;


import static de.ivu.elect.business.hochrechnung.control.AnzahlWahlberechtigteFuerAlleGebieteGenerator.getSummeWahlberechtigteVonGebieten;
import static de.ivu.elect.business.hochrechnung.control.AnzahlWahlberechtigteFuerAlleGebieteGenerator.getUebrigeZuVerteilindeWahlberechtigteMitWahlschein;

public class BriefwahlgebietPseudoAnzahlWahlberechtigteGenerator {

  public Map<Gebiet, WahlteilnehmerAnzahl> generatePseudoAnzahlWahlberechtigteFuerBriefwahlgebieteOhneWahlberechtigteAnzahl(Collection<Gebiet> alleGebiete, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, RelativeGroesseZwischenZweiGebietenBestimmer relativeGroesseZwischenZweiGebietenBestimmer) {

    Set<Gebiet> briefwahlgebiete = getBriefwahlgebiete(alleGebiete);

    Map<Gebiet, Gebiet> erstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNullPerGebiet = getErstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNullPerGebiet(
        briefwahlgebiete, vorhandeneWahlberechtigteProGebiet);

    Map<Gebiet, Set<Gebiet>> briefwahlgebietePerUebergeordnetesGebiet = getBriefwahlgebietePerUebergeordnetesGebiet(
        erstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNullPerGebiet);

    return verteileWahlberechtigteMitWahlscheinAufUntergeordneteGebieteOhneWahlberechtigte(briefwahlgebietePerUebergeordnetesGebiet, vorhandeneWahlberechtigteProGebiet, relativeGroesseZwischenZweiGebietenBestimmer);
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> verteileWahlberechtigteMitWahlscheinAufUntergeordneteGebieteOhneWahlberechtigte(Map<Gebiet, Set<Gebiet>> briefwahlgebietePerUebergeordnetesGebiet, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, RelativeGroesseZwischenZweiGebietenBestimmer relativeGroesseZwischenZweiGebietenBestimmer) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new HashMap<>();
    briefwahlgebietePerUebergeordnetesGebiet.forEach((uebergeordnetesGebiet, untergeordneteGebiete) ->
        result.putAll(verteileWahlberechtigteMitWahlscheinAufUntergeordneteGebieteOhneWahlberechtigte(uebergeordnetesGebiet, untergeordneteGebiete, vorhandeneWahlberechtigteProGebiet, relativeGroesseZwischenZweiGebietenBestimmer)));
    return result;
  }

  private Map<Gebiet, WahlteilnehmerAnzahl> verteileWahlberechtigteMitWahlscheinAufUntergeordneteGebieteOhneWahlberechtigte(Gebiet uebergeordnetesGebiet, Set<Gebiet> untergeordneteGebiete, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet, RelativeGroesseZwischenZweiGebietenBestimmer relativeGroesseZwischenZweiGebietenBestimmer) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new HashMap<>();

    WahlteilnehmerAnzahl wahlberechtigteUebergeordnetesGebiets = vorhandeneWahlberechtigteProGebiet.get(uebergeordnetesGebiet);
    if (!hasWahlberechtigteMitWahlschein(wahlberechtigteUebergeordnetesGebiets)) {
      return result;
    }

    Set<Gebiet> untergeordneteGebieteMitAnazahlWahlberechtigteGroesserNull = getGebieteMitAnazahlWahlberechtigteGroesserNull(untergeordneteGebiete, vorhandeneWahlberechtigteProGebiet);

    WahlteilnehmerAnzahl summeWahlberechtigteVonUntergeordnetenBriefgebieten = getSummeWahlberechtigteVonGebieten(untergeordneteGebieteMitAnazahlWahlberechtigteGroesserNull, vorhandeneWahlberechtigteProGebiet);

    WahlteilnehmerAnzahl uebrigeZuVerteilindeWahlberechtigte = getUebrigeZuVerteilindeWahlberechtigteMitWahlschein(wahlberechtigteUebergeordnetesGebiets, summeWahlberechtigteVonUntergeordnetenBriefgebieten);

    if (!hasWahlberechtigteMitWahlschein(uebrigeZuVerteilindeWahlberechtigte)) {
      return result;
    }

    Long anzahlZuVerteilen = uebrigeZuVerteilindeWahlberechtigte.getMitWahlscheinNotNull();

    List<Gebiet> untergeordneteGebieteOhneAnazahlWahlberechtigte = filterElementeRaus(untergeordneteGebiete, untergeordneteGebieteMitAnazahlWahlberechtigteGroesserNull);

    List<Double> relativeGroesseZumUebergeordnetenGebiet = getRelativeGroesseZumGebietAlleGroesserNull(untergeordneteGebieteOhneAnazahlWahlberechtigte, uebergeordnetesGebiet, relativeGroesseZwischenZweiGebietenBestimmer);

    List<Long> entsprechendeAnzahlen = new AnteileAufAnzahlenMapper().mappeAnteileAufAnzahlen(anzahlZuVerteilen, relativeGroesseZumUebergeordnetenGebiet);

    for (int i = 0; i < entsprechendeAnzahlen.size();i++) {
      WahlteilnehmerAnzahl wahlberechtigte = new WahlteilnehmerAnzahl();
      Long anzahl = entsprechendeAnzahlen.get(i);
      wahlberechtigte.setAnzahl(Stimmenart.MIT_WAHLSCHEIN, anzahl);
      wahlberechtigte.setAnzahl(Stimmenart.GESAMT, anzahl);
      result.put(untergeordneteGebieteOhneAnazahlWahlberechtigte.get(i), wahlberechtigte);
    }

    return result;
  }

  private List<Double> getRelativeGroesseZumGebietAlleGroesserNull(List<Gebiet> vonGebiete, Gebiet zumGebiet, RelativeGroesseZwischenZweiGebietenBestimmer relativeGroesseZwischenZweiGebietenBestimmer) {
    List<Double> result = vonGebiete.stream().map(untergeordnetesGebiet ->
        relativeGroesseZwischenZweiGebietenBestimmer.getRelativeGroesseZwischenZweiGebietenBestimmer(untergeordnetesGebiet, zumGebiet))
        .collect(Collectors.toList());

    return replaceNullWerteMitMinimum(result);
  }

  private List<Double> replaceNullWerteMitMinimum(List<Double> werte) {
    double min = werte.stream().filter(Objects::nonNull).mapToDouble(x -> x).min().orElse(1.0 / werte.size());
    return werte.stream().map(x -> {
      if (x != null && x > 0) {
        return x;
      }
      return min;
    }).collect(Collectors.toList());
  }

  private <T> List<T> filterElementeRaus(Set<T> all, Set<T> elementsToExclude) {
    return all.stream()
        .filter(e -> !elementsToExclude.contains(e))
        .collect(Collectors.toList());
  }

  private Set<Gebiet> getGebieteMitAnazahlWahlberechtigteGroesserNull(Set<Gebiet> untergeordneteGebiete, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet) {
    return untergeordneteGebiete.stream().filter(gebiet -> hasWahlberechtigteMitWahlschein(vorhandeneWahlberechtigteProGebiet.get(gebiet)))
        .collect(Collectors.toSet());
  }

  private Map<Gebiet, Set<Gebiet>> getBriefwahlgebietePerUebergeordnetesGebiet(Map<Gebiet, Gebiet> uebergeordnetesGebietPerBriefwahlgebiet) {
    Map<Gebiet, Set<Gebiet>> result = new HashMap<>();
    uebergeordnetesGebietPerBriefwahlgebiet.forEach((briefwahlgebiet, uebergeordnetesGebiet) -> {
      if (uebergeordnetesGebiet != null) {
        result.putIfAbsent(uebergeordnetesGebiet, new HashSet<>());
        result.get(uebergeordnetesGebiet).add(briefwahlgebiet);
      }
    });
    return result;
  }

  private Map<Gebiet, Gebiet> getErstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNullPerGebiet(Set<Gebiet> briefwahlgebieteOhneAnzahlWahlberechtigte, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet) {
    Map<Gebiet, Gebiet> result = new HashMap<>();
    briefwahlgebieteOhneAnzahlWahlberechtigte.forEach(briefwahlgebiet -> {
      Gebiet erstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNull = getErstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNull(
          briefwahlgebiet, vorhandeneWahlberechtigteProGebiet);
      result.put(briefwahlgebiet, erstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNull);
    });
    return result;
  }

  private Gebiet getErstesUebergeordnetesGebietMitAnzahlWahlberechtigteMitWahlscheinGroesserNull(Gebiet gebiet, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet) {
    Gebiet uebergeordnetesGebiet = gebiet.getUebergeordnetesGebiet();
    while (uebergeordnetesGebiet != null) {
      WahlteilnehmerAnzahl anzahlWahlberechtigte = vorhandeneWahlberechtigteProGebiet.get(uebergeordnetesGebiet);
      if (hasWahlberechtigteMitWahlschein(anzahlWahlberechtigte)) {
        return uebergeordnetesGebiet;
      }
      uebergeordnetesGebiet = uebergeordnetesGebiet.getUebergeordnetesGebiet();
    }
    return null;
  }

  private Set<Gebiet> getBriefwahlgebiete(Collection<Gebiet> alleGebiete) {
    return alleGebiete.stream().filter(AbstractGebiet::isBriefwahlgebiet).collect(Collectors.toSet());
  }

  private Set<Gebiet> getBriefwahlgebieteOhneAnzahlWahlberechtigte(Set<Gebiet> briefwahlgebiete, Map<Gebiet, WahlteilnehmerAnzahl> vorhandeneWahlberechtigteProGebiet) {
    return briefwahlgebiete.stream().filter(gebiet -> hasWahlberechtigteMitWahlschein(vorhandeneWahlberechtigteProGebiet.get(gebiet))).collect(Collectors.toSet());
  }

  private boolean hasWahlberechtigteMitWahlschein(WahlteilnehmerAnzahl anzahlWahlberechtigte) {
    return anzahlWahlberechtigte != null && anzahlWahlberechtigte.getMitWahlscheinNotNull() > 0;
  }
}
