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

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

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


import static de.ivu.elect.business.hochrechnung.control.StimmenHochrechnerHilfsfunktionen.notNull;

public class GebietDatenAnteileToGanzeZahlenConverter {

  public Map<Stimmenart, Long> berechneGueltigeAnzahlen(Map<Stimmenart, Double> gueltige, Map<Stimmenart, Long> anzahlAbgegebeneStimmen) {
    Map<Stimmenart, Long> result = new EnumMap<>(Stimmenart.class);
    gueltige.forEach((stimmenart, anteil) ->
        result.put(stimmenart, getStimmenAnzahl(anzahlAbgegebeneStimmen.get(stimmenart), anteil)));
    return result;
  }

  private Long getStimmenAnzahl(Long anzahlAbgegebeneStimmen, Double anteil) {
    if (anzahlAbgegebeneStimmen == null || anteil == null) {
      return null;
    }
    return Math.round(anteil * anzahlAbgegebeneStimmen);
  }

  public Map<Stimmenart, Long> berechneUngueltigeStimmenVonRestlichenWaehler(Map<Stimmenart, Long> gueltige, Map<Stimmenart, Long> anzahlAbgegebeneStimmen) {
    Map<Stimmenart, Long> result = new EnumMap<>(Stimmenart.class);
    gueltige.forEach((stimmenart, anzahl) -> result.put(stimmenart, getAnzahlRestlicherWaehler(anzahlAbgegebeneStimmen.get(stimmenart), anzahl)));
    return result;
  }

  private Long getAnzahlRestlicherWaehler(Long anzahlAbgegebeneStimmen, Long anzahlAbzuziehen) {
    if (anzahlAbgegebeneStimmen == null) {
      return null;
    }
    return anzahlAbgegebeneStimmen - notNull(anzahlAbzuziehen);
  }




  public Map<PolitischeGruppe, ParteiStimmen<Long>> berechneParteiStimmenAnzahlen(Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen, Map<Stimmenart, Long> gueltige) {
    Map<Stimmenart, Map<PolitischeGruppe, Long>> anzahlStimmenProPolitischeGruppeProStimmenart = berechneAnzahlStimmenProPolitischeGruppeProStimmenart(parteiStimmen, gueltige);
    return erzeugeParteiStimmenanzahlenMap (anzahlStimmenProPolitischeGruppeProStimmenart, parteiStimmen);
  }

  private Map<PolitischeGruppe, ParteiStimmen<Long>> erzeugeParteiStimmenanzahlenMap(Map<Stimmenart, Map<PolitischeGruppe, Long>> anzahlStimmenProPolitischeGruppeProStimmenart, Map<PolitischeGruppe, ParteiStimmen<Double>> alsAnteile) {
    Map<PolitischeGruppe, ParteiStimmen<Long>> result = new HashMap<>();

    anzahlStimmenProPolitischeGruppeProStimmenart.forEach((stimmenart, parteistimmen) -> parteistimmen.forEach((politischeGruppe, anzahl) -> {
      result.putIfAbsent(politischeGruppe, new ParteiStimmen<Long>(politischeGruppe, new EnumMap<>(Stimmenart.class)));
      result.get(politischeGruppe).putStimmen(stimmenart, anzahl);
      if (stimmenart == Stimmenart.LISTENSTIMME) {
        result.get(politischeGruppe).addAllListenKandidaturStimmen(berechneListenkandidaturStimmenAnzahlen(anzahl, alsAnteile.get(politischeGruppe).getListenkandidaturStimmen()));
      }
    }));

    return result;
  }

  public Map<Listenkandidatur, Long> berechneListenkandidaturStimmenAnzahlen(Long anzahl, Map<Listenkandidatur, Double> listenkandidaturStimmenAnteile) {
    if (anzahl == null || anzahl == 0 || listenkandidaturStimmenAnteile.size() == 0) {
      return Collections.emptyMap();
    }
    Map<Listenkandidatur, Long> result = new HashMap<>();

    List<Listenkandidatur> listenkandidaturen = new ArrayList<>();
    List<Double> entsprechendeAnteile = new ArrayList<>();

    listenkandidaturStimmenAnteile
        .forEach((listenkandidatur, anteil) -> {
          listenkandidaturen.add(listenkandidatur);
          entsprechendeAnteile.add(anteil);
        });

    List<Long> entsprechendeAnzahlen = new AnteileAufAnzahlenMapper().mappeAnteileAufAnzahlen(anzahl, entsprechendeAnteile);

    for (int i = 0;i < entsprechendeAnzahlen.size();i++) {
      result.put(listenkandidaturen.get(i), entsprechendeAnzahlen.get(i));
    }
    return result;
  }

  private Map<Stimmenart, Map<PolitischeGruppe, Long>> berechneAnzahlStimmenProPolitischeGruppeProStimmenart(Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen, Map<Stimmenart, Long> gueltige) {
    Map<Stimmenart, Map<PolitischeGruppe, Long>> result = new EnumMap<>(Stimmenart.class);
    for (Map.Entry<Stimmenart, Long> gueltigeStimmen: gueltige.entrySet()) {
      result.put(gueltigeStimmen.getKey(), berechneAnzahlStimmenProPolitischeGruppeFuerStimmenart(parteiStimmen, gueltigeStimmen));
    }
    return result;
  }

