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

import java.util.List;
import java.util.Map;

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 static de.ivu.elect.business.auffaelligkeit.control.AuffaelligkeitBemerkungenErzeuger.SEHR_KLEINE_ZAHL;
import static de.ivu.elect.business.hochrechnung.control.StimmenHochrechnerHilfsfunktionen.notNull;

public class HochgerechneteGebietDaten 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;
  private final Boolean ergebnisVorhanden;

  public HochgerechneteGebietDaten(GebietDaten<Double> gebietDatenVorperiode, GebietDaten<Long> gebietDatenAktuellAlsAnzahlen, List<GewichteteSkalierung> gewichteteSkalierungen, KoordinatenInformationen koordinatenInformationen) {
    this.gebiet = extractGebiet(gebietDatenVorperiode, gebietDatenAktuellAlsAnzahlen);

    GebietDatenHochrechnerAusGewichtetenSkalierungen gebietDatenHochrechner = new GebietDatenHochrechnerAusGewichtetenSkalierungen();

    WahlteilnehmerAnzahl anzahlWahlberechtigteImGebietInAktuelleWahl = getAnzahlWahlberechtigteImGebiet(gebietDatenAktuellAlsAnzahlen);
    WahlteilnehmerAnzahl anzahlWaehlerImGebietInAktuelleWahl = getAnzahlWaehlerImGebiet(gebietDatenAktuellAlsAnzahlen);

    this.anzahlWahlberechtigte = gebietDatenHochrechner.getAnzahlWahlberechtigteImGebietFuerHochrechnung(getAnzahlWahlberechtigteImGebiet(gebietDatenVorperiode), anzahlWahlberechtigteImGebietInAktuelleWahl);
    this.wahlbeteiligung = gebietDatenHochrechner.getHochgerechneteWahlbeteiligung(gebietDatenVorperiode, gewichteteSkalierungen);
    this.anzahlWaehler = gebietDatenHochrechner.getAnzahlWaehlerImGebietFuerHochrechnung(anzahlWaehlerImGebietInAktuelleWahl, this.wahlbeteiligung, this.anzahlWahlberechtigte);

    this.gueltige = gebietDatenHochrechner.getGueltigeHochgerechnet(gebietDatenVorperiode, gewichteteSkalierungen, koordinatenInformationen);
    this.ungueltige = gebietDatenHochrechner.getUngueltigeVonGueltige(this.gueltige);

    this.parteiStimmen = gebietDatenHochrechner.getParteiStimmenHochgerechnet(gebietDatenVorperiode, gebietDatenAktuellAlsAnzahlen, gewichteteSkalierungen, koordinatenInformationen);

    this.referendumFragen = gebietDatenHochrechner.getReferendumFragenHochgerechnet(gebietDatenVorperiode, gebietDatenAktuellAlsAnzahlen, gewichteteSkalierungen, koordinatenInformationen);
    this.ergebnisVorhanden = isHochgerechneteStimmenVorhanden(parteiStimmen, referendumFragen);
  }

  private Gebiet extractGebiet(GebietDaten<? extends Number> vorperiode, GebietDaten<? extends Number> aktuell) {
    if (aktuell != null && aktuell.getGebiet() != null) {
      return aktuell.getGebiet();
    } else if (vorperiode != null && vorperiode.getGebiet() != null) {
      return vorperiode.getGebiet();
    }
    return null;
  }

  private WahlteilnehmerAnzahl getAnzahlWaehlerImGebiet(GebietDaten<? extends Number> gebietDaten) {
    if (gebietDaten == null || gebietDaten.getAnzahlWaehlerImGebiet() == null) {
      return new WahlteilnehmerAnzahl();
    }
    return gebietDaten.getAnzahlWaehlerImGebiet();
  }

  private WahlteilnehmerAnzahl getAnzahlWahlberechtigteImGebiet(GebietDaten<? extends Number> gebietDaten) {
    if (gebietDaten == null || gebietDaten.getAnzahlWahlberechtigteImGebiet() == null) {
      return new WahlteilnehmerAnzahl();
    }
    return gebietDaten.getAnzahlWahlberechtigteImGebiet();
  }

  private Boolean isHochgerechneteStimmenVorhanden(Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen, Map<Gruppe, ReferendumFrage<Double>> referendumFragen) {
    return hasParteiStimmenAnyAnzahlStimmenNotNull(parteiStimmen) || hasReferendumFragenAnyAnzahlStimmenNotNull(referendumFragen);
  }

  private Boolean hasParteiStimmenAnyAnzahlStimmenNotNull(Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen) {
    return parteiStimmen.values().stream().anyMatch(this::hasParteiStimmenAnyAnzahlStimmenNotNull);
  }

  private Boolean hasReferendumFragenAnyAnzahlStimmenNotNull(Map<Gruppe, ReferendumFrage<Double>> referendumFragen) {
    return referendumFragen.values().stream().anyMatch(this::hasReferendumFrageAnyAnzahlStimmenNotNull);
  }

  private boolean hasReferendumFrageAnyAnzahlStimmenNotNull(ReferendumFrage<Double> referendumFrage) {
    return referendumFrage != null && referendumFrage.getStimmenPerReferendumOption().values().stream().anyMatch(x -> notNull(x) > SEHR_KLEINE_ZAHL);
  }

  private boolean hasParteiStimmenAnyAnzahlStimmenNotNull(ParteiStimmen<Double> parteiStimmen) {
    return parteiStimmen != null && hasStimmenMapAnyAnzahlStimmenNotNull(parteiStimmen.getStimmen());
  }

  private boolean hasStimmenMapAnyAnzahlStimmenNotNull(Map<Stimmenart, Double> stimmen) {
    return stimmen.values().stream().anyMatch(x -> notNull(x) > SEHR_KLEINE_ZAHL);
  }


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

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

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

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

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

  @Override
  public boolean isErgebnisVorhanden() {
    return this.ergebnisVorhanden;
  }

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

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

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