
package de.ivu.elect.sitzberechnung.verteilung.saintelague;

import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.function.Predicate;

import de.ivu.elect.sitzberechnung.math.FractionConverter;
import de.ivu.elect.sitzberechnung.verteilung.Teilnehmer;
import de.ivu.elect.sitzberechnung.verteilung.TeilnehmerErgebnis;
import de.ivu.elect.sitzberechnung.verteilung.VerteilungContext;

/**
 * Verteilt solange Sitze nach dem Höchstzahlverfahren, bis alle Teilnehmer ihre Anzahl an Mindestsitzen erreicht haben.
 */
public class SainteLagueVerteilungMitMindestsitzen extends SainteLagueVerteilung {

  private static final long ANZAHL_SITZE_ZU_VERTEILEN_MAX = 1000L;

  private final long anzahlSitzeZuVerteilenMax;

  public SainteLagueVerteilungMitMindestsitzen(FractionConverter fractionConverter) {
    this(fractionConverter, ANZAHL_SITZE_ZU_VERTEILEN_MAX);
  }

  public SainteLagueVerteilungMitMindestsitzen(FractionConverter fractionConverter, long anzahlSitzeZuVerteilenMax) {
    super(fractionConverter);

    this.anzahlSitzeZuVerteilenMax = anzahlSitzeZuVerteilenMax;
  }

  @Override
  protected SainteLagueHoechstzahlErgebnis verteileSitze(VerteilungContext context, List< ? extends Teilnehmer> teilnehmerInVerteilung) {
    /**
     * Aufruf dieser Methode setzt voraus, dass mindestens eine gültige Stimme für irgendeinen Teilnehmer erfasst wurde, da ansonsten SainteLagueVerteilung#verteileSitzeKeineStimmen aufgerufen worden
     * wäre...
     *
     * ... SPEZIALFALL ELECT-16389, hat ein Teilnehmer 0 Stimmen sind auch dessen Höchstzahlen ALLE 0! Das heißt, ein Teilnehmer mit 0 Stimmen wird auch bei beliebig vielen Iterationen NIE einen
     * zusätzlichen Sitz bekommen, da ein anderer Teilnehmer mit mindestens 1 Stimme existiert, dessen x-te Höchstzahl IMMER größer sein wird als 0.
     */
    Predicate< ? super TeilnehmerErgebnis> anzahlMindestsitzeErreicht = teilnehmerErgebnis -> teilnehmerErgebnis.getStimmen() < 1L
        || teilnehmerErgebnis.getSitze() >= teilnehmerErgebnis.getTeilnehmer().getMindestsitze();

    List< ? extends Teilnehmer> teilnehmerInVerteilungMitStimmen = teilnehmerInVerteilung.stream().filter(teilnehmer -> teilnehmer.getStimmen() > 0L).collect(toList());
    long anzahlMindestsitzeMitStimmen = teilnehmerInVerteilungMitStimmen.stream().mapToLong(Teilnehmer::getMindestsitze).sum();

    long anzahlSitzeZuVerteilenInitial = Math.max(anzahlMindestsitzeMitStimmen, context.getAnzahlSitze());

    return verteileSitzeIterativ(context.getFractionConverter(), teilnehmerInVerteilung, anzahlMindestsitzeErreicht, anzahlSitzeZuVerteilenInitial, anzahlSitzeZuVerteilenMax);
  }

  private SainteLagueHoechstzahlErgebnis verteileSitzeIterativ(FractionConverter fractionConverter, List< ? extends Teilnehmer> teilnehmerInVerteilung,
      Predicate< ? super TeilnehmerErgebnis> anzahlMindestsitzeErreicht, long anzahlSitzeZuVerteilenInitial, long anzahlSitzeZuVerteilenMax) {
    VerteilungContext context = new VerteilungContext(fractionConverter, anzahlSitzeZuVerteilenInitial, teilnehmerInVerteilung);

    SainteLagueHoechstzahlErgebnis ergebnis = verteileSitze(context, teilnehmerInVerteilung, context.getHareQuote());

    while (!ergebnis.isEntschieden() || !ergebnis.getTeilnehmerErgebnisse().stream().allMatch(anzahlMindestsitzeErreicht)) {
      if (context.getAnzahlSitze() >= anzahlSitzeZuVerteilenMax) {
        throw new ArithmeticException("Die Anzahl zu verteilender Sitze hat einen Wert von " + anzahlSitzeZuVerteilenMax
            + " erreicht, ohne dass das Ergebnis entschieden ist und alle Teilnehmer ihre Anzahl an Mindestsitzen erreicht haben. DIE VERTEILUNG WIRD ABGEBROCHEN!");
      }

      context = new VerteilungContext(fractionConverter, context.getAnzahlSitze() + 1L, teilnehmerInVerteilung);

      ergebnis = verteileSitze(context, teilnehmerInVerteilung, context.getHareQuote());
    }

    return ergebnis;
  }
}
