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

import java.util.List;

import de.ivu.elect.business.sitzberechnung.control.util.Fraction;

public class AbstractVerteilungsErgebnis<T extends TeilnehmerErgebnis> {

  private final long anzahlSitzeZuVerteilen;
  private final List<T> teilnehmerErgebnisse;
  private final List<T> teilnehmerAmLosverfahren;
  private final long anzahlSitzeZuVerlosen;

  public AbstractVerteilungsErgebnis(long anzahlSitzeZuVerteilen, List<T> teilnehmerErgebnisse,
      List<T> teilnehmerAmLosverfahren, long anzahlSitzeZuVerlosen) {
    this.anzahlSitzeZuVerteilen = anzahlSitzeZuVerteilen;
    this.teilnehmerErgebnisse = teilnehmerErgebnisse;
    this.anzahlSitzeZuVerlosen = anzahlSitzeZuVerlosen;
    this.teilnehmerAmLosverfahren = teilnehmerAmLosverfahren;
  }

  /**
   * @return die Anzahl Sitze, die ursprünglich zu verteilen waren
   */
  public long getAnzahlSitzeZuVerteilen() {
    return anzahlSitzeZuVerteilen;
  }

  public Fraction getHareQuotient() {
    return Fraction.getFraction(getAnzahlStimmen(), Math.max(1, getAnzahlSitzeZuVerteilen()));
  }

  public List<T> getTeilnehmerErgebnisse() {
    return teilnehmerErgebnisse;
  }

  public T getTeilnehmerErgebnis(int index) {
    return teilnehmerErgebnisse.get(index);
  }

  public List<T> getTeilnehmerAmLosverfahren() {
    return teilnehmerAmLosverfahren;
  }

  public Teilnehmer getTeilnehmer(int index) {
    return getTeilnehmerErgebnis(index).getTeilnehmer();
  }

  /**
   * @return Anzahl der Sitze, die nach der Restsitzverteilung übrig sind / waren und ggf. per Los vergeben werden
   *         müssen / mussten
   */
  public long getAnzahlSitzeZuVerlosen() {
    return anzahlSitzeZuVerlosen;
  }

  /**
   * @return Anzahl der Sitze, die nach der Restsitzverteilung und der bisherigen Verteilung per Los noch nicht vergeben
   *         wurden
   */
  public long getAnzahlNichtVerteilterSitze() {
    long result = anzahlSitzeZuVerlosen;
    for (T teilnehmerErgebnis : teilnehmerAmLosverfahren) {
      result -= teilnehmerErgebnis.getRestsitzeAusLosverfahren();
    }
    return result;
  }

  /**
   * @return die höchste Sitzezahl, die ein Teilnehmer erreicht hat (gemäß {@link TeilnehmerErgebnis#getSitze()}
   */
  public long getMaximaleAnzahlSitzeEinesTeilnehmers() {
    return teilnehmerErgebnisse.stream().mapToLong(e -> e.getSitze()).max().orElse(0);
  }

  /**
   * @return die Summe der Stimmen aller Teilnehmer
   */
  public long getAnzahlStimmen() {
    return teilnehmerErgebnisse.stream().mapToLong(e -> e.getTeilnehmer().getStimmen()).sum();
  }

  public T getTeilnehmerErgebnis(Teilnehmer teilnehmer) {
    for (T ergebnis : teilnehmerErgebnisse) {
      if (ergebnis.getTeilnehmer().equals(teilnehmer)) {
        return ergebnis;
      }
    }
    return null;
  }

  public long getAnzahlSitze(Teilnehmer teilnehmer) {
    return getTeilnehmerErgebnis(teilnehmer).getSitze();
  }

  public boolean isTeilnehmerAmLosverfahren(Teilnehmer teilnehmer) {
    return getTeilnehmerErgebnis(teilnehmer).isTeilnehmerAmLosverfahren();
  }

  /**
   * @return <code>true</code>, wenn alle Ergebnisse entschieden sind, andernfalls <code>false</code>
   */
  public boolean isEntschieden() {
    for (T teilnehmerErgebnis : teilnehmerAmLosverfahren) {
      if (!teilnehmerErgebnis.isEntschieden()) {
        return false;
      }
    }
    return true;
  }

  /**
   * Prüft, ob die Mehrheitssicherungsklausel Anwendung findet. Das ist der Fall, wenn ein Teilnehmer mit der absoluten
   * Mehrheit der Stimmen in der ersten Verteilung nicht die absolute Mehrheit der Sitze erhalten hat.
   * <p/>
   * Hinweis: Für die Prüfung wird die Methode getSitze() verwendet. Sitze aus dem Losverfahren werden also nur
   * berücksichtigt, falls dieses abgeschlossen ist.
   * 
   * @return der Teilnehmer mit der absoluten Mehrheit der Stimmen aber ohne die absolute Mehrheit der Sitze, falls es
   *         einen solchen Teilnehmer gibt. Andernfalls <code>null</code>
   */
  public Teilnehmer pruefeAnwendungDerMehrheitssicherungsklausel() {
    long stimmenGesamt = 0;
    long sitzeGesamt = 0;
    for (T teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      stimmenGesamt += teilnehmerErgebnis.getTeilnehmer().getStimmen();
      sitzeGesamt += teilnehmerErgebnis.getSitze();
    }
    long absoluteMehrheitDerStimmen = stimmenGesamt / 2 + 1;
    long absoluteMehrheitDerSitze = sitzeGesamt / 2 + 1;

    for (T teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      if (teilnehmerErgebnis.getTeilnehmer().getStimmen() >= absoluteMehrheitDerStimmen) {
        // Dieser Teilnehmer hat die Stimmenmahrheit
        if (teilnehmerErgebnis.getSitze() < absoluteMehrheitDerSitze) {
          // .. aber keine absolute Mehrheit der Sitze
          return teilnehmerErgebnis.getTeilnehmer();
        } else {
          // Es kann nur einen geben, deshalb nicht weiter suchen
          return null;
        }
      }
    }
    return null;
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + "(" + getTeilnehmerErgebnisse() + ")";
  }

}
