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

import static java.util.Collections.emptyList;

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

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

/**
 * Implementation der Verteilung nach Sainte-Laguë (Divisorverfahren mit Standardrundung) durch Anwendung des Höchstzahlverfahrens.
 *
 * <p>
 * Die Verteilung von Restsitzen per Los ist nicht enthalten. Es werden im Ergebnisobjekt lediglich die dafür erforderlichen Informationen geliefert.
 *
 * <p>
 * Es werden die Höchstzahlen (in einer Matrix) mit ungeraden Teilern (Folge +1 -> 0,5 – 1,5 – 2,5 – 3,5 – (n−0,5) oder auch Folge +2 -> 1 – 3 – 5 – 7 – (2n−1) mit n = Anzahl der Sitze) berechnet,
 * anhand deren aufsteigender Reihenfolge die Verteilung der Sitze vorgenommen wird.
 *
 * <p>
 * Bspw. bei 5 Parteien, 100 Stimmen und 10 Sitzen...
 *
 * <p>
 * ... Folge +1 -> 0,5 – 1,5 – 2,5 – 3,5 – (n−0,5)...
 *
 * <pre>
 * _________________________________________________________________
 * |_Teiler_|_Partei A_|_Partei B_|_Partei C_|_Partei D_|_Partei E_|
 * _________________________________________________________________
 * |____0,5_|____86,00_|____66,00_|____24,00_|____16,00_|_____8,00_|
 * _________________________________________________________________
 * |____1,5_|___~28,67_|____22,00_|_____8,00_|____~5,33_|______..._|
 * _________________________________________________________________
 * |____2,5_|____17,20_|____13,20_|______..._|______..._|______..._|
 * _________________________________________________________________
 * |____3,5_|___~12,29_|____~9,43_|______..._|______..._|______..._|
 * _________________________________________________________________
 * |____4,5_|____~9,56_|______..._|______..._|______..._|______..._|
 * </pre>
 *
 * <p>
 * ... Folge +2 -> 1 – 3 – 5 – 7 – (2n−1))...
 *
 * <pre>
 * _________________________________________________________________
 * |_Teiler_|_Partei A_|_Partei B_|_Partei C_|_Partei D_|_Partei E_|
 * _________________________________________________________________
 * |______1_|____43,00_|____33,00_|____12,00_|_____8,00_|_____4,00_|
 * _________________________________________________________________
 * |______3_|___~14,33_|____11,00_|_____4,00_|____~2,67_|______..._|
 * _________________________________________________________________
 * |______5_|_____8,60_|_____6,60_|______..._|______..._|______..._|
 * _________________________________________________________________
 * |______7_|____~6,14_|____~4,71_|______..._|______..._|______..._|
 * _________________________________________________________________
 * |______9_|____~4,78_|______..._|______..._|______..._|______..._|
 * </pre>
 *
 * <p>
 * ... demnach ergibt sich die Verteilung...
 * <ol>
 * <li>Partei A -> I: 86,00, II: 43,00</li>
 * <li>Partei B -> I: 66,00, II: 33,00</li>
 * <li>Partei A -> I: ~28,67, II: ~14,33</li>
 * <li>Partei C -> I: 24,00, II: 12,00</li>
 * <li>Partei B -> I: 22,00, II: 11,00</li>
 * <li>Partei A -> I: 17,20, II: 8,60</li>
 * <li>Partei D -> I: 16,00, II: 8,00</li>
 * <li>Partei B -> I: 13,20, II: 6,60</li>
 * <li>Partei A -> I: ~12,29, II: ~6,14</li>
 * <li>Partei A -> I: ~9,56, II: ~4,78</li>
 * </ol>
 */
public class SainteLagueVerteilung extends AbstractVerteilung<SainteLagueHoechstzahlTeilnehmerErgebnis, SainteLagueHoechstzahlErgebnis> {

  public SainteLagueVerteilung() {
    this(FractionConverter.convertingExakt());
  }

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

  @Override
  protected SainteLagueHoechstzahlErgebnis verteileSitzeKeineSitze(VerteilungContext context, List< ? extends Teilnehmer> teilnehmerInVerteilung) {
    return SainteLagueHoechstzahlErgebnis.ohneVerteilung(context, teilnehmerInVerteilung);
  }

  @Override
  protected SainteLagueHoechstzahlErgebnis verteileSitzeKeineStimmen(VerteilungContext context, List< ? extends Teilnehmer> teilnehmerInVerteilung) {
    return SainteLagueHoechstzahlErgebnis.ohneVerteilung(context, teilnehmerInVerteilung);
  }

  @Override
  protected SainteLagueHoechstzahlErgebnis verteileSitze(VerteilungContext context, List< ? extends Teilnehmer> teilnehmerInVerteilung) {
    return verteileSitze(context, teilnehmerInVerteilung, context.getHareQuote());
  }