  private Map<PolitischeGruppe, Long> berechneAnzahlStimmenProPolitischeGruppeFuerStimmenart(Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen, Map.Entry<Stimmenart, Long> gueltigeStimmen) {
    if (gueltigeStimmen.getValue() == null || gueltigeStimmen.getValue() == 0) {
      return Collections.emptyMap();
    }

    List<PolitischeGruppe> politischeGruppen = new ArrayList<>(parteiStimmen.keySet());
    List<Double> entsprechendeAnteile = getStimmenanteileVonEntsprechendenPolitischenGruppen(politischeGruppen, gueltigeStimmen.getKey(), parteiStimmen);
    List<Long> entsprechendeAnzahlen = new AnteileAufAnzahlenMapper().mappeAnteileAufAnzahlen(gueltigeStimmen.getValue(), entsprechendeAnteile);

    Map<PolitischeGruppe, Long> result = new HashMap<>();
    for (int i = 0;i < entsprechendeAnzahlen.size();i++) {
      result.put(politischeGruppen.get(i), entsprechendeAnzahlen.get(i));
    }
    return result;
  }

  private List<Double> getStimmenanteileVonEntsprechendenPolitischenGruppen(List<PolitischeGruppe> politischeGruppen, Stimmenart stimmenart, Map<PolitischeGruppe, ParteiStimmen<Double>> parteiStimmen) {
    List<Double> result = new ArrayList<>();
    politischeGruppen.forEach(politischeGruppe -> {
      ParteiStimmen<Double> parteiStimme = parteiStimmen.get(politischeGruppe);
      if (parteiStimme != null) {
        result.add(parteiStimme.getStimmen(stimmenart));
      }
    });
    return result;
  }

  public Map<Stimmenart, Long> getAnzahlAbgegebeneStimmen(GebietDaten<Double> gebietDatenAlsAnteile, AnzahlStimmenProWaehlerNachStimmenartBestimmer anzahlStimmenProWaehlerBestimmer) {
    Long anzahlWaehler = gebietDatenAlsAnteile.getAnzahlWaehlerImGebiet().getGesamtNotNull();

    Map<Stimmenart, Long> result = new EnumMap<>(Stimmenart.class);

    Set<Stimmenart> stimmenarten = new HashSet<>(gebietDatenAlsAnteile.getGueltige().keySet());
    stimmenarten.addAll(gebietDatenAlsAnteile.getUngueltige().keySet());
    stimmenarten.add(Stimmenart.REFERENDUM_STIMME);

    stimmenarten
        .forEach(stimmenart -> result.put(stimmenart, getAnzahlAbgegebeneStimmenFuerStimmenart(stimmenart, gebietDatenAlsAnteile, anzahlWaehler, anzahlStimmenProWaehlerBestimmer)));

    return result;
  }

  private Long getAnzahlAbgegebeneStimmenFuerStimmenart(Stimmenart stimmenart, GebietDaten<Double> gebietDatenAlsAnteile, Long waehlerAnzahl, AnzahlStimmenProWaehlerNachStimmenartBestimmer anzahlStimmenProWaehlerBestimmer) {
    Long anzahlStimmenProStimmenart = anzahlStimmenProWaehlerBestimmer.getAnzahlVomWaehlerBenutzteStimmenDurchschnittlich(stimmenart, gebietDatenAlsAnteile.getGebiet());
    if (waehlerAnzahl == null  || anzahlStimmenProStimmenart == null || waehlerAnzahl == 0L  || anzahlStimmenProStimmenart == 0L) {
      return null;
    }
    return waehlerAnzahl * anzahlStimmenProStimmenart;
  }

  public Map<Gruppe, ReferendumFrage<Long>> berechneReferendumFragenStimmenAnzahlen(Map<Gruppe, ReferendumFrage<Double>> referendumFragen, Map<Stimmenart, Long> anzahlAbgegebeneStimmen) {
    Long anzahlStimmenGesamt = anzahlAbgegebeneStimmen.get(Stimmenart.REFERENDUM_STIMME);
    Map<Gruppe, ReferendumFrage<Long>> result = new HashMap<>();

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

    return result;
  }

  private ReferendumFrage<Long> berechneReferendumFragenStimmenAnzahlen(Gruppe referendumVorlage, ReferendumFrage<Double> referendumFrage, Long anzahlStimmenGesamt) {
    if (referendumFrage == null) {
      return null;
    }
    Long gueltige = getStimmenAnzahl(anzahlStimmenGesamt, referendumFrage.getGueltige());
    Long ungueltige = getAnzahlRestlicherWaehler(anzahlStimmenGesamt, gueltige);
    Map<ReferendumOption, Long> stimmenPerReferendumOption = berechneStimmenPerReferendumOptionAnzahlen(referendumFrage.getStimmenPerReferendumOption(), gueltige);
    return new ReferendumFrage<>(gueltige, ungueltige, referendumVorlage, stimmenPerReferendumOption);
  }

  private Map<ReferendumOption, Long> berechneStimmenPerReferendumOptionAnzahlen(Map<ReferendumOption, Double> stimmenPerReferendumOption, Long gueltigeStimmen) {
    if (gueltigeStimmen == null || gueltigeStimmen == 0) {
      return Collections.emptyMap();
    }

    List<ReferendumOption> referendumOptionen = new ArrayList<>();
    List<Double> entsprechendeAnteile = new ArrayList<>();
    stimmenPerReferendumOption.forEach(((referendumOption, stimmenanteil) -> {
      referendumOptionen.add(referendumOption);
      entsprechendeAnteile.add(stimmenanteil);
    }));
    List<Long> entsprechendeAnzahlen = new AnteileAufAnzahlenMapper().mappeAnteileAufAnzahlen(gueltigeStimmen, entsprechendeAnteile);

    Map<ReferendumOption, Long> result = new HashMap<>();
    for (int i = 0;i < entsprechendeAnzahlen.size();i++) {
      result.put(referendumOptionen.get(i), entsprechendeAnzahlen.get(i));
    }
    return result;
  }
}
