
package de.ivu.elect.business.stimmeingabe.control.fiktive;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.hochrechnung.control.GebietDatenAnteileToGanzeZahlenConverter;
import de.ivu.elect.business.hochrechnung.entity.AnzahlStimmenProWaehlerNachStimmenartBestimmer;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmen;
import de.ivu.elect.business.hochrechnung.entity.StimmenhochrechnerGebietAusgabedaten;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmenErfassenModel;

public class ListenkandidaturStimmeingabenAusFiktivwertenSetzer extends AbstractStimmeingabenAusFiktivwertenSetzer {

  public ListenkandidaturStimmeingabenAusFiktivwertenSetzer() {
    super();
  }

  @Override
  public boolean isVomLetztenErgebniseingangUebernommeneStimmenZuUeberschreiben(Ergebniseingang ergebniseingangAktuell, Collection<Stimmeingabe> uebernommeneStimmeingaben) {
    return true;
    // weil der Verteilungsalgorithmus hier nur die Differenz (zu verteilenden - verteilt) verteilt.
  }

  @Override
  public void setzeErgebniseingangStimmeingabenAnzahlAusHochgerechnetenWerten(Ergebniseingang ergebniseingang, StimmenhochrechnerGebietAusgabedaten hochrechnerAusgabedaten, AnzahlStimmenProWaehlerNachStimmenartBestimmer anzahlStimmenProWaehlerBestimmer) {
    if (ergebniseingang.isStimmeingabeGruppeStimmenErfassenModelSet() && ergebniseingang.hasStimmeingabeListenkandidaturStimmenErfassenModels()) {

      Map<GruppeImGebiet, Stimmeingabe> zweitstimmenStimmeingabePerGruppeImGebiet = getListenstimmenStimmeingabenPerGruppeImGebiet(ergebniseingang);
      Map<GruppeImGebiet, List<Stimmeingabe>> listenkandidaturStimmeingabenPerGruppeImGebiet = getListenkandidaturStimmeingabenPerGruppeImGebiet(ergebniseingang);

      for (Map.Entry<GruppeImGebiet, Stimmeingabe> entry : zweitstimmenStimmeingabePerGruppeImGebiet.entrySet()) {
        GruppeImGebiet gruppeImGebiet = entry.getKey();
        verteileVerbleibendeListenkandidaturenStimmenFuerGruppe(entry.getValue(), listenkandidaturStimmeingabenPerGruppeImGebiet.get(gruppeImGebiet), hochrechnerAusgabedaten, gruppeImGebiet);
      }
    }
  }

  private void verteileVerbleibendeListenkandidaturenStimmenFuerGruppe(Stimmeingabe stimmeingabeFuerPolitischeGruppe, Collection<Stimmeingabe> listenkandidaturStimmeingaben, StimmenhochrechnerGebietAusgabedaten hochrechnerAusgabedaten, GruppeImGebiet gruppeImGebiet) {
    // checking fuer extra Sicherheit, dass wir keine Nullen haben
    if (stimmeingabeFuerPolitischeGruppe == null || gruppeImGebiet == null || !gruppeImGebiet.isPolitischeGruppe() || hochrechnerAusgabedaten == null) {
      return;
    }
    PolitischeGruppe politischeGruppe = castTo(gruppeImGebiet.getGruppe(), PolitischeGruppe.class);
    if (politischeGruppe == null) {
      return;
    }
    GebietDaten<Double> hochgerechnetAnteile = hochrechnerAusgabedaten.getHochgerechnetAnteile();
    if (hochgerechnetAnteile == null) {
      return;
    }
    long anzahlGesamt = stimmeingabeFuerPolitischeGruppe.getAnzahl();
    verteileVerbleibendeListenkandidaturenStimmenFuerGesamtStimmenanzahl(anzahlGesamt, listenkandidaturStimmeingaben, hochgerechnetAnteile, politischeGruppe);
  }

  void verteileVerbleibendeListenkandidaturenStimmenFuerGesamtStimmenanzahl(long stimmenanzahlGesamt, Collection<Stimmeingabe> listenkandidaturStimmeingaben, GebietDaten<Double> hochgerechnetAnteile, PolitischeGruppe politischeGruppe) {
    long anzahlVerteilt = getAnzahlverteilteListenkandidaturenStimmen(listenkandidaturStimmeingaben);
    long anzahlZuVerteilenden = stimmenanzahlGesamt - anzahlVerteilt;
    if (anzahlZuVerteilenden < 1) {
      return;
    }
    verteileVerbleibendeAnzahlAufListenkandidaturenStimmen(anzahlZuVerteilenden, listenkandidaturStimmeingaben, hochgerechnetAnteile, politischeGruppe);
  }

  private long getAnzahlverteilteListenkandidaturenStimmen(Collection<Stimmeingabe> listenkandidaturStimmeingaben) {
    return listenkandidaturStimmeingaben.stream().mapToLong(this::getAnzahlStimmenFuerListenkandidatur).sum();
  }

  private long getAnzahlStimmenFuerListenkandidatur(Stimmeingabe listenkandidaturStimmeingabe) {
    if (listenkandidaturStimmeingabe != null && listenkandidaturStimmeingabe.getListenkandidatur() != null) {
      return listenkandidaturStimmeingabe.getAnzahl();
    }
    return 0;
  }

