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

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

import de.ivu.elect.sitzberechnung.math.Fraction;
import de.ivu.elect.sitzberechnung.math.FractionConverter;
import de.ivu.elect.sitzberechnung.math.VordereInRangfolgeErmittler;
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 Hare-Niemeyer (Quotenverfahren mit Restausgleich nach größten Bruchteilen).
 *
 * <p>
 * Die Verteilung von Restsitzen per Los ist nicht enthalten. Es werden im Ergebnisobjekt lediglich die dafür erforderlichen Informationen geliefert.
 */
public class HareNiemeyerVerteilung extends AbstractVerteilung<HareNiemeyerTeilnehmerErgebnis, HareNiemeyerErgebnis> {

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

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

  protected Fraction getDivisorVorgegeben(VerteilungContext context) {
    return context.getHareQuote();
  }

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

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

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

  protected HareNiemeyerErgebnis verteileSitze(VerteilungContext context, Fraction divisorVorgegeben, List< ? extends Teilnehmer> teilnehmerInVerteilung) {
    Fraction zuteilungsdivisor = convertZuteilungsdivisor(context, divisorVorgegeben);

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

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

      anzahlSitzeNachAbrundungVerteilt += teilnehmerErgebnisNachAbrundung.getSitzeAusAbrundung();
    }

    return verteileRestsitze(context, zuteilungsdivisor, teilnehmerErgebnisse, anzahlSitzeNachAbrundungVerteilt);
  }

  /**
   * Es werden die Restsitze in der Reihenfolge der größten Nachkommateile der Quoten den Parteien zugeteilt.
   */
  protected HareNiemeyerErgebnis verteileRestsitze(VerteilungContext context, Fraction zuteilungsdivisor, List<HareNiemeyerTeilnehmerErgebnis> teilnehmerErgebnisse,
      long anzahlSitzeNachAbrundungVerteilt) {
    long anzahlRestsitzeVerbleibend = context.getAnzahlSitze() - anzahlSitzeNachAbrundungVerteilt;

    if (anzahlRestsitzeVerbleibend < 0L) { // ELECT-11926, Super-Spezialfall!!! Bspw. in der AGH-Wahl bei Gewinn eines Direktmandates und gleichzeitig 0 Zweitstimmen...
      anzahlRestsitzeVerbleibend = 0L;
    }

    FractionConverter fractionConverter = context.getFractionConverter();

    Comparator<HareNiemeyerTeilnehmerErgebnis> comparatorByRestDesc = (one, another) -> another.getRest(fractionConverter).compareTo(one.getRest(fractionConverter));
    VordereInRangfolgeErmittler<HareNiemeyerTeilnehmerErgebnis> rangfolgeErmittler = new VordereInRangfolgeErmittler<>(teilnehmerErgebnisse, comparatorByRestDesc, anzahlRestsitzeVerbleibend);

    for (HareNiemeyerTeilnehmerErgebnis teilnehmerErgebnis : rangfolgeErmittler.getVordere()) {
      teilnehmerErgebnis.vergibRestsitz();
    }

    if (rangfolgeErmittler.isEntschieden()) {
      return new HareNiemeyerErgebnis(context, zuteilungsdivisor, teilnehmerErgebnisse);
    }

    List<HareNiemeyerTeilnehmerErgebnis> teilnehmerErgebnisseImLosverfahren = rangfolgeErmittler.getUnentschiedene();
    int anzahlSitzeZuVerlosen = rangfolgeErmittler.getAnzahlUnverteilterSitze();

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

    return new HareNiemeyerErgebnis(context, zuteilungsdivisor, teilnehmerErgebnisse, anzahlSitzeZuVerlosen, teilnehmerErgebnisseImLosverfahren);
  }

  private Fraction convertZuteilungsdivisor(VerteilungContext context, Fraction divisorVorgegeben) {
    return context.getFractionConverter().asFractionConverted(divisorVorgegeben);
  }
}