  protected SainteLagueHoechstzahlErgebnis verteileSitze(VerteilungContext context, List< ? extends Teilnehmer> teilnehmerInVerteilung, Fraction divisorInitial) {
    Fraction divisor = context.getFractionConverter().asFractionConverted(divisorInitial);

    List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse = new ArrayList<>();
    long anzahlSitzeNachAbrundungVerteilt = 0L;

    /*
     * Hier wird künstlich zwischen Sitzen nach Abrundung (== Sitze, die ein Teilnehmer auf jeden Fall bekommt) und Restsitzen (== zusätzliche Sitze ohne Losverfahren) unterschieden, um den
     * Erwartungen von TeilnehmerErgebnis#getSitze zu entsprechen. An der Verteilung der Sitze ändert sich nichts!
     */

    for (Teilnehmer teilnehmer : teilnehmerInVerteilung) {
      SainteLagueHoechstzahlTeilnehmerErgebnis teilnehmerErgebnisNachAbrundung = new SainteLagueHoechstzahlTeilnehmerErgebnis(teilnehmer, divisor);
      teilnehmerErgebnisse.add(teilnehmerErgebnisNachAbrundung);

      anzahlSitzeNachAbrundungVerteilt += teilnehmerErgebnisNachAbrundung.getSitzeAusAbrundung();
    }

    // Für alle Teilnehmer alle Höchstzahlen (maximal Anzahl der Sitze) berechnen.
    for (SainteLagueHoechstzahlTeilnehmerErgebnis teilnehmerErgebnis : teilnehmerErgebnisse) {
      List<SainteLagueHoechstzahl> hoechstzahlen = new ArrayList<>();

      for (int teilerIndexZeroBased = 0; teilerIndexZeroBased < context.getAnzahlSitze(); teilerIndexZeroBased++) {
        SainteLagueHoechstzahl hoechstzahl = new SainteLagueHoechstzahl(teilnehmerErgebnis, teilerIndexZeroBased);
        hoechstzahlen.add(hoechstzahl);
      }

      teilnehmerErgebnis.setHoechstzahlen(hoechstzahlen);
    }

    return verteileRestsitze(context, teilnehmerErgebnisse, anzahlSitzeNachAbrundungVerteilt);
  }

  private SainteLagueHoechstzahlErgebnis verteileRestsitze(VerteilungContext context, List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse, long anzahlSitzeNachAbrundungVerteilt) {
    SainteLagueHoechstzahlSortierer hoechstzahlSortierer = new SainteLagueHoechstzahlSortierer(context.getFractionConverter(), context.getAnzahlSitze(), teilnehmerErgebnisse);

    // Anhand der berechneten Höchstzahlen die Anzahlen der Restsitze der Teilnehmer aktualisieren.
    Map<SainteLagueHoechstzahlTeilnehmerErgebnis, List<SainteLagueHoechstzahl>> hoechstzahlenMitSitzenEntschiedenPerTeilnehmerErgebnis = hoechstzahlSortierer
        .getHoechstzahlenMitSitzenEntschiedenPerTeilnehmerErgebnis();

    for (SainteLagueHoechstzahlTeilnehmerErgebnis teilnehmerErgebnis : teilnehmerErgebnisse) {
      List<SainteLagueHoechstzahl> hoechstzahlenMitSitzenEntschieden = hoechstzahlenMitSitzenEntschiedenPerTeilnehmerErgebnis.getOrDefault(teilnehmerErgebnis, emptyList());
      long sitzeAusHoechstzahlen = hoechstzahlenMitSitzenEntschieden.size();

      teilnehmerErgebnis.vergibRestsitzeAusHoechstzahlen(sitzeAusHoechstzahlen);
    }

    if (hoechstzahlSortierer.isEntschieden()) {
      return new SainteLagueHoechstzahlErgebnis(context, teilnehmerErgebnisse);
    }

    // Anhand der berechneten Höchstzahlen die Teilnehmer im Losverfahren markieren.
    List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisseImLosverfahren = new ArrayList<>(hoechstzahlSortierer.getHoechstzahlenMitSitzenUnentschiedenPerTeilnehmerErgebnis().keySet());
    long anzahlSitzeZuVerlosen = hoechstzahlSortierer.getAnzahlSitzeUnentschieden();

    for (SainteLagueHoechstzahlTeilnehmerErgebnis teilnehmerErgebnis : teilnehmerErgebnisseImLosverfahren) {
      teilnehmerErgebnis.seiTeilnehmerAmLosverfahren();
    }

    return new SainteLagueHoechstzahlErgebnis(context, teilnehmerErgebnisse, anzahlSitzeZuVerlosen, teilnehmerErgebnisseImLosverfahren);
  }
}
