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

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
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.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmen;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmenGleichverteilungGeneratorKonfiguration;
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 ParteiStimmenGleichverteilungGenerator extends ZufaelligeParteiStimmenGenerator {

  ParteiStimmenGleichverteilungGeneratorKonfiguration konfiguration;

  public ParteiStimmenGleichverteilungGenerator(ParteiStimmenGleichverteilungGeneratorKonfiguration 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<>();

    Map<PolitischeGruppe, Double> stimmenProPolitischeGruppe = erzeugePolitischeGruppenAusGleichverteilung(politischeGruppen);

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

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

  private Map<PolitischeGruppe, Double> erzeugePolitischeGruppenAusGleichverteilung(List<PolitischeGruppe> politischeGruppen) {
    Map<PolitischeGruppe, Double> result = erzeugeAnteileAusGleichverteilung(
        new HashSet<>(politischeGruppen), new ParteiStimmenGleichverteilungGenerator.PolitscheGruppeByRelativerPositionComparator(), politischeGruppe -> 1L);

    return result;
  }

  private <O> Map<O, Double> erzeugeAnteileAusGleichverteilung(Set<O> objekte, Comparator<O> comparatorSortiertNachWichtigkeitAufsteigend, Function<O, Long> zusaetzlicheWichtungFunktoin) {

    long anzahlObjekte = objekte.size();
    if (anzahlObjekte < 1) {
      return new HashMap<>();
    }
    if (anzahlObjekte == 1) {
      return Collections.singletonMap(objekte.iterator().next(), 1d);
    }

    List<O> objekteSortiertNachWichtigkeitAufsteigend = objekte.stream()
        .sorted(comparatorSortiertNachWichtigkeitAufsteigend)
        .collect(Collectors.toList());

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

    for (O o : objekteSortiertNachWichtigkeitAufsteigend) {
      Long wichtung = zusaetzlicheWichtungFunktoin.apply(o);
      if (wichtung == null || wichtung < 1) {
        wichtung = 1L;
      }
      double y = this.konfiguration.getUniformDistribution().sample();
      result.put(o, y * wichtung);
    }

    return result;
  }

  private class PolitscheGruppeByRelativerPositionComparator implements Comparator<PolitischeGruppe>, Serializable {
    @Override
    public int compare(PolitischeGruppe o1, PolitischeGruppe o2) {
      return o1.getRelativePosition().compareTo(o2.getRelativePosition());
    }
  }

  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 = erzeugeListenKandidaturStimmenAusGleichverteilung(listenkandidaturen);
    }

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

  private Map<Listenkandidatur, Double> erzeugeListenKandidaturStimmenAusGleichverteilung(Set<Listenkandidatur> listenkandidaturen) {
    Map<Listenkandidatur, Double> result = erzeugeAnteileAusGleichverteilung(
        listenkandidaturen,
        new ListenkandidaturByRelativePositionComparator(),
        ParteiStimmenGleichverteilungGenerator::getAnzahlNennungenFuerListenkandidatur);

    return result;
  }
  private static long getAnzahlNennungenFuerListenkandidatur(Listenkandidatur listenkandidatur) {
    Long anzahlNennungen = listenkandidatur.getAnzahlNennungen();
    if (anzahlNennungen == null || anzahlNennungen < 1) {
      anzahlNennungen = 1L;
    }
    return anzahlNennungen;
  }
}
