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

import static de.ivu.elect.business.sitzberechnung.control.util.GewaehlteNachStimmenzahlErmittlerUtil.toListOfSingletonLists;

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

import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebniskonflikt.control.util.SitzverteilungAbbruchBeiErgebnisKonfliktException;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;

/**
 * Hilfsklasse, die bei Kandidaturen, die schon nach Stimmenzahl sortiert sind, die vorderen als Gewählte ausgibt, die
 * hinteren als Nachrücker.
 * <ul>
 * <li>
 * Bei Stimmengleichheit zwischen Gewählten erhalten beide den gleichen Rang.</li>
 * <li>Bei Stimmengleichheit um den letzten Sitz / die letzten Sitze wird eine Entscheidung aus einem Callback einholt
 * und ggf. weitere Gewählte ausgibt.</li>
 * <li>Bei Stimmengleichheit der Nachrücker wird ebenfalls eine Entscheidung aus einem Callback um die Reihenfolge
 * einholt.</li>
 * </ul>
 */
public class GewaehlteNachStimmenzahlErmittler<K extends Kandidatur> {

  private boolean rangfolgeFuerNichtGewaehlteFortsetzen = true;
  private long rangOffset = 0;

  private final NachNummerSortierer<K, Long> nachStimmenSortierer;
  private final long anzahlSitze;
  private final GewaehlteNachStimmenzahlErmittlerCallback<K> callback;

  public GewaehlteNachStimmenzahlErmittler(NachNummerSortierer<K, Long> nachStimmenSortierer,
      GewaehlteNachStimmenzahlErmittlerCallback<K> callback) {
    this.nachStimmenSortierer = nachStimmenSortierer;
    this.anzahlSitze = nachStimmenSortierer.getAnzahlSitze();
    this.callback = callback;
  }

  /**
   * Default ist <code>true</code>
   * 
   * @return <code>true</code>, wenn die nicht Gewählten die Rangfolge der Gewählten fortsetzen sollen.
   *         <code>false</code>, wenn die Rangfolge der nicht Gewählten wieder bei 1 beginnen soll.
   */
  public boolean isRangfolgeFuerNichtGewaehlteFortsetzen() {
    return rangfolgeFuerNichtGewaehlteFortsetzen;
  }

  public void setRangfolgeFuerNichtGewaehlteFortsetzen(boolean rangfolgeFuerNichtGewaehlteFortsetzen) {
    this.rangfolgeFuerNichtGewaehlteFortsetzen = rangfolgeFuerNichtGewaehlteFortsetzen;
  }

  public void setRangOffset(long rangOffset) {
    this.rangOffset = rangOffset;
  }

  public void ermittleGewaehlte() {
    SortedMap<Long, List<K>> vordere = nachStimmenSortierer.getVordereGruppiert();
    List<K> unentschiedene = nachStimmenSortierer.getUnentschiedene();
    SortedMap<Long, List<K>> hintere = nachStimmenSortierer.getHintereGruppiert();

    long anzahlVordere = erzeugeGewaehlteFuerVordere(vordere);
    long rang = 1 + anzahlVordere;
    rang = erzeugeGewaehlteFuerTeilnehmerAnLosentscheid(unentschiedene, rang, anzahlSitze - anzahlVordere);
    erzeugeGewaehlteFuerHintere(hintere, rang);
  }

  /**
   * @param vordere
   *          die Gewählten gruppiert und sortiert nach Stimmenzahl. Stimmengleiche erhalten den gleichen Rang.
   * @return die Anzahl von Gewaehlten, die erzeugt wurden
   */
  private long erzeugeGewaehlteFuerVordere(SortedMap<Long, List<K>> vordere) {
    long count = 0;
    for (List<K> kandidaturen : vordere.values()) {
      for (K kandidatur : kandidaturen) {
        callback.createGewaehlt(kandidatur, true, 1 + rangOffset + count);
      }
      count += kandidaturen.size();
    }
    return count;
  }

  /**
   * @param anzahlZuLosen
   * @return die Verlierer des Losverfahrens
   * @throws SitzverteilungAbbruchBeiErgebnisKonfliktException
   *           bei ungelöstem Konflikt
   */
  private long erzeugeGewaehlteFuerTeilnehmerAnLosentscheid(List<K> unentschiedene, long rang, long anzahlZuLosen) {
    if (unentschiedene.isEmpty()) {
      if (rangfolgeFuerNichtGewaehlteFortsetzen) {
        return rang;
      } else {
        return 1;
      }
    }

    List<K> gelosteKandidaturen = callback.bestimmeLetzteGewaehlteBeiStimmengleichheit(unentschiedene, anzahlZuLosen);
    for (K kandidatur : gelosteKandidaturen) {
      callback.createGewaehlt(kandidatur, true, rang + rangOffset);
    }

    List<K> nichtGelosteKandidaturen = new ArrayList<>(unentschiedene);
    nichtGelosteKandidaturen.removeAll(gelosteKandidaturen);
    return erzeugeNichtGewaehlte(nichtGelosteKandidaturen, rang + gelosteKandidaturen.size());
  }

  private void erzeugeGewaehlteFuerHintere(SortedMap<Long, List<K>> hintere, long derRang) {
    long rang = derRang;
    for (List<K> kandidaturen : hintere.values()) {
      rang = erzeugeNichtGewaehlte(kandidaturen, rang);
    }
  }

  /**
   * Erzeuge {@link Gewaehlt}e für die Nachrückerliste.
   * 
   * @param kandidaturen
   *          eine Menge Kandidaturen mit gleicher Stimmenzahl, die keinen Sitz erhalten
   * @throws SitzverteilungAbbruchBeiErgebnisKonfliktException
   *           bei ungelöstem Konflikt
   */
  private long erzeugeNichtGewaehlte(List<K> kandidaturen, long derRang) {
    long rang = derRang;
    List<List<K>> kandidaturenListenInReihenfolge;
    if (kandidaturen.size() <= 1) {
      kandidaturenListenInReihenfolge = toListOfSingletonLists(kandidaturen);
    } else {
      kandidaturenListenInReihenfolge = callback.bestimmeNachrueckerReihenfolge(kandidaturen);
    }

    for (List<K> kandidaturenListe : kandidaturenListenInReihenfolge) {
      for (K kandidatur : kandidaturenListe) {
        callback.createGewaehlt(kandidatur, false, rang + rangOffset);
      }
      rang += kandidaturenListe.size();
    }

    return rang;
  }

}
