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

import static java.util.Collections.emptyList;
import static java.util.Collections.reverseOrder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;

import de.ivu.elect.sitzberechnung.math.Fraction;
import de.ivu.elect.sitzberechnung.verteilung.Teilnehmer;
import de.ivu.elect.sitzberechnung.verteilung.VerteilungContext;

public class SainteLagueHoechstzahlErgebnis extends AbstractSainteLagueErgebnis<SainteLagueHoechstzahlTeilnehmerErgebnis> {

  public SainteLagueHoechstzahlErgebnis(VerteilungContext context, List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse) {
    this(context, teilnehmerErgebnisse, 0L, emptyList());
  }

  public SainteLagueHoechstzahlErgebnis(VerteilungContext context, List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse, long anzahlSitzeZuVerlosen,
      List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisseImLosverfahren) {
    super(context, teilnehmerErgebnisse, anzahlSitzeZuVerlosen, teilnehmerErgebnisseImLosverfahren);
  }

  /**
   * Ein geeigneter Zuteilungsdivisor liegt bei Kenntnis der Höchstzahlen immer im Bereich zwischen der Höchstzahl für den letzten vergebenen Sitz (einschließlich) und der Höchstzahl, die den nächsten
   * Sitz bekommen hätte, (ausschließlich). Bei mehreren gleich großen Höchstzahlen für den letzten vergebenen Sitz entspricht ein geeigneter Zuteilungsdivisor genau deren Wert.
   */
  public SainteLagueDivisorInterval getDivisorInterval() {
    Set<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse = new HashSet<>();
    teilnehmerErgebnisse.addAll(getTeilnehmerErgebnisse());
    teilnehmerErgebnisse.addAll(getTeilnehmerAmLosverfahren());

    SainteLagueHoechstzahlSortierer hoechstzahlSortierer = getHoechstzahlSortierer();

    /*
     * Die SainteLagueHoechstzahl verwendet ganzzahlige Teiler aus der Folge +2 -> 1 – 3 – 5 – 7 – (2n−1), die hier auf deren nicht ganzzahlige Entsprechung aus der Folge +1 -> 0,5 – 1,5 – 2,5 – 3,5 –
     * (n−0,5) zurückgerechnet werden, um dem Muster +0,5 bzw. -0,5 bezüglich der Anzahl der Sitze bei der Suche nach einem geeigneten Zuteilungsdivisor zu entsprechen.
     */

    Function<SainteLagueHoechstzahl, Fraction> classifier = hoechstzahl -> hoechstzahl.toFractionMitTeilerAusFolgePlusEins(getFractionConverter());

    SortedMap<Fraction, List<SainteLagueHoechstzahl>> hoechstzahlenMitSitzPerFractionAsc = new TreeMap<>();
    hoechstzahlenMitSitzPerFractionAsc.putAll(hoechstzahlSortierer.groupHoechstzahlenMitSitzenEntschieden(classifier));
    hoechstzahlenMitSitzPerFractionAsc.putAll(hoechstzahlSortierer.groupHoechstzahlenMitSitzenUnentschieden(classifier));

    SortedMap<Fraction, List<SainteLagueHoechstzahl>> hoechstzahlenOhneSitzPerFractionDesc = new TreeMap<>(reverseOrder());
    hoechstzahlenOhneSitzPerFractionDesc.putAll(hoechstzahlSortierer.groupHoechstzahlenOhneSitz(classifier));

    Fraction min = Fraction.ZERO;
    Fraction max = Fraction.ZERO;

    Optional<Entry<Fraction, List<SainteLagueHoechstzahl>>> hoechstzahlenPerFractionMin = hoechstzahlenOhneSitzPerFractionDesc.entrySet().stream().findFirst();
    Optional<Entry<Fraction, List<SainteLagueHoechstzahl>>> hoechstzahlenPerFractionMax = hoechstzahlenMitSitzPerFractionAsc.entrySet().stream().findFirst();

    if (hoechstzahlenPerFractionMin.isPresent()) {
      Entry<Fraction, List<SainteLagueHoechstzahl>> entry = hoechstzahlenPerFractionMin.get();

      min = entry.getKey();
    }

    if (hoechstzahlenPerFractionMax.isPresent()) {
      Entry<Fraction, List<SainteLagueHoechstzahl>> entry = hoechstzahlenPerFractionMax.get();

      max = entry.getKey();

      if (entry.getValue().size() > 1) {
        min = max;
      }
    }

    if (min.compareTo(max) <= 0) {
      return new SainteLagueDivisorInterval(min, max);
    }

    return SainteLagueDivisorInterval.UNBESTIMMT;
  }

