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

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBemerkung;
import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBenfordTestChartErgebnis;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gebietsbaum.entity.GebietUK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.hochrechnung.control.AbweichungZwischenGebietDatenBerechner;
import de.ivu.elect.business.hochrechnung.entity.AnzahlStimmenProWaehlerNachStimmenartBestimmer;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.StimmenhochrechnerAusgabedaten;
import de.ivu.elect.business.hochrechnung.entity.StimmenhochrechnerGebietAusgabedaten;
import de.ivu.elect.jee.entity.AbstractNoPersistentEntity;

/**
 *  on 29.03.2016.
 */
public class StimmeingabeAuffaelligkeit extends AbstractNoPersistentEntity<GebietPK, GebietUK> implements EntityMitGebiet {

  private static final long serialVersionUID = 1L;

  private final Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet;
  private final StimmenhochrechnerAusgabedaten gesamtAusgabedaten;
  private final StimmenhochrechnerGebietAusgabedaten stimmenhochrechnerAusgabedaten;
  private final Map<Stimmenart, Double> absoluteAbweichungProStimmenart = new EnumMap<>(Stimmenart.class);
  private final List<AuffaelligkeitBenfordTestChartErgebnis> auffaelligkeitBenfordTestChartErgebnis;
  private final List<AuffaelligkeitBemerkung> bemerkungen;
  private final String erfasser;

  public StimmeingabeAuffaelligkeit(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, StimmenhochrechnerAusgabedaten gesamtAusgabedaten) {
    this(wahldurchfuehrungsgebiet, gesamtAusgabedaten, new ArrayList<>());
  }

  public StimmeingabeAuffaelligkeit(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, StimmenhochrechnerAusgabedaten gesamtAusgabedaten, List<AuffaelligkeitBenfordTestChartErgebnis> auffaelligkeitBenfordTestChartErgebnis) {
    this(wahldurchfuehrungsgebiet, gesamtAusgabedaten, auffaelligkeitBenfordTestChartErgebnis, new ArrayList<>(), null);
  }

  public StimmeingabeAuffaelligkeit(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, StimmenhochrechnerAusgabedaten gesamtAusgabedaten, List<AuffaelligkeitBenfordTestChartErgebnis> auffaelligkeitBenfordTestChartErgebnis, List<AuffaelligkeitBemerkung> bemerkungen, String erfasser) {
    super();

    this.wahldurchfuehrungsgebiet = wahldurchfuehrungsgebiet;
    this.gesamtAusgabedaten = gesamtAusgabedaten;
    this.stimmenhochrechnerAusgabedaten = gesamtAusgabedaten.getAusgabedatenVomGebiet(wahldurchfuehrungsgebiet);
    this.auffaelligkeitBenfordTestChartErgebnis = auffaelligkeitBenfordTestChartErgebnis;
    this.bemerkungen = bemerkungen;
    this.erfasser = erfasser;
  }

  public Wahldurchfuehrungsgebiet getWahldurchfuehrungsgebiet() {
    return wahldurchfuehrungsgebiet;
  }

  public StimmenhochrechnerAusgabedaten getGesamtStimmenhochrechnerAusgabedaten() {
    return gesamtAusgabedaten;
  }

  public StimmenhochrechnerGebietAusgabedaten getStimmenhochrechnerAusgabedaten() {
    return stimmenhochrechnerAusgabedaten;
  }

  private StimmenhochrechnerGebietAusgabedaten getAusgabedatenVomNachbarnGebiet(Gebiet g2) {
    return gesamtAusgabedaten.getAusgabedatenVomGebiet(g2);
  }

  public List<StimmenhochrechnerGebietAusgabedaten> getAusgabeDatenAehnlichsterGebiete(long size) {
    if (stimmenhochrechnerAusgabedaten == null) {
      return Collections.emptyList();
    }
    return stimmenhochrechnerAusgabedaten.getAbstaendeZuAnderenGebieten()
        .stream()
        .sorted()
        .limit(size)
        .map(gebietGewichtetesObject -> getAusgabedatenVomNachbarnGebiet(gebietGewichtetesObject.getObject()))
        .collect(Collectors.toList());
  }

  public String getErfasser() {
    return erfasser;
  }

  @Override
  public Long getId() {
    if (wahldurchfuehrungsgebiet == null) {
      return null;
    }
    return wahldurchfuehrungsgebiet.getId();
  }

