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

import java.util.List;

import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;

public class Verteilung {

  private final VerteilungsSchluessel verteilungsSchluessel;

  public Verteilung(VerteilungsSchluessel verteilungsSchluessel) {
    this.verteilungsSchluessel = verteilungsSchluessel;
  }

  /**
   * Verteilt die zu verteilenden Stimmen auf die Stimmeingaben so, wie die Verteilungsschlüssel verteilt sind.
   */
  public void verteileStimmenAufStimmeingaben(long anzahlZuVerteilenderStimmen, List<Stimmeingabe> stimmeingaben) {

    verteileOhneBeachtungVonRundungsdifferenzen(anzahlZuVerteilenderStimmen, stimmeingaben);
    long stimmenZuVielVerteilt = summiereAnzahlen(stimmeingaben) - anzahlZuVerteilenderStimmen;
    korrigiereRundungsdifferenzenNachVerteilung(stimmeingaben, stimmenZuVielVerteilt);

  }

  private void verteileOhneBeachtungVonRundungsdifferenzen(long anzahlZuVerteilenderStimmen, List<Stimmeingabe> stimmeingaben) {

    if (verteilungsSchluessel.getAnzahl(stimmeingaben) != stimmeingaben.size()) {
      throw new IllegalArgumentException("Verteilungsschlüssel passt nicht zu den Stimmeingaben, auf die verteilt werden soll.");
    }

    double summeVerteilungsschluessel = verteilungsSchluessel.getSumme(stimmeingaben);

    for (int i = 0; i < stimmeingaben.size(); i++) {
      Stimmeingabe stimmeingabe = stimmeingaben.get(i);
      double faktor = verteilungsSchluessel.getWert(i, stimmeingabe) / summeVerteilungsschluessel;
      long verteilteStimmen = Math.round(faktor * anzahlZuVerteilenderStimmen);

      stimmeingabe.setAnzahl(verteilteStimmen);
    }
  }

  // NOTE, dass stimmenZuVielVerteilt auch negativ sein kann...!
  private void korrigiereRundungsdifferenzenNachVerteilung(List<Stimmeingabe> stimmeingaben, long stimmenZuVielVerteilt) {

    long nochAbzuziehendeStimmen = stimmenZuVielVerteilt;
    for (Stimmeingabe stimmeingabe : stimmeingaben) {
      long stimmeingabeAnzahl = stimmeingabe.getAnzahl();
      long stimmeingabeAnzahlKorrigiert = Math.max(0, stimmeingabeAnzahl - nochAbzuziehendeStimmen);

      stimmeingabe.setAnzahl(stimmeingabeAnzahlKorrigiert);
      nochAbzuziehendeStimmen -= stimmeingabeAnzahl - stimmeingabeAnzahlKorrigiert;
    }
  }

  private static long summiereAnzahlen(List<Stimmeingabe> stimmeingaben) {
    return stimmeingaben.stream().mapToLong(Stimmeingabe::getAnzahl).sum();
  }
}