  public SortedMap<Long, Collection<SainteLagueHoechstzahl>> getHoechstzahlenPerRang() {
    SortedMap<Long, Collection<SainteLagueHoechstzahl>> result = new TreeMap<>();

    SainteLagueHoechstzahlSortierer hoechstzahlSortierer = getHoechstzahlSortierer();

    long rang = 1;

    for (List<SainteLagueHoechstzahl> hoechstzahlen : hoechstzahlSortierer.getHoechstzahlenMitSitzenEntschiedenPerFraction().values()) {
      rang = addHoechstzahlenPerRang(result, hoechstzahlen, rang, true, false);
    }

    for (List<SainteLagueHoechstzahl> hoechstzahlen : hoechstzahlSortierer.getHoechstzahlenMitSitzenUnentschiedenPerFraction().values()) {
      rang = addHoechstzahlenPerRang(result, hoechstzahlen, rang, true, true);
    }

    for (List<SainteLagueHoechstzahl> hoechstzahlen : hoechstzahlSortierer.getHoechstzahlenOhneSitzPerFraction().values()) {
      rang = addHoechstzahlenPerRang(result, hoechstzahlen, rang, false, false);
    }

    return result;
  }

  private long addHoechstzahlenPerRang(SortedMap<Long, Collection<SainteLagueHoechstzahl>> result, List<SainteLagueHoechstzahl> hoechstzahlen, long rang, boolean rangSitzrelevant,
      boolean rangImLosverfahren) {
    result.putIfAbsent(rang, new HashSet<>());

    for (SainteLagueHoechstzahl hoechstzahl : hoechstzahlen) {
      hoechstzahl.setRang(rang);
      hoechstzahl.setRangSitzrelevant(rangSitzrelevant);
      hoechstzahl.setRangImLosverfahren(rangImLosverfahren);

      result.get(rang).add(hoechstzahl);
    }

    return rang + hoechstzahlen.size();
  }

  private SainteLagueHoechstzahlSortierer getHoechstzahlSortierer() {
    Set<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse = new HashSet<>();
    teilnehmerErgebnisse.addAll(getTeilnehmerErgebnisse());
    teilnehmerErgebnisse.addAll(getTeilnehmerAmLosverfahren());

    return new SainteLagueHoechstzahlSortierer(getFractionConverter(), getAnzahlSitzeZuVerteilen(), teilnehmerErgebnisse);
  }

  /**
   * @return Ergebnis der Verteilung, wenn keine richtige Verteilung stattgefunden hat
   */
  public static SainteLagueHoechstzahlErgebnis ohneVerteilung(VerteilungContext context, List< ? extends Teilnehmer> teilnehmerInVerteilung) {
    List<SainteLagueHoechstzahlTeilnehmerErgebnis> teilnehmerErgebnisse = new ArrayList<>();

    for (Teilnehmer teilnehmer : teilnehmerInVerteilung) {
      teilnehmerErgebnisse.add(SainteLagueHoechstzahlTeilnehmerErgebnis.ohneVerteilung(teilnehmer));
    }

    return new SainteLagueHoechstzahlErgebnis(context, teilnehmerErgebnisse);
  }
}