  @Override
  public Wahldurchfuehrungsgebiet getGebiet() {
    return wahldurchfuehrungsgebiet;
  }

  public double getSummeAbsoluteAbweichungEinerStimmenart(Stimmenart stimmenart) {
    absoluteAbweichungProStimmenart.putIfAbsent(stimmenart, berechneSummeAbsoluteAbweichungEinerStimmenart(stimmenart));
    return absoluteAbweichungProStimmenart.get(stimmenart);
  }

  private Double berechneSummeAbsoluteAbweichungEinerStimmenart(Stimmenart stimmenart) {
    if (stimmenhochrechnerAusgabedaten == null || !stimmenhochrechnerAusgabedaten.getAktuellAnzahlen().isErgebnisVorhanden()) {
      return 0d;
    }
    return new AbweichungZwischenGebietDatenBerechner().getSummeAbsoluteAbweichungEinerStimmenart(stimmenart, getStimmenhochrechnerAusgabedaten().getAbweichungAktuellVonHochgerechnet().getParteiStimmen());
  }

  public double getMaximaleAbweichungssummeVonEinemStimmenart() {
    assureAbweichungssummeVonStimmenartenSindBerechnet();
    return absoluteAbweichungProStimmenart.values().stream().max(Double::compareTo).orElse(0d);
  }

  private void assureAbweichungssummeVonStimmenartenSindBerechnet() {
    if (stimmenhochrechnerAusgabedaten == null) {
      return;
    }
    for (Stimmenart stimmenart: stimmenhochrechnerAusgabedaten.getAbweichungAktuellVonHochgerechnet().getGueltige().keySet()) {
      getSummeAbsoluteAbweichungEinerStimmenart(stimmenart);
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

    StimmeingabeAuffaelligkeit that = (StimmeingabeAuffaelligkeit) o;

    return wahldurchfuehrungsgebiet != null
        ? wahldurchfuehrungsgebiet.equals(that.wahldurchfuehrungsgebiet)
        : that.wahldurchfuehrungsgebiet == null;

  }

  @Override
  public int hashCode() {
    return wahldurchfuehrungsgebiet != null ? wahldurchfuehrungsgebiet.hashCode() : 0;
  }

  public List<AuffaelligkeitBemerkung> getBemerkungen() {
    return this.bemerkungen;
  }

  public List<AuffaelligkeitBenfordTestChartErgebnis> getBenfordTestErgebnisse() {
    return auffaelligkeitBenfordTestChartErgebnis;
  }

  public Set<Stimmen> getAktuelleStimmen(Gruppe gruppeGueltige) {
    if (stimmenhochrechnerAusgabedaten == null) {
      return Collections.emptySet();
    }
    return getStimmen(stimmenhochrechnerAusgabedaten.getAktuellAnzahlen(), gruppeGueltige);
  }

  public Set<Stimmen> getHochgerechneteStimmen(AnzahlStimmenProWaehlerNachStimmenartBestimmer anzahlStimmenProWaehlerNachStimmenartBestimmer, Gruppe gruppeGueltige) {
    if (stimmenhochrechnerAusgabedaten == null) {
      return Collections.emptySet();
    }
    return getStimmen(stimmenhochrechnerAusgabedaten.getHochgerechnetAlsAnzahlen(anzahlStimmenProWaehlerNachStimmenartBestimmer), gruppeGueltige);
  }

  private Set<Stimmen> getStimmen(GebietDaten<Long> gebietDaten, Gruppe gruppeGueltige) {
    Set<Stimmen> result = new HashSet<>();

    gebietDaten.getGueltige()
        .forEach(((stimmenart, zahl) -> result.add(createStimmen(gruppeGueltige, getGebiet(), stimmenart, zahl))));

    gebietDaten.getParteiStimmen().values().forEach(parteistimme -> {
      if (parteistimme != null) {
        parteistimme.getStimmen()
            .forEach(((stimmenart, zahl) -> result.add(createStimmen(parteistimme.getPartei(), getGebiet(), stimmenart, zahl))));
      }
    });
    return result;
  }

  private Stimmen createStimmen(Gruppe gruppe, Gebiet gebiet, Stimmenart stimmenart, Long anzahl) {
    Stimmen stimmen = new Stimmen();
    stimmen.setAnzahl(anzahl);
    stimmen.setGruppe(gruppe);
    stimmen.setGebiet(gebiet);
    stimmen.setStimmenart(stimmenart);
    return stimmen;
  }
}
