
package de.ivu.elect.business.sitzberechnung.control.saintelague;

import static java.util.Collections.emptyList;

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

import de.ivu.elect.business.sitzberechnung.control.util.Fraction;
import de.ivu.elect.business.sitzberechnung.control.verteilung.AbstractVerteilung;
import de.ivu.elect.business.sitzberechnung.control.verteilung.Teilnehmer;

/**
 * Implementation der Verteilung nach Saine-Laguë ohne Bezug zum Datenmodell.
 * <p/>
 * Die Verteilung von Sitzen 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 berechnet berechnet, anhand deren aufsteigender Reihenfolge die Verteilung der Sitze vorgenommen wird. Bspw. bei 5 Parteien, 100
 * Stimmen und 10 Sitzen...
 *
 * <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>
 *
 * ... demnach ergibt sich die Verteilung...
 * <ol>
 * <li>Partei A (43,00)</li>
 * <li>Partei B (33,00)</li>
 * <li>Partei A (~14,33)</li>
 * <li>Partei C (12,00)</li>
 * <li>Partei B (11,00)</li>
 * <li>Partei A (8,60)</li>
 * <li>Partei D (8,00)</li>
 * <li>Partei B (6,60)</li>
 * <li>Partei A (~6,14)</li>
 * <li>Partei A (~4,78)</li>
 * </ol>
 */
public class SainteLagueVerteilung extends AbstractVerteilung<SainteLagueTeilnehmerErgebnis, SainteLagueErgebnis> {

  public SainteLagueVerteilung() {
    super();
  }

  @Override
  protected SainteLagueErgebnis createErgebnisOhneVerteilung(long anzahlSitze, List< ? extends Teilnehmer> dieTeilnehmer) {
    return SainteLagueErgebnis.ohneVerteilung(anzahlSitze, dieTeilnehmer);
  }

  @Override
  protected SainteLagueErgebnis verteileSitze(long anzahlSitzeGesamt, List< ? extends Teilnehmer> dieTeilnehmer, long anzahlStimmenGesamt) {
    Fraction divisor = Fraction.getFraction(anzahlStimmenGesamt, anzahlSitzeGesamt);
    List<SainteLagueTeilnehmerErgebnis> teilnehmerErgebnisse = new ArrayList<>();

    /*
     * 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 : dieTeilnehmer) {
      SainteLagueTeilnehmerErgebnis teilnehmerErgebnisNachAbrundung = new SainteLagueTeilnehmerErgebnis(teilnehmer, divisor);

      teilnehmerErgebnisse.add(teilnehmerErgebnisNachAbrundung);
    }

    return verteileRestsitzeAusHoechstzahlen(teilnehmerErgebnisse, anzahlSitzeGesamt);
  }

  private SainteLagueErgebnis verteileRestsitzeAusHoechstzahlen(List<SainteLagueTeilnehmerErgebnis> teilnehmerErgebnisse, long anzahlSitzeGesamt) {
    List<SainteLagueHoechstzahl> hoechstzahlenTeilnehmerErgebnisse = new ArrayList<>();

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

      for (int teilerIndex = 0; teilerIndex < anzahlSitzeGesamt; teilerIndex++) {
        SainteLagueHoechstzahl hoechstzahl = new SainteLagueHoechstzahl(teilnehmerErgebnis, teilerIndex);

        hoechstzahlen.add(hoechstzahl);
        hoechstzahlenTeilnehmerErgebnisse.add(hoechstzahl);
      }

      teilnehmerErgebnis.setHoechstzahlen(hoechstzahlen);
    }

    SainteLagueHoechstzahlSortierer hoechstzahlSortierer = new SainteLagueHoechstzahlSortierer(anzahlSitzeGesamt, hoechstzahlenTeilnehmerErgebnisse);

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

    for (SainteLagueTeilnehmerErgebnis teilnehmerErgebnis : teilnehmerErgebnisse) {
      teilnehmerErgebnis.vergibRestsitzeAusHoechstzahlen(hoechstzahlenMitSitzenEntschiedenPerTeilnehmerErgebnis.getOrDefault(teilnehmerErgebnis, emptyList()).size());
    }

    if (hoechstzahlSortierer.isEntschieden()) {
      return new SainteLagueErgebnis(anzahlSitzeGesamt, teilnehmerErgebnisse);
    }

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

    for (SainteLagueTeilnehmerErgebnis teilnehmerErgebnis : teilnehmerErgebnisseMitSitzenUnentschieden) {
      teilnehmerErgebnis.seiTeilnehmerAmLosverfahren();
    }

    return new SainteLagueErgebnis(anzahlSitzeGesamt, teilnehmerErgebnisse, teilnehmerErgebnisseMitSitzenUnentschieden, anzahlSitzeUnentschieden);
  }
}