  private void verteileVerbleibendeAnzahlAufListenkandidaturenStimmen(Long anzahl, Collection<Stimmeingabe> listenkandidaturStimmeingaben, GebietDaten<Double> hochgerechnetAnteile, PolitischeGruppe politischeGruppe) {
    ParteiStimmen<Double> parteiStimmen = hochgerechnetAnteile.getParteiStimmen().get(politischeGruppe);
    verteileVerbleibendeAnzahlAufListenkandidaturenStimmenNachAnteilen(anzahl, listenkandidaturStimmeingaben, parteiStimmen);
  }

  private void verteileVerbleibendeAnzahlAufListenkandidaturenStimmenNachAnteilen(Long anzahl, Collection<Stimmeingabe> listenkandidaturStimmeingaben, ParteiStimmen<Double> parteiStimmenAnteile) {
    if (parteiStimmenAnteile == null) {
      return;
    }
    // Wir brauchen getListenkandidaturStimmenAnteileFuerGegebeneListenkandidaturen eigentlich nicht, wenn beide Listenkandidaturenmengen gleich wären
    Map<Listenkandidatur, Long> listenkandidaturStimmen = new GebietDatenAnteileToGanzeZahlenConverter()
        .berechneListenkandidaturStimmenAnzahlen(anzahl, getListenkandidaturStimmenAnteileFuerGegebeneListenkandidaturen(parteiStimmenAnteile.getListenkandidaturStimmen(), listenkandidaturStimmeingaben));
    addlistenkandidaturStimmeingabenPerGruppeImGebietAus(listenkandidaturStimmeingaben, listenkandidaturStimmen);
  }

  private Map<Listenkandidatur, Double> getListenkandidaturStimmenAnteileFuerGegebeneListenkandidaturen(Map<Listenkandidatur, Double> listenkandidaturenAnteile, Collection<Stimmeingabe> gegebeneListenkandidaturStimmeingaben) {
    Map<Listenkandidatur, Double> result = new HashMap<>();
    for (Stimmeingabe stimmeingabe : gegebeneListenkandidaturStimmeingaben) {
      Listenkandidatur listenkandidatur = stimmeingabe.getListenkandidatur();
      if (listenkandidatur != null) {
        result.put(listenkandidatur, listenkandidaturenAnteile.get(listenkandidatur));
      }
    }
    return result;
  }

  private void addlistenkandidaturStimmeingabenPerGruppeImGebietAus(Collection<Stimmeingabe> listenkandidaturStimmeingaben, Map<Listenkandidatur, Long> listenkandidaturStimmen) {
    for (Stimmeingabe stimmeingabe : listenkandidaturStimmeingaben) {
      addListenkandidaturStimmenEingabeAnzahlAufVorhandeneAnzahl(listenkandidaturStimmen, stimmeingabe);
    }
  }

  private Map<GruppeImGebiet, List<Stimmeingabe>> getListenkandidaturStimmeingabenPerGruppeImGebiet(Ergebniseingang ergebniseingang) {
    Map<GruppeImGebiet, List<Stimmeingabe>> listenkandidaturStimmeingabenPerGruppeImGebiet = new HashMap<>();

    for (StimmeingabeListenkandidaturStimmenErfassenModel listenkandidaturStimmenErfassenModel : ergebniseingang.getStimmeingabeListenkandidaturStimmenErfassenModels()) {
      List<Stimmeingabe> stimmeingaben = listenkandidaturStimmenErfassenModel.getListenkandidaturStimmen().stream() //
          .filter(StimmeingabeListenkandidaturStimmen::isAnzahlEingabeErfassbar) // ELECTKOM-2958
          .flatMap(stimmeingabeListenkandidaturStimmen -> stimmeingabeListenkandidaturStimmen.getStimmeingaben().stream()) //
          .collect(toList());

      listenkandidaturStimmeingabenPerGruppeImGebiet.computeIfAbsent(listenkandidaturStimmenErfassenModel.getBezugGruppeImGebiet(), ignored -> new ArrayList<>()).addAll(stimmeingaben);
    }

    return listenkandidaturStimmeingabenPerGruppeImGebiet;
  }

  private Map<GruppeImGebiet, Stimmeingabe> getListenstimmenStimmeingabenPerGruppeImGebiet(Ergebniseingang ergebniseingang) {
    Map<GruppeImGebiet, Stimmeingabe> result = new HashMap<>();

    for (Stimmeingabe stimmeingabe : ergebniseingang.getStimmeingaben()) {
      if (isStimmeingabeFuerListenstimmenFuerPartei(stimmeingabe)) {
        result.put(stimmeingabe.getGruppeImGebiet(), stimmeingabe);
      }
    }

    return result;
  }

  protected boolean isStimmeingabeFuerListenstimmenFuerPartei(Stimmeingabe stimmeingabe) {
    return stimmeingabe.isGruppeStimmeingabe() && !stimmeingabe.isSystemGruppenStimmeingabe() && stimmeingabe.getStimmenart().isListenstimme();
  }
}
