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

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.control.GebietDatenHochrechnerAusGewichtetenSkalierungen;
import de.ivu.elect.business.hochrechnung.control.GebietDatenNormierer;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;

public class GebietDatenAusZufaelligenZahlenGenerator implements GebietDaten<Double> {

  private final Gebiet gebiet;
  private final WahlteilnehmerAnzahl anzahlWahlberechtigte;
  private final WahlteilnehmerAnzahl anzahlWaehler;
  private final GebietDatenWahlbeteiligung wahlbeteiligung;
  private final Map<Stimmenart, Double> gueltige;
  private final Map<Stimmenart, Double> ungueltige;
  private final Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen;
  private final Map<Gruppe, ReferendumFrage<Double>> referendumFragen;

  public GebietDatenAusZufaelligenZahlenGenerator(Gebiet gebiet, WahlteilnehmerAnzahl anzahlWahlberechtigte, WahlteilnehmerAnzahl anzahlWaehlerVorhandenInAktuellerWahl, ZufaelligerStimmenHochrechnerKonfiguration hochrechnerKonfiguration, KoordinatenInformationen koordinatenInformationen, Function<PolitischeGruppe, Set<Listenkandidatur>> listenkandidaturenProPolitischeGruppe, List<PolitischeGruppe> fuerGebietRelevantePolitischeGruppen, Map<Gruppe, List<ReferendumOption>> referendumOptionenPerVorlage) {
    this.gebiet = gebiet;
    this.anzahlWahlberechtigte = anzahlWahlberechtigte != null? anzahlWahlberechtigte: new WahlteilnehmerAnzahl();
    this.wahlbeteiligung = getZufaelligeWahlbeteiligung(gebiet, hochrechnerKonfiguration, this.anzahlWahlberechtigte);
    this.gueltige = hochrechnerKonfiguration.getGueltigeStimmenanteileGenerator().generateZufaelligeGueltigeStimmenanteile(gebiet, koordinatenInformationen.getAlleStimmenartenSortiert());
    this.ungueltige = new GebietDatenNormierer<Double>().berechneUngueltigeAnteileVonGueltige(this.gueltige);
    this.parteiStimmen = hochrechnerKonfiguration.getParteiStimmenGenerator().generateZufaelligeParteiStimmenanteile(gebiet, fuerGebietRelevantePolitischeGruppen,
        politischeGruppe -> new HashSet<>(koordinatenInformationen.getAlleStimmenartenSortiert()),
        listenkandidaturenProPolitischeGruppe);
    this.referendumFragen = hochrechnerKonfiguration.getReferendumFrageStimmenGenerator().generateZufaelligeReferendumFrageStimmenanteileUndAendereWahlbeteiligungDassQuorumErfuelltIst(gebiet, referendumOptionenPerVorlage, this.wahlbeteiligung);
    this.anzahlWaehler = new GebietDatenHochrechnerAusGewichtetenSkalierungen().getAnzahlWaehlerImGebietFuerHochrechnung(anzahlWaehlerVorhandenInAktuellerWahl, this.wahlbeteiligung, this.anzahlWahlberechtigte);
  }

  private GebietDatenWahlbeteiligung getZufaelligeWahlbeteiligung(Gebiet gebiet, ZufaelligerStimmenHochrechnerKonfiguration hochrechnerKonfiguration, WahlteilnehmerAnzahl anzahlWahlberechtigte) {
    GebietDatenWahlbeteiligung result;
    if (gebiet.isBriefwahlgebiet() && !gebiet.isUrnenwahlgebiet()) {
      result = hochrechnerKonfiguration.getWahlbeteiligungGenerator()
          .getZufaelligeWahlbeteiligungFuerBriefgebiet();
    } else if (gebiet.getBriefwahlInUrneErfassen()) {
      result = hochrechnerKonfiguration.getWahlbeteiligungGenerator().getZufaelligeWahlbeteiligungFuerUrnengebietMitBriefwahlInUrneErfassen();
    } else {
      result = hochrechnerKonfiguration.getWahlbeteiligungGenerator().getZufaelligeWahlbeteiligungFuerUrnengebiet();
    }
    if (result.getGesamt() == null) {
      long gesamtWaehler = 0;
      for (Stimmenart stimmenart: result.getStimmenarten()) {
        gesamtWaehler += Math.round(result.getAnzahlNotNull(stimmenart) * anzahlWahlberechtigte.getAnzahlNotNull(stimmenart));
      }
      long gesamtWahlberechtigte = anzahlWahlberechtigte.getGesamtNotNull();
      if (gesamtWahlberechtigte != 0) {
        result.setAnzahl(Stimmenart.GESAMT, 1.0 * gesamtWaehler / gesamtWahlberechtigte);
      }
    }
    return result;
  }

  @Override
  public Gebiet getGebiet() {
    return this.gebiet;
  }

  @Override
  public WahlteilnehmerAnzahl getAnzahlWahlberechtigteImGebiet() {
    return this.anzahlWahlberechtigte;
  }

  @Override
  public WahlteilnehmerAnzahl getAnzahlWaehlerImGebiet() {
    return this.anzahlWaehler;
  }

  @Override
  public GebietDatenWahlbeteiligung getWahlbeteiligung() {
    return this.wahlbeteiligung;
  }

  @Override
  public Map<Stimmenart, Double> getGueltige() {
    return this.gueltige;
  }

  @Override
  public Map<Stimmenart, Double> getUngueltige() {
    return this.ungueltige;
  }

  @Override
  public boolean isErgebnisVorhanden() {
    return true;
  }

  @Override
  public Map<PolitischeGruppe, ParteiStimmen<Double>> getParteiStimmen() {
    return this.parteiStimmen;
  }

  @Override
  public Map<Gruppe, ReferendumFrage<Double>> getReferendumFragen() {
    return this.referendumFragen;
  }
}
