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

import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmen;
import de.ivu.elect.business.hochrechnung.entity.ReferendumFrage;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;

public class GebietDatenNormierer <N extends Number> {

  public Map<Stimmenart, Double> getNormierteGueltigeStimmen (Map<Stimmenart, N> gueltige, Map<Stimmenart, N> ungueltige) {
    Map<Stimmenart, Double> result = new EnumMap<>(Stimmenart.class);
    if (gueltige != null) {
      gueltige.keySet().forEach(stimmenart -> result.put(stimmenart, getGueltigeStimmenAnteil(stimmenart, gueltige, ungueltige)));
    }
    return result;
  }

  private Double getGueltigeStimmenAnteil(Stimmenart stimmenart, Map<Stimmenart, N> gueltige, Map<Stimmenart, N> ungueltige) {
    N gueltigeWert = gueltige.get(stimmenart);
    N ungueltigeWert = ungueltige.get(stimmenart);
    return getRatio (gueltigeWert, notNull(gueltigeWert) + notNull(ungueltigeWert));
  }

  public Map<Stimmenart, Double> berechneUngueltigeAnteileVonGueltige(Map<Stimmenart, Double> gueltige) {
    Map<Stimmenart, Double> result = new EnumMap<>(Stimmenart.class);
    gueltige.entrySet().stream()
        .filter(entry -> entry.getValue() != null && entry.getValue() > 0)
        .forEach(entry -> result.put(entry.getKey(), 1 - entry.getValue()));
    return result;
  }




  public Map<PolitischeGruppe, ParteiStimmen<Double>> getParteiStimmenAnteileNormiert(Map<PolitischeGruppe, ParteiStimmen<N>> parteiStimmen) {
    Map<PolitischeGruppe, ParteiStimmen<Double>> result = new HashMap<>();

    Map<Stimmenart, Double> stimmenSummeProStimmenart = berechneStimmenSummeProStimmenart(parteiStimmen);

    parteiStimmen.entrySet().stream().filter(entry -> entry.getValue() != null)
        .forEach(entry -> {
          result.put(entry.getKey(), getParteiStimmeAlsAnteileNormiert(entry, stimmenSummeProStimmenart));
        });

    return result;
  }

  private Map<Stimmenart, Double> berechneStimmenSummeProStimmenart(Map<PolitischeGruppe, ParteiStimmen<N>> parteiStimmen) {
    Map<Stimmenart, Double> result = new EnumMap<>(Stimmenart.class);

    parteiStimmen.values().stream().filter(Objects::nonNull)
        .forEach(parteiStimme -> parteiStimme.getStimmen()
            .forEach((stimmenart, zahl)
                -> result.put(stimmenart, result.getOrDefault(stimmenart, 0D) + notNull(zahl))));

    return result;
  }

  private ParteiStimmen<Double> getParteiStimmeAlsAnteileNormiert(Map.Entry<PolitischeGruppe, ParteiStimmen<N>> parteiStimmeEntry, Map<Stimmenart, Double> stimmenSummeProStimmenart) {
    Map<Stimmenart, Double> stimmen = new EnumMap<>(Stimmenart.class);

    parteiStimmeEntry.getValue().getStimmen()
        .forEach((stimmenart, zahl) -> stimmen.put(stimmenart, getRatio(zahl, stimmenSummeProStimmenart.getOrDefault(stimmenart, 0D))));

    Map<Listenkandidatur, Double> listenkandidaturStimmen = getListenKandidaturStimmenAlsAnteileNormiert(parteiStimmeEntry.getValue().getListenkandidaturStimmen());

    return new ParteiStimmen<>(parteiStimmeEntry.getKey(), stimmen, listenkandidaturStimmen);
  }

  private Map<Listenkandidatur, Double> getListenKandidaturStimmenAlsAnteileNormiert(Map<Listenkandidatur, N> listenkandidaturStimmen) {
    Map<Listenkandidatur, Double> result = new HashMap<>();
    double summe = listenkandidaturStimmen.values().stream().mapToDouble(this::notNull).sum();
    listenkandidaturStimmen
        .forEach((listenkandidatur, zahl) -> result.put(listenkandidatur, getRatio(zahl, summe)));
    return result;
  }

  private Double getRatio(N zahl, double summe) {
    if (zahl == null) {
      return null;
    }
    if (summe == 0) {
      return zahl.doubleValue();
    }
    return zahl.doubleValue() / summe;
  }

  private double notNull(N x) {
    return x != null ? x.doubleValue() : 0;
  }

  public Map<Gruppe, ReferendumFrage<Double>> getReferendumFragenStimmenAnteileNormiert(Map<Gruppe, ReferendumFrage<N>> referendumFragen) {

    Map<Gruppe, ReferendumFrage<Double>> result = new HashMap<>();

    referendumFragen.forEach(((referendumVorlage, referendumFrage) -> {
      result.put(referendumVorlage, getReferendumStimmenAnteileNormiert(referendumFrage));
    }));

    return result;
  }

  private ReferendumFrage<Double> getReferendumStimmenAnteileNormiert(ReferendumFrage<N> referendumFrage) {
    if (referendumFrage == null) {
      return null;
    }
    Double gueltige = getRatio(referendumFrage.getGueltige(), notNull(referendumFrage.getGueltige()) + notNull(referendumFrage.getUngueltige()));
    Double ungueltige = (gueltige != null && gueltige > 0)? 1 - gueltige : null;

    double sum = referendumFrage.getStimmenPerReferendumOption().values().stream().mapToDouble(this::notNull).sum();
    Map<ReferendumOption, Double> stimmenPerReferendumOption = new HashMap<>();

    referendumFrage.getStimmenPerReferendumOption().forEach(((referendumOption, stimmen) -> {
      stimmenPerReferendumOption.put(referendumOption, getRatio(stimmen, sum));
    }));

    return new ReferendumFrage<>(gueltige, ungueltige, referendumFrage.getReferendumVorlage(), stimmenPerReferendumOption);
  }
}
