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

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;

import de.ivu.elect.business.sitzberechnung.control.util.Fraction;
import de.ivu.elect.business.sitzberechnung.control.util.NachNummerSortierer;
import de.ivu.elect.business.sitzberechnung.control.verteilung.AbstractVerteilungsErgebnis;
import de.ivu.elect.business.sitzberechnung.control.verteilung.Teilnehmer;

/**
 * Das Ergebnis einer Sitzverteilung nach D'Hondt. Sie wird als Quasi-Quotenverfahren
 * (http://www.wahlrecht.de/verfahren/quotenverfahren.html) umgesetzt. D.h. zunächst wird die Stimmenzahl durch den
 * Hare-Quotienten dividiert und auf die nächste ganze Zahl abgerundet. Anschließend werden Restsitze vergeben. Man
 * erhält es nach Verteilung der abgerundeten Sitze und der Restsitze von einer {@link DHondtVerteilung}. Durch eine
 * evtl. spätere Verteilung nach Los (oder anderen Kriterien) werden nachträglich noch Sitze vergeben und damit dieses
 * Objekt modifiziert. Es enthält für jeden Teilnehmer an der Verteilung ein {@link DHondtTeilnehmerErgebnis}, das
 * ebenfalls noch durch spätere Verteilung nach Los (oder anderen Kriterien) modifiziert werden kann.
 * 
 *
 */
public class DHondtErgebnis extends AbstractVerteilungsErgebnis<DHondtTeilnehmerErgebnis> {

  /**
   * @return Ergebnis der Verteilung, wenn keine richtige Verteilung stattgefunden hat
   */
  public static DHondtErgebnis ohneVerteilung(long anzahlSitze, List< ? extends Teilnehmer> dieTeilnehmer) {
    List<DHondtTeilnehmerErgebnis> teilnehmerErgebnisse = new ArrayList<>();
    for (Teilnehmer teilnehmer : dieTeilnehmer) {
      teilnehmerErgebnisse.add(DHondtTeilnehmerErgebnis.ohneVerteilung(teilnehmer));
    }

    return new DHondtErgebnis(anzahlSitze, teilnehmerErgebnisse);
  }

  /**
   * Ergebnis der DHondtVerteilung ohne dass ein Losverfahren nötig war
   */
  public DHondtErgebnis(long anzahlSitze, List<DHondtTeilnehmerErgebnis> teilnehmerErgebnisse) {
    this(anzahlSitze, teilnehmerErgebnisse, Collections.<DHondtTeilnehmerErgebnis> emptyList(), 0);
  }

  public DHondtErgebnis(long anzahlSitze, List<DHondtTeilnehmerErgebnis> teilnehmerErgebnisse,
      List<DHondtTeilnehmerErgebnis> teilnehmerAmLosverfahren, long anzahlSitzeZuVerlosen) {
    super(anzahlSitze, teilnehmerErgebnisse, teilnehmerAmLosverfahren, anzahlSitzeZuVerlosen);
  }

  /**
   * @return den Quotienten, mit dem der nächste Sitz zugeteilt worden wäre
   */
  public Fraction getNaechsterQuotient() {
    Fraction max = Fraction.ZERO;
    for (DHondtTeilnehmerErgebnis teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      if (max.compareTo(teilnehmerErgebnis.getNaechsterQuotient()) < 0) {
        max = teilnehmerErgebnis.getNaechsterQuotient();
      }
    }

    return max;
  }

  /**
   * @return den Quotienten, mit dem der letzte Sitz zugeteilt wurde. null, wenn kein Sitz zugeteilt wurde.
   */
  public Fraction getLetzterQuotient() {
    Fraction min = null;
    for (DHondtTeilnehmerErgebnis teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      if (teilnehmerErgebnis.getSitze() > 0) {
        Fraction quotient = Fraction.getFraction(teilnehmerErgebnis.getTeilnehmer().getStimmen(),
            teilnehmerErgebnis.getSitze());
        if (min == null || min.compareTo(quotient) > 0) {
          min = quotient;
        }
      }
    }

    return min;
  }

  /**
   * Die Anzahl der {@link DHondtQuotient}en pro Teilnehmer ist um 1 größer, als die maximale Anzahl Sitze eine
   * Teilnehmers.
   * 
   * @param nurBeiVerteilungQuotientenErzeugen
   *          wenn dies true ist, ist das Ergebnis leer in dem Fall, dass keine richtige Verteilung stattgefunden hat
   *          (wegen 0 Sitze oder 0 Stimmen für alle)
   * @return eine Map mit einer Liste von {@link DHondtQuotient}en aller Teilnehmer an der Verteilung.
   */
  public Map<Teilnehmer, List<DHondtQuotient>> getDHondtQuotientenProTeilnehmer(
      boolean nurBeiVerteilungQuotientenErzeugen) {
    Map<Teilnehmer, List<DHondtQuotient>> map = new LinkedHashMap<>();
    createDHondtQuotienten(map, nurBeiVerteilungQuotientenErzeugen);
    return map;
  }

  /**
   * @param nurBeiVerteilungQuotientenErzeugen
   *          wenn dies true ist, ist das Ergebnis leer in dem Fall, dass keine richtige Verteilung stattgefunden hat
   *          (wegen 0 Sitze oder 0 Stimmen für alle)
   * @return Die {@link DHondtQuotient}en aller Teilnehmer gruppiert und absteigend sortiert nach Rang
   */
  public List<List<DHondtQuotient>> getDHondtQuotientenNachRang(boolean nurBeiVerteilungQuotientenErzeugen) {
    Map<Teilnehmer, List<DHondtQuotient>> map = new LinkedHashMap<>();
    SortedMap<Fraction, List<DHondtQuotient>> result = createDHondtQuotienten(map, nurBeiVerteilungQuotientenErzeugen);
    return new ArrayList<>(result.values());
  }

  /**
   * @param nurBeiVerteilungQuotientenErzeugen
   *          wenn dies true ist, ist das Ergebnis leer in dem Fall, dass keine richtige Verteilung stattgefunden hat
   *          (wegen 0 Sitze oder 0 Stimmen für alle)
   * @return Die {@link DHondtQuotient}en aller Teilnehmer
   */
  public List<DHondtQuotient> getDHondtQuotienten(boolean nurBeiVerteilungQuotientenErzeugen) {
    List<DHondtQuotient> result = new ArrayList<>();
    for (List<DHondtQuotient> dHondtQuotienten : getDHondtQuotientenProTeilnehmer(nurBeiVerteilungQuotientenErzeugen)
        .values()) {
      result.addAll(dHondtQuotienten);
    }
    return new ArrayList<>(result);
  }

  /**
   * Wenn <code>isOhneVerteilung() == true && nurBeiVerteilungQuotientenErzeugen == true</code>, dann werden keine
   * Quotienten erzeugt. ansonsten werden für jeden Teilnehmer ein Quotient mehr erzeugt als die maximale Sitzanzahl der
   * Teilnehmer.
   */
  private SortedMap<Fraction, List<DHondtQuotient>> createDHondtQuotienten(Map<Teilnehmer, List<DHondtQuotient>> map,
      boolean nurBeiVerteilungQuotientenErzeugen) {
    NachNummerSortierer<DHondtQuotient, Fraction> alleDHondtQuotientenSortiert = new NachNummerSortierer<>();

    long anzahlDHondtQuotienten = getMaximaleAnzahlSitzeEinesTeilnehmers() + 1;
    if (isOhneVerteilung() && nurBeiVerteilungQuotientenErzeugen) {
      anzahlDHondtQuotienten = 0; // Keine Quotienten erzeugen !!!
    }
    for (DHondtTeilnehmerErgebnis teilnehmerErgebnis : getTeilnehmerErgebnisse()) {
      List<DHondtQuotient> dHondtQuotients = teilnehmerErgebnis.createDHondtQuotienten(anzahlDHondtQuotienten);
      map.put(teilnehmerErgebnis.getTeilnehmer(), dHondtQuotients);
      for (DHondtQuotient dHondtQuotient : dHondtQuotients) {
        alleDHondtQuotientenSortiert.add(dHondtQuotient.getFraction(), dHondtQuotient);
      }
    }

    SortedMap<Fraction, List<DHondtQuotient>> rangGleiche = alleDHondtQuotientenSortiert.getHintereGruppiert();
    long rang = 1;
    for (List<DHondtQuotient> dHondtQuotienten : rangGleiche.values()) {
      for (DHondtQuotient dHondtQuotient : dHondtQuotienten) {
        dHondtQuotient.setRang(rang);
      }
      rang += dHondtQuotienten.size();
    }

    return rangGleiche;
  }

  /**
   * @return <code>true</code>, wenn kein Teilnehmer eine Sitz erhalten hat und keine Losentscheid mehr offen ist. Das
   *         ist nur der Fall, wenn keine Verteilung stattgefunden hat, entweder weil keine Sitze zu verteilen waren
   *         oder weil kein Teilnehmer eine Stimme erhalten hat.
   */
  public boolean isOhneVerteilung() {
    return getMaximaleAnzahlSitzeEinesTeilnehmers() == 0 && isEntschieden();
  }

}
