package de.ivu.elect.business.hochrechnung.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import de.ivu.elect.business.hochrechnung.entity.WertMitIndex;


import static de.ivu.elect.business.hochrechnung.control.StimmenHochrechnerHilfsfunktionen.notNull;

public class AnteileAufAnzahlenMapper {
  public List<Long> mappeAnteileAufAnzahlen (long mussSumme, List<Double> doubleWerte) {
    if (doubleWerte.size() == 0) {
      return Collections.emptyList();
    }

    if (!sindWerteNormiert(doubleWerte)) {
      doubleWerte = normiereWerte(doubleWerte);
    }
    List<Double> resultVorRundung = multiplyVector(mussSumme, doubleWerte);
    List<Long> nachRundung = rundeZahlen(resultVorRundung);

    return getKorrigierteVerteilung(mussSumme, resultVorRundung, nachRundung);
  }

  private List<Double> normiereWerte(List<Double> doubleWerte) {
    double summe = doubleWerte.stream().mapToDouble(StimmenHochrechnerHilfsfunktionen::notNull).sum();
    return doubleWerte.stream().map(x -> x == null? null : x / summe).collect(Collectors.toList());
  }

  private boolean sindWerteNormiert(List<Double> doubleWerte) {
    double summe = doubleWerte.stream().mapToDouble(StimmenHochrechnerHilfsfunktionen::notNull).sum();
    return Math.abs(summe - 1) < 0.000001;
  }

  private List<Long> getKorrigierteVerteilung(long mussSumme, List<Double> resultVorRundung, List<Long> nachRundung) {
    long verteiltMinusMussSumme = getUnterschiedZwischenVerteiltUndMussSumme(mussSumme, nachRundung);
    if (verteiltMinusMussSumme == 0) {
      return nachRundung;
    }

    List<WertMitIndex<Double>> erhoehungenInWertenWegenRundungSortiert = getErhoehungenInWertenWegenRundungSortiert(resultVorRundung, nachRundung);

    return korrigierteVerteilung(verteiltMinusMussSumme, nachRundung, erhoehungenInWertenWegenRundungSortiert);
  }

  private List<Long> korrigierteVerteilung(long verteiltMinusMussSumme, List<Long> nachRundung, List<WertMitIndex<Double>> erhoehungenInWertenWegenRundungSortiert) {
    if (verteiltMinusMussSumme > 0) {
      return korrigierteVerteilungWennZuVielVerteilt(Math.abs(verteiltMinusMussSumme), nachRundung, erhoehungenInWertenWegenRundungSortiert);
    }
    return korrigierteVerteilungWennZuWenigVerteilt(Math.abs(verteiltMinusMussSumme), nachRundung, erhoehungenInWertenWegenRundungSortiert);
  }

  private List<Long> korrigierteVerteilungWennZuWenigVerteilt(long anzahlZuWenigVerteilt, List<Long> nachRundung, List<WertMitIndex<Double>> erhoehungenInWertenWegenRundungSortiert) {
    int i = 0;
    while (anzahlZuWenigVerteilt > 0 && i < erhoehungenInWertenWegenRundungSortiert.size()) {
      int indexOfWertMitNaechstGroessteAbzugWegenRundung = erhoehungenInWertenWegenRundungSortiert.get(i).getIndex();
      i++;

      Long oldValue = nachRundung.get(indexOfWertMitNaechstGroessteAbzugWegenRundung);
      if (oldValue != null) {
        nachRundung.set(indexOfWertMitNaechstGroessteAbzugWegenRundung, oldValue + 1);
        anzahlZuWenigVerteilt--;
      }
    }
    return nachRundung;
  }

  private List<Long> korrigierteVerteilungWennZuVielVerteilt(long anzahlZuVielVerteilt, List<Long> nachRundung, List<WertMitIndex<Double>> erhoehungenInWertenWegenRundungSortiert) {
    int i = erhoehungenInWertenWegenRundungSortiert.size() - 1;
    while (anzahlZuVielVerteilt > 0 && i >= 0) {
      int indexOfWertMitNaechstGroessteAbzugWegenRundung = erhoehungenInWertenWegenRundungSortiert.get(i).getIndex();
      i--;

      Long oldValue = nachRundung.get(indexOfWertMitNaechstGroessteAbzugWegenRundung);
      if (oldValue != null) {
        nachRundung.set(indexOfWertMitNaechstGroessteAbzugWegenRundung, oldValue - 1);
        anzahlZuVielVerteilt--;
      }
    }
    return nachRundung;
  }

  private List<WertMitIndex<Double>> getErhoehungenInWertenWegenRundungSortiert(List<Double> resultVorRundung, List<Long> nachRundung) {
    List<WertMitIndex<Double>> result = new ArrayList<>();
    for (int i = 0; i < nachRundung.size();i++) {
      double differenz = getDifferenz (resultVorRundung.get(i), nachRundung.get(i));
      result.add(new WertMitIndex<>(differenz, i));
    }
    result.sort(Comparator.comparingDouble(WertMitIndex::getObject));
    return result;
  }

  private double getDifferenz(Double vor, Long nach) {
    return notNull(nach) - notNull(vor);
  }

  private long getUnterschiedZwischenVerteiltUndMussSumme(long mussSumme, List<Long> nachRundung) {
    long summeVerteilt = nachRundung.stream().mapToLong(StimmenHochrechnerHilfsfunktionen::notNull).sum();
    return summeVerteilt - mussSumme;
  }

  private List<Long> rundeZahlen(List<Double> resultVorRundung) {
    return resultVorRundung.stream().map(this::runde).collect(Collectors.toList());
  }

  private Long runde(Double wert) {
    return wert != null? Math.round(wert) : null;
  }

  private List<Double> multiplyVector(long faktor, List<Double> doubleWerte) {
    return doubleWerte.stream().map(wert -> multiply (faktor, wert)).collect(Collectors.toList());
  }

  private Double multiply(long faktor, Double wert) {
    return wert != null? faktor * wert : null;
  }

  private List<WertMitIndex<Double>> getDoubleWerteMitOriginalIndex(List<Double> doubleWerte) {
    List<WertMitIndex<Double>> result = new ArrayList<>();
    for (int i = 0;i < doubleWerte.size();i++) {
      result.add(new WertMitIndex<Double>(doubleWerte.get(i), i));
    }
    //result.sort(Comparator.comparingDouble(WertMitIndex::getObjectAsNumberNonNull).reversed());
    return result;
  }
}
