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

import java.util.HashMap;
import java.util.Map;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.hochrechnung.control.AnzahlWahlberechtigteFuerAlleGebieteGenerator;
import de.ivu.elect.business.hochrechnung.control.RelativeGroesseZwischenZweiGebietenAnhandGesamtWaehlerAnzahl;
import de.ivu.elect.business.hochrechnung.control.StimmenHochrechnerEingabedatenVonErgebnisdaten;
import de.ivu.elect.business.hochrechnung.control.ZufaelligeAnzahlWahlberechtigteGenerator;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.GebietDatenWahlbeteiligung;
import de.ivu.elect.business.hochrechnung.entity.StimmenHochrechnerEingabedaten;
import de.ivu.elect.business.hochrechnung.entity.StimmenhochrechnerAusgabedaten;
import de.ivu.elect.business.hochrechnung.entity.WahlteilnehmerAnzahl;

public interface Hochrechnungsalgorithm {
  default StimmenhochrechnerAusgabedaten ermittleHochgerechneteStimmen (ErgebnisDaten ergebnisDatenAktuell) {
    return ermittleHochgerechneteStimmen(new StimmenHochrechnerEingabedatenVonErgebnisdaten(ergebnisDatenAktuell));
  }

  StimmenhochrechnerAusgabedaten ermittleHochgerechneteStimmen (StimmenHochrechnerEingabedaten<Long> eingabedaten);

  HochrechnungsalgorithmusKonfiguration getHochrechnungsalgorithmusKonfiguration();

  default void setZufaelligeAnzahlWahlberechtigteWoNichtVorhanden(StimmenHochrechnerEingabedaten<Long> eingabedaten, ZufaelligeAnzahlWahlberechtigteGenerator zufaelligeAnzahlWahlberechtigteGenerator) {
    setZufaelligeAnzahlWahlberechtigteWoNichtVorhanden(eingabedaten.getAktuelleWahl(), eingabedaten.getVorperiode(), zufaelligeAnzahlWahlberechtigteGenerator);
    setZufaelligeAnzahlWahlberechtigteWoNichtVorhanden(eingabedaten.getVorperiode(), eingabedaten.getAktuelleWahl(), zufaelligeAnzahlWahlberechtigteGenerator);
  }

  default void setZufaelligeAnzahlWahlberechtigteWoNichtVorhanden(Map<Gebiet, GebietDaten<Long>> primaereWahl, Map<Gebiet, GebietDaten<Long>> sekondaereWahl, ZufaelligeAnzahlWahlberechtigteGenerator zufaelligeAnzahlWahlberechtigteGenerator) {
    RelativeGroesseZwischenZweiGebietenAnhandGesamtWaehlerAnzahl relativeGroesseZwischenZweiGebietenBestimmer = new RelativeGroesseZwischenZweiGebietenAnhandGesamtWaehlerAnzahl(
        primaereWahl, sekondaereWahl);
    AnzahlWahlberechtigteFuerAlleGebieteGenerator anzahlWahlberechtigteFuerAlleGebieteGenerator = new AnzahlWahlberechtigteFuerAlleGebieteGenerator(zufaelligeAnzahlWahlberechtigteGenerator,
        relativeGroesseZwischenZweiGebietenBestimmer);
    Map<Gebiet, WahlteilnehmerAnzahl> wahlberechtigteFuerAlleGebiete = anzahlWahlberechtigteFuerAlleGebieteGenerator
        .generatePseudeZahlenFuerBriefgebieteUndZufaelligeAnzahlWahlberechtigteFuerAlleGebieteOhneAnzahlWahlberechtigte(
            primaereWahl.keySet(), getVorhandeneWahlberechtigteProGebiet(primaereWahl));
    ueberschreibeNichtVorhandeneAnzahlWahlberechtigte(primaereWahl, sekondaereWahl, wahlberechtigteFuerAlleGebiete);
  }

