
package de.ivu.elect.sitzberechnung.verteilung;

import de.ivu.elect.sitzberechnung.math.Fraction;
import de.ivu.elect.sitzberechnung.math.FractionConverter;

/**
 * Ergebnis der Sitzverteilung für einen Teilnehmer, zunächst noch ohne das Losverfahren. Der Losentscheid ist die einzige später noch zulässige Modifikation des Objekts (Methode
 * {@link #entscheideLosverfahren(long)}).
 */
public class TeilnehmerErgebnis {

  protected final Teilnehmer teilnehmer;

  protected final Fraction divisor;

  protected final long sitzeAusAbrundung;

  protected long restsitzeOhneLosverfahren = 0;

  protected long restsitzeAusLosverfahren = 0;

  protected boolean isTeilnehmerAmLosverfahren = false;

  protected boolean isEntschieden = true;

  protected TeilnehmerErgebnis(Teilnehmer teilnehmer) {
    this(teilnehmer, Fraction.ZERO);
  }

  protected TeilnehmerErgebnis(Teilnehmer teilnehmer, Fraction divisor) {
    super();

    this.teilnehmer = teilnehmer;

    this.divisor = divisor;

    Fraction quoteExakt = FractionConverter.convertingExakt().asFractionConverted(teilnehmer.getStimmen(), divisor);
    this.sitzeAusAbrundung = quoteExakt.getProperWhole(); // Beim ganzzahligen Anteil ist die Genauigkeit der Nachkommstellen egal...
  }

  protected TeilnehmerErgebnis(Teilnehmer teilnehmer, long sitzeAusAbrundung) {
    super();

    this.teilnehmer = teilnehmer;

    this.divisor = Fraction.ZERO;

    this.sitzeAusAbrundung = sitzeAusAbrundung;
  }

  public Teilnehmer getTeilnehmer() {
    return teilnehmer;
  }

  /**
   * @return Anzahl Stimmen / Divisor (Fraction.ZERO, falls nicht relevant)
   */
  public Fraction getQuote(FractionConverter fractionConverter) {
    return fractionConverter.asFractionConverted(getStimmen(), divisor);
  }

  /**
   * @return Nicht-ganzzahliger Anteil der Quote (ganzzahliger Anteil der Quote == sitzeAusAbrundung)
   */
  public Fraction getRest(FractionConverter fractionConverter) {
    return getQuote(fractionConverter).subtract(getSitzeAusAbrundung());
  }

  /**
   * @return Anzahl Stimmen
   */
  public long getStimmen() {
    return teilnehmer.getStimmen();
  }

  /**
   * Summe der Sitze aus Abrundung, aus der Restsitzverteilung und dem (bisherigen) Losverfahren
   *
   * @return Anzahl der verteilten Sitze
   */
  public long getSitze() {
    return sitzeAusAbrundung + restsitzeOhneLosverfahren + restsitzeAusLosverfahren;
  }

  /**
   * @return Anzahl der verteilten Sitze ohne Vergabe nach Losentscheid
   */
  public long getSitzeOhneLosverfahren() {
    return sitzeAusAbrundung + restsitzeOhneLosverfahren;
  }

  /**
   * @return (Anzahl Stimmen / Divisor) abgerundet auf die nächste ganze Zahl
   */
  public long getSitzeAusAbrundung() {
    return sitzeAusAbrundung;
  }

  /**
   * Erhöht die Anzahl Restsitze ohne Losverfahren um 1.
   * <p/>
   * Darf nur von einem Sitzverteilungsverfahren aufgerufen werden.
   */
  protected void vergibRestsitz() {
    restsitzeOhneLosverfahren += 1;
  }

  public long getRestsitzeOhneLosverfahren() {
    return restsitzeOhneLosverfahren;
  }

  public long getRestsitzeAusLosverfahren() {
    return restsitzeAusLosverfahren;
  }

  /**
   * @return Anzahl der Restsitze einschließlich des (bisherigen) Losverfahrens
   */
  public long getRestsitze() {
    return restsitzeOhneLosverfahren + restsitzeAusLosverfahren;
  }

  /**
   * Darf nur von einem Sitzverteilungsverfahren aufgerufen werden.
   */
  protected void seiTeilnehmerAmLosverfahren() {
    this.isTeilnehmerAmLosverfahren = true;
    this.isEntschieden = false;
  }

  /**
   * @param restsitzeAusLosverfahren
   *          Anzahl Restsitze, die beim Losen zugewiesen wurden. Kann nur 0 oder 1 betragen
   */
  public void entscheideLosverfahren(long restsitzeAusLosverfahren) {
    assert restsitzeAusLosverfahren == 0 || restsitzeAusLosverfahren == 1;
    assert isEntschieden == false;

    this.restsitzeAusLosverfahren = restsitzeAusLosverfahren;
    this.isEntschieden = true;
  }

  public boolean isEntschieden() {
    return isEntschieden;
  }

  public boolean isTeilnehmerAmLosverfahren() {
    return isTeilnehmerAmLosverfahren;
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + "(" + getTeilnehmer() + "; Sitze=" + getSitze() + "; aus Abrundung=" + sitzeAusAbrundung + "; Restsitze ohne Los=" + restsitzeOhneLosverfahren
        + (isTeilnehmerAmLosverfahren ? "; per Los=" + restsitzeAusLosverfahren + (isEntschieden ? "!!!GELOST!!!" : "!!!UNENTSCHIEDEN!!!") : "") + ")";
  }
}
