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

import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.control.GruppeByRelativerPositionComparator;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmen;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmenNachGeometricheFolgeGeneratorKonfiguration;
import de.ivu.elect.business.hochrechnung.entity.ZufaelligeParteiStimmenGenerator;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturByRelativePositionComparator;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;

public class ParteiStimmenNachGeometricheFolgeGenerator extends ZufaelligeParteiStimmenGenerator {

  ParteiStimmenNachGeometricheFolgeGeneratorKonfiguration konfiguration;

  public ParteiStimmenNachGeometricheFolgeGenerator(ParteiStimmenNachGeometricheFolgeGeneratorKonfiguration konfiguration) {
    this.konfiguration = konfiguration;
  }

  @Override
  public Map<PolitischeGruppe, ParteiStimmen<Double>> generateZufaelligeParteiStimmenanteile(Gebiet gebiet, List<PolitischeGruppe> politischeGruppen, Function<PolitischeGruppe, Set<Stimmenart>> stimmenartenProPolitischeGruppe, Function<PolitischeGruppe, Set<Listenkandidatur>> listenkandidaturenProPolitischeGruppe) {
    Map<PolitischeGruppe, ParteiStimmen<Double>> resultUnnormiert = new HashMap<>();

    List<PolitischeGruppe> politischeGruppenSortiert = politischeGruppen.stream().sorted(new GruppeByRelativerPositionComparator().reversed()).collect(Collectors.toList());

    Map<PolitischeGruppe, Double> stimmenProPolitischeGruppe = erzeugePolitischeGruppenStimmenNachGeometrischerFolge(konfiguration.getBasisFuerGeometrischeFolgeParteiStimmen(), politischeGruppenSortiert);

    for (PolitischeGruppe politischeGruppe: politischeGruppenSortiert) {
      resultUnnormiert.put(politischeGruppe, getParteiStimmenFuerPolitischeGruppe(politischeGruppe, stimmenProPolitischeGruppe.get(politischeGruppe), stimmenartenProPolitischeGruppe.apply(politischeGruppe), listenkandidaturenProPolitischeGruppe.apply(politischeGruppe)));
    }

    return new GebietDatenNormierer<Double>().getParteiStimmenAnteileNormiert(resultUnnormiert);
  }

  private ParteiStimmen<Double> getParteiStimmenFuerPolitischeGruppe(PolitischeGruppe politischeGruppe, Double stimmenanteilGesamtFuerGruppe, Set<Stimmenart> stimmenarten, Set<Listenkandidatur> listenkandidaturen) {
    Map<Stimmenart, Double> stimmen = new EnumMap<>(Stimmenart.class);

    if (stimmenarten != null) {
      stimmenarten.forEach(stimmenart -> stimmen.put(stimmenart, stimmenanteilGesamtFuerGruppe));
    }

    Map<Listenkandidatur, Double> listenkandidaturStimmen = new HashMap<>();

    if (listenkandidaturen != null && !listenkandidaturen.isEmpty()) {
      listenkandidaturStimmen = erzeugeListenKandidaturStimmenNachGeometrischerFolge(konfiguration.getBasisFuerGeometrischeFolgeListenkandidaturenStimmen(), listenkandidaturen);
    }

    return new ParteiStimmen<>(politischeGruppe, stimmen, listenkandidaturStimmen);
  }

  public static Map<Listenkandidatur, Double> erzeugeListenKandidaturStimmenNachGeometrischerFolge(double basisFuerGeometrischeFolge, Set<Listenkandidatur> listenkandidaturenAmGebiet) {
    List<Listenkandidatur> listenkandidaturenSortedByRelativePosition = listenkandidaturenAmGebiet.stream()
        .sorted(new ListenkandidaturByRelativePositionComparator().reversed())
        .collect(Collectors.toList());

    Map<Listenkandidatur, Double> result = new HashMap<>();
    double value = 1;

    for (Listenkandidatur listenkandidatur : listenkandidaturenSortedByRelativePosition) {
      Long anzahlNennungen = listenkandidatur.getAnzahlNennungen();
      if (anzahlNennungen == null || anzahlNennungen < 1) {
        anzahlNennungen = 1L;
      }
      result.put(listenkandidatur, value * anzahlNennungen);
      value *= basisFuerGeometrischeFolge;
    }

    return result;
  }

  private Map<PolitischeGruppe, Double> erzeugePolitischeGruppenStimmenNachGeometrischerFolge(double basisFuerGeometrischeFolge, List<PolitischeGruppe> politischeGruppenSortiert) {

    Map<PolitischeGruppe, Double> result = new HashMap<>();

    double value = 1;

    for (PolitischeGruppe politischeGruppe : politischeGruppenSortiert) {
      result.put(politischeGruppe, value);
      value *= basisFuerGeometrischeFolge;
    }

    return result;
  }
}
