
package de.ivu.elect.sitzberechnung.verteilung;

import java.util.List;
import java.util.Objects;

import javax.annotation.Nullable;

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

public class AbstractVerteilungErgebnis<T extends TeilnehmerErgebnis> {

  private final FractionConverter fractionConverter;

  private final long anzahlSitze;

  private final List<T> teilnehmerErgebnisse;

  private final long anzahlSitzeZuVerlosen;

  private final List<T> teilnehmerErgebnisseImLosverfahren;

  public AbstractVerteilungErgebnis(VerteilungContext context, List<T> teilnehmerErgebnisse, long anzahlSitzeZuVerlosen, List<T> teilnehmerErgebnisseImLosverfahren) {
    super();

    this.fractionConverter = context.getFractionConverter();

    this.anzahlSitze = context.getAnzahlSitze();
    this.teilnehmerErgebnisse = teilnehmerErgebnisse;

    this.anzahlSitzeZuVerlosen = anzahlSitzeZuVerlosen;
    this.teilnehmerErgebnisseImLosverfahren = teilnehmerErgebnisseImLosverfahren;
  }

  /**
   * @return FractionConverter, der während der Verteilung verwendet wurde
   */
  public FractionConverter getFractionConverter() {
    return fractionConverter;
  }

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

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

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

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

  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 : teilnehmerErgebnisseImLosverfahren) {
      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(TeilnehmerErgebnis::getSitze).max().orElse(0L);
  }

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

  @Nullable
  public T getTeilnehmerErgebnis(Teilnehmer teilnehmer) {
    for (T teilnehmerErgebnis : teilnehmerErgebnisse) {
      if (Objects.equals(teilnehmerErgebnis.getTeilnehmer(), teilnehmer)) {
        return teilnehmerErgebnis;
      }
    }

    return null;
  }

  public long getAnzahlSitze(Teilnehmer teilnehmer) {
    T teilnehmerErgebnis = getTeilnehmerErgebnis(teilnehmer);

    if (teilnehmerErgebnis != null) {
      return teilnehmerErgebnis.getSitze();
    }

    return 0L;
  }

  public boolean isTeilnehmerAmLosverfahren(Teilnehmer teilnehmer) {
    T teilnehmerErgebnis = getTeilnehmerErgebnis(teilnehmer);

    return teilnehmerErgebnis != null && teilnehmerErgebnis.isTeilnehmerAmLosverfahren();
  }

  /**
   * @return <code>true</code>, wenn alle Ergebnisse entschieden sind, andernfalls <code>false</code>
   */
  public boolean isEntschieden() {
    for (T teilnehmerErgebnis : teilnehmerErgebnisseImLosverfahren) {
      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>
   */
  @Nullable
  public Teilnehmer pruefeAnwendungDerMehrheitssicherungsklausel() {
    long anzahlStimmenAbsoluteMehrheit = getAnzahlStimmenAbsoluteMehrheit();
    long anzahlSitzeAbsoluteMehrheit = getAnzahlSitzeAbsoluteMehrheit();

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

    return null;
  }

  public long getAnzahlSitzeZusaetzlichDurchMehrheitssicherung() {
    return getAnzahlSitzeZusaetzlichDurchMehrheitssicherung(pruefeAnwendungDerMehrheitssicherungsklausel());
  }

  public long getAnzahlSitzeZusaetzlichDurchMehrheitssicherung(Teilnehmer teilnehmer) {
    if (teilnehmer != null) {
      for (T teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
        if (teilnehmerErgebnis != null && Objects.equals(teilnehmerErgebnis.getTeilnehmer(), teilnehmer)) {
          long anzahlSitzeVerteiltIncremented = getTeilnehmerErgebnisse().stream().mapToLong(TeilnehmerErgebnis::getSitze).sum();

          if (anzahlSitzeVerteiltIncremented > 0L) {
            long anzahlSitzeTeilnehmerInitial = teilnehmerErgebnis.getSitze();
            long anzahlSitzeTeilnehmerIncremented = anzahlSitzeTeilnehmerInitial;

            long anzahlSitzeAbsoluteMehrheitIncremented = getAnzahlSitzeAbsoluteMehrheit(anzahlSitzeVerteiltIncremented);

            while (anzahlSitzeTeilnehmerIncremented < anzahlSitzeAbsoluteMehrheitIncremented) {
              anzahlSitzeVerteiltIncremented++;
              anzahlSitzeTeilnehmerIncremented++;

              anzahlSitzeAbsoluteMehrheitIncremented = getAnzahlSitzeAbsoluteMehrheit(anzahlSitzeVerteiltIncremented); // ELECT-16459
            }

            return Math.max(0L, anzahlSitzeTeilnehmerIncremented - anzahlSitzeTeilnehmerInitial);
          }
        }
      }
    }

    return 0L;
  }

  public long getAnzahlStimmenAbsoluteMehrheit() {
    long anzahlStimmenGesamt = 0L;

    for (T teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      anzahlStimmenGesamt += teilnehmerErgebnis.getStimmen();
    }

    return anzahlStimmenGesamt / 2L + 1L;
  }

  public long getAnzahlSitzeAbsoluteMehrheit() {
    long anzahlSitzeGesamt = 0L;

    for (T teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      anzahlSitzeGesamt += teilnehmerErgebnis.getSitze();
    }

    return getAnzahlSitzeAbsoluteMehrheit(anzahlSitzeGesamt);
  }

  private long getAnzahlSitzeAbsoluteMehrheit(long anzahlSitzeGesamt) {
    return anzahlSitzeGesamt / 2L + 1L;
  }

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