
package de.ivu.elect.sitzberechnung.verteilung.saintelague;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.function.Function;

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

public class SainteLagueHoechstzahlSortierer {

  private final NachNummerSortierer<SainteLagueHoechstzahl, Fraction> sortierer;

  private final List<SainteLagueHoechstzahl> vordere;

  private final SortedMap<Fraction, List<SainteLagueHoechstzahl>> vordereGruppiert;

  private final List<SainteLagueHoechstzahl> unentschiedene;

  private final SortedMap<Fraction, List<SainteLagueHoechstzahl>> unentschiedeneGruppiert;

  private final List<SainteLagueHoechstzahl> hintere;

  private final SortedMap<Fraction, List<SainteLagueHoechstzahl>> hintereGruppiert;

  public SainteLagueHoechstzahlSortierer(FractionConverter fractionConverter, long anzahlSitze, Collection<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse) {
    super();

    this.sortierer = new NachNummerSortierer<>(anzahlSitze);

    new HashSet<>(teilnehmerErgebnisse).stream() //
        .map(SainteLagueHoechstzahlTeilnehmerErgebnis::getHoechstzahlen) //
        .flatMap(List::stream) //
        .forEach(hoechstzahl -> this.sortierer.add(hoechstzahl.toFraction(fractionConverter), hoechstzahl));

    this.vordere = sortierer.getVordere();
    this.vordereGruppiert = sortierer.getVordereGruppiert();

    this.unentschiedene = sortierer.getUnentschiedene();
    this.unentschiedeneGruppiert = sortierer.getUnentschiedeneGruppiert();

    this.hintere = sortierer.getHintere();
    this.hintereGruppiert = sortierer.getHintereGruppiert();
  }

  public boolean isEntschieden() {
    return sortierer.isEntschieden();
  }

  public long getAnzahlSitzeGesamt() {
    return sortierer.getAnzahlSitze();
  }

  public long getAnzahlSitzeEntschieden() {
    return getHoechstzahlenMitSitzenEntschiedenPerTeilnehmerErgebnis().values().stream().mapToLong(List::size).sum();
  }

  public Map<SainteLagueHoechstzahlTeilnehmerErgebnis, List<SainteLagueHoechstzahl>> getHoechstzahlenMitSitzenEntschiedenPerTeilnehmerErgebnis() {
    return groupHoechstzahlenMitSitzenEntschieden(SainteLagueHoechstzahl::getTeilnehmerErgebnis);
  }

  public SortedMap<Fraction, List<SainteLagueHoechstzahl>> getHoechstzahlenMitSitzenEntschiedenPerFraction() {
    return vordereGruppiert;
  }

  public <T> Map<T, List<SainteLagueHoechstzahl>> groupHoechstzahlenMitSitzenEntschieden(Function<SainteLagueHoechstzahl, T> classifier) {
    return groupHoechstzahlen(getHoechstzahlenMitSitzenEntschieden(), classifier);
  }

  private List<SainteLagueHoechstzahl> getHoechstzahlenMitSitzenEntschieden() {
    return vordere;
  }

  public long getAnzahlSitzeUnentschieden() {
    return Math.max(0, getAnzahlSitzeGesamt() - getAnzahlSitzeEntschieden());
  }

  public Map<SainteLagueHoechstzahlTeilnehmerErgebnis, List<SainteLagueHoechstzahl>> getHoechstzahlenMitSitzenUnentschiedenPerTeilnehmerErgebnis() {
    return groupHoechstzahlenMitSitzenUnentschieden(SainteLagueHoechstzahl::getTeilnehmerErgebnis);
  }

  public SortedMap<Fraction, List<SainteLagueHoechstzahl>> getHoechstzahlenMitSitzenUnentschiedenPerFraction() {
    return unentschiedeneGruppiert;
  }

  public <T> Map<T, List<SainteLagueHoechstzahl>> groupHoechstzahlenMitSitzenUnentschieden(Function<SainteLagueHoechstzahl, T> classifier) {
    return groupHoechstzahlen(getHoechstzahlenMitSitzenUnentschieden(), classifier);
  }

  private List<SainteLagueHoechstzahl> getHoechstzahlenMitSitzenUnentschieden() {
    return unentschiedene;
  }

  public Map<SainteLagueHoechstzahlTeilnehmerErgebnis, List<SainteLagueHoechstzahl>> getHoechstzahlenOhneSitzPerTeilnehmerErgebnis() {
    return groupHoechstzahlenOhneSitz(SainteLagueHoechstzahl::getTeilnehmerErgebnis);
  }

  public <T> Map<T, List<SainteLagueHoechstzahl>> groupHoechstzahlenOhneSitz(Function<SainteLagueHoechstzahl, T> classifier) {
    return groupHoechstzahlen(getHoechstzahlenOhneSitz(), classifier);
  }

  public SortedMap<Fraction, List<SainteLagueHoechstzahl>> getHoechstzahlenOhneSitzPerFraction() {
    return hintereGruppiert;
  }

  private List<SainteLagueHoechstzahl> getHoechstzahlenOhneSitz() {
    return hintere;
  }

  private <T> Map<T, List<SainteLagueHoechstzahl>> groupHoechstzahlen(List<SainteLagueHoechstzahl> hoechstzahlen, Function<SainteLagueHoechstzahl, T> classifier) {
    return hoechstzahlen.stream().collect(groupingBy(classifier, LinkedHashMap::new, toList())); // LinkedHashMap -> Sortierung beibehalten...
  }
}
