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

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

import de.ivu.elect.sitzberechnung.math.Fraction;
import de.ivu.elect.sitzberechnung.math.FractionConverter;
import de.ivu.elect.sitzberechnung.math.NachNummerSortierer;
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 D’Hondt (Divisorverfahren mit Abrundung) als Quasi-Quoten-Verfahren (Hagenbach-Bischoffsches Verfahren).
 *
 * <p>
 * Die Verteilung von Restsitzen per Los ist nicht enthalten. Es werden im Ergebnisobjekt lediglich die dafür erforderlichen Informationen geliefert.
 */
public class DHondtVerteilung extends AbstractVerteilung<DHondtTeilnehmerErgebnis, DHondtErgebnis> {

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

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

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

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

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

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

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

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

      anzahlSitzeNachAbrundungVerteilt += teilnehmerErgebnisNachAbrundung.getSitzeAusAbrundung();
    }

    return verteileRestsitze(context, teilnehmerErgebnisse, anzahlSitzeNachAbrundungVerteilt);
  }

  /**
   * Es werden die Stimmen der Parteien durch die um 1, 2, 3, … erhöhte schon zugeteilte Anzahl Sitze (nach Abrundung) dividiert und Restsitze nach größten Höchstzahlen zugeteilt.
   */
  private DHondtErgebnis verteileRestsitze(VerteilungContext context, List<DHondtTeilnehmerErgebnis> teilnehmerErgebnisse, long anzahlSitzeNachAbrundungVerteilt) {
    long anzahlRestsitzeVerbleibend = context.getAnzahlSitze() - anzahlSitzeNachAbrundungVerteilt;

    FractionConverter fractionConverter = context.getFractionConverter();

    while (anzahlRestsitzeVerbleibend > 0L) {
      NachNummerSortierer<DHondtTeilnehmerErgebnis, Fraction> quotientSortierer = new NachNummerSortierer<>();

      for (DHondtTeilnehmerErgebnis teilnehmerErgebnis : teilnehmerErgebnisse) {
        Fraction quotient = teilnehmerErgebnis.getNaechsterQuotient(fractionConverter);

        quotientSortierer.add(quotient, teilnehmerErgebnis);
      }

      List<DHondtTeilnehmerErgebnis> erstplatzierte = quotientSortierer.getErstplatzierte();

      if (erstplatzierte.size() > anzahlRestsitzeVerbleibend) {
        List<DHondtTeilnehmerErgebnis> teilnehmerErgebnisseImLosverfahren = erstplatzierte;
        long anzahlSitzeZuVerlosen = anzahlRestsitzeVerbleibend;

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

        return new DHondtErgebnis(context, teilnehmerErgebnisse, anzahlSitzeZuVerlosen, teilnehmerErgebnisseImLosverfahren);
      } else {
        for (DHondtTeilnehmerErgebnis teilnehmerErgebnis : erstplatzierte) {
          teilnehmerErgebnis.vergibRestsitz();

          anzahlRestsitzeVerbleibend--;
        }
      }
    }

    return new DHondtErgebnis(context, teilnehmerErgebnisse);
  }
}