  default void ueberschreibeNichtVorhandeneAnzahlWahlberechtigte(Map<Gebiet, GebietDaten<Long>> primaereWahl, Map<Gebiet, GebietDaten<Long>> sekondaereWahl, Map<Gebiet, WahlteilnehmerAnzahl> wahlberechtigteFuerAlleGebiete) {
    primaereWahl.forEach(((gebiet, gebietDaten) -> {
      WahlteilnehmerAnzahl anzahlWahlberechtigteImGebiet = getWahlberechtigteVomGebietDaten(gebietDaten);
      WahlteilnehmerAnzahl anzahlWahlberechtigteImGebietInSekundaererWahl = getWahlberechtigteVomGebietDaten(sekondaereWahl.get(gebiet));
      if (!hasAnyAnzahlWahlberechtigte(anzahlWahlberechtigteImGebiet) && hasAnyAnzahlWahlberechtigte(anzahlWahlberechtigteImGebietInSekundaererWahl)) { // uebernehme lieber echte Werte von der sekondären Wahl als die zufälligen
        updateAnzahlWahlberechtigteUndWahlbeteiligung(anzahlWahlberechtigteImGebietInSekundaererWahl, gebietDaten);
      }
      else if (!(gebiet.isErfassungsgebiet() && hasAnyAnzahlWahlberechtigte(anzahlWahlberechtigteImGebiet))) {
        WahlteilnehmerAnzahl newWahlberechtigteAnzahl = wahlberechtigteFuerAlleGebiete.get(gebiet);
        updateAnzahlWahlberechtigteUndWahlbeteiligung(newWahlberechtigteAnzahl, gebietDaten);
      }
    }));
  }

  default boolean hasAnyAnzahlWahlberechtigte(WahlteilnehmerAnzahl anzahlWahlberechtigteImGebiet) {
    return anzahlWahlberechtigteImGebiet != null && anzahlWahlberechtigteImGebiet.hasAnyAnzahlWahlberechtigte();
  }

  default void updateAnzahlWahlberechtigteUndWahlbeteiligung(WahlteilnehmerAnzahl newWahlberechtigteAnzahl, GebietDaten<Long> gebietDatenToBeUpdated) {
    WahlteilnehmerAnzahl anzahlWahlberechtigteToBeUpdated = gebietDatenToBeUpdated.getAnzahlWahlberechtigteImGebiet();
    if (newWahlberechtigteAnzahl == null || anzahlWahlberechtigteToBeUpdated == null) {
      return;
    }
    newWahlberechtigteAnzahl.getStimmenarten().forEach(stimmenart -> anzahlWahlberechtigteToBeUpdated.setAnzahl(stimmenart, newWahlberechtigteAnzahl.getAnzahl(stimmenart)));


    WahlteilnehmerAnzahl anzahlWaehler = gebietDatenToBeUpdated.getAnzahlWaehlerImGebiet();
    GebietDatenWahlbeteiligung wahlbeteiligungToBeUpdated = gebietDatenToBeUpdated.getWahlbeteiligung();
    if (wahlbeteiligungToBeUpdated == null) {
      return;
    }
    GebietDatenWahlbeteiligung newWahlbeteiligung = new GebietDatenWahlbeteiligung(anzahlWahlberechtigteToBeUpdated, anzahlWaehler);
    newWahlbeteiligung.getStimmenarten().forEach(stimmenart -> wahlbeteiligungToBeUpdated.setAnzahl(stimmenart, newWahlbeteiligung.getAnzahl(stimmenart)));
  }

  default Map<Gebiet, WahlteilnehmerAnzahl> getVorhandeneWahlberechtigteProGebiet(Map<Gebiet, GebietDaten<Long>> primaereWahl) {
    Map<Gebiet, WahlteilnehmerAnzahl> result = new HashMap<>();
    primaereWahl.forEach(((gebiet, gebietDaten) -> {
      WahlteilnehmerAnzahl anzahlWahlberechtigteImGebiet = getWahlberechtigteVomGebietDaten(gebietDaten);
      if (hasAnyAnzahlWahlberechtigte(anzahlWahlberechtigteImGebiet)) {
        result.put(gebiet, anzahlWahlberechtigteImGebiet);
      }
    }));
    return result;
  }

  default WahlteilnehmerAnzahl getWahlberechtigteVomGebietDaten(GebietDaten<Long> gebietDaten) {
    if (gebietDaten != null) {
      return gebietDaten.getAnzahlWahlberechtigteImGebiet();
    }
    return new WahlteilnehmerAnzahl();
  }

}
