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

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

import java.util.List;

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

  private static final long ABBRUCH_ANZAHL_SITZE_ZU_VERTEILEN = 5000L;

  public SainteLagueVerteilungMitMindestsitzen(FractionConverter fractionConverter) {
    super(fractionConverter);
  }

  @Override
  protected SainteLagueHoechstzahlErgebnis verteileSitze(VerteilungContext context, List<? extends Teilnehmer> teilnehmerInVerteilung) {

    long anzahlSitzeZuVerteilen = Math.max(getSummeMindestsitze(teilnehmerInVerteilung), context.getAnzahlSitze());

    VerteilungContext contextMitAngepassterAnzahlSitzen = new VerteilungContext(context.getFractionConverter(), anzahlSitzeZuVerteilen, teilnehmerInVerteilung);

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

    while (!ergebnis.isEntschieden() || !isMindestsitzeFuerAlleTeilnehmerErreicht(ergebnis)) {
      if (anzahlSitzeZuVerteilen >= ABBRUCH_ANZAHL_SITZE_ZU_VERTEILEN) {
        throw new RuntimeException("Die Anzahl zu verteilender Sitze hat einen Wert von " + ABBRUCH_ANZAHL_SITZE_ZU_VERTEILEN + " erreicht, ohne dass das Ergebnis entschieden ist und alle Teilnehmer ihre Mindestsitze erhalten haben. Die Verteilung wird abgebrochen.");
      }
      contextMitAngepassterAnzahlSitzen = new VerteilungContext(context.getFractionConverter(), ++anzahlSitzeZuVerteilen, teilnehmerInVerteilung);
      ergebnis = verteileSitze(contextMitAngepassterAnzahlSitzen, teilnehmerInVerteilung, contextMitAngepassterAnzahlSitzen.getHareQuote());
    }

    return ergebnis;
  }

  private long getSummeMindestsitze(List<? extends Teilnehmer> teilnehmerInVerteilung) {
    return teilnehmerInVerteilung.stream().mapToLong(Teilnehmer::getMindestsitze).sum();
  }

  private boolean isMindestsitzeFuerAlleTeilnehmerErreicht(SainteLagueHoechstzahlErgebnis ergebnis) {
    return ergebnis.getTeilnehmerErgebnisse().stream().noneMatch(teilnehmerErgebnis -> teilnehmerErgebnis.getSitze() < teilnehmerErgebnis.getTeilnehmer().getMindestsitze());
  }
}
