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

import java.util.ArrayList;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.control.GruppeByRelativerPositionComparator;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.boundary.Hochrechnungsalgorithm;
import de.ivu.elect.business.hochrechnung.control.KoordinatenInformationenVonEingabedatenErzeuger;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.wahlvorschlag.control.ReferendumOptionByPositionComparator;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;

public class ZufaelligerStimmenHochrechner implements Hochrechnungsalgorithm {
  private static final Logger LOG = LoggerFactory.getLogger(ZufaelligerStimmenHochrechner.class);

  private final ZufaelligerStimmenHochrechnerKonfiguration konfiguration;

  public ZufaelligerStimmenHochrechner(ZufaelligerStimmenHochrechnerKonfiguration konfiguration) {
    this.konfiguration = konfiguration;
  }

  @Override
  public StimmenhochrechnerAusgabedaten ermittleHochgerechneteStimmen(StimmenHochrechnerEingabedaten<Long> eingabedaten) {
    HochrechneralgorithmusAusfuehrbarkeitStatus ausfuehrbarkeitStatus = HochrechneralgorithmusAusfuehrbarkeitStatus.AUSFUEHRBAR;

    LOG.info("Random generator used by Hochrechnungsalgorithmus: " + konfiguration.getRandomGenerator().getClass());
    LOG.info("Seed used by random generator = " + konfiguration.getSeedFuerRandomGenerator());

    log("Hochrechner: Zufaellige Erzeugung anzahl Wahlberechtigte Fuer aktuelle Wahl wo sie nicht Vorhanden sind");
    setZufaelligeAnzahlWahlberechtigteWoNichtVorhanden(eingabedaten, konfiguration.getAnzahlWahlberechtigteGenerator());
    log("Hochrechner: Zufaellige Erzeugung anzahl Wahlberechtigte Fuer aktuelle Wahl wo sie nicht Vorhanden sind - done");

    log("Hochrechner: Ermittle Koordinaten-Informationen");
    KoordinatenInformationen koordinatenInformationen = new KoordinatenInformationenVonEingabedatenErzeuger(eingabedaten);
    log("Hochrechner: Ermittle Koordinaten-Informationen - done");

    StimmenhochrechnerAusgabedaten result = new StimmenhochrechnerAusgabedaten(ausfuehrbarkeitStatus);
    log("Initialize Ausgabedaten vom Hochrechner");
    result.initializeAusgabedaten(eingabedaten, koordinatenInformationen);
    log("Initialize Ausgabedaten vom Hochrechner - done");

    log("Berechne hochgerechnete Stimmen zufaellig");
    berechneUndSetStimmenHochZufaellig(result, koordinatenInformationen);
    log("Berechne hochgerechnete Stimmen zufaellig - Done");

    return result;
  }

  private void log(String msg) {
    LOG.debug(msg);
  }

  private void berechneUndSetStimmenHochZufaellig(StimmenhochrechnerAusgabedaten result, KoordinatenInformationen koordinatenInformationen) {
    Map<Gebiet, StimmenhochrechnerGebietAusgabedaten> ausgabedatenProGebiet = result.getAusgabedatenProGebiet();
    koordinatenInformationen.getAlleGebiete().stream().filter(gebiet -> ausgabedatenProGebiet.containsKey(gebiet))
        .forEach(gebiet -> {
          StimmenhochrechnerGebietAusgabedaten gebietAusgabedaten = ausgabedatenProGebiet.get(gebiet);
          GebietDaten<Double> hochgerechnet = berechneStimmenHochFuerGebietZufaellig(gebietAusgabedaten, koordinatenInformationen);
          gebietAusgabedaten.setHochgerechnetAnteile(hochgerechnet);
        });
  }

  private GebietDaten<Double> berechneStimmenHochFuerGebietZufaellig(StimmenhochrechnerGebietAusgabedaten gebietAusgabedaten, KoordinatenInformationen koordinatenInformationen) {
    return new GebietDatenAusZufaelligenZahlenGenerator(gebietAusgabedaten.getGebiet(),
        gebietAusgabedaten.getAktuellAnzahlen().getAnzahlWahlberechtigteImGebiet(),
        gebietAusgabedaten.getAktuellAnzahlen().getAnzahlWaehlerImGebiet(),
        getHochrechnungsalgorithmusKonfiguration(), koordinatenInformationen,
        getListenKandidaturFuerPolitischeGruppeAufGebiet(gebietAusgabedaten.getAktuellAnzahlen()),
        getFuerGebietRelevantePolitischeGruppenSortiert(gebietAusgabedaten.getAktuellAnzahlen(), koordinatenInformationen),
        getReferendumOptionenPerVorlage(gebietAusgabedaten.getAktuellAnzahlen()));
  }

  private Map<Gruppe, List<ReferendumOption>> getReferendumOptionenPerVorlage(GebietDaten<Long> gebietDatenAktuellAlsAnzahlen) {
    Map<Gruppe, List<ReferendumOption>> result = new HashMap<>();
    gebietDatenAktuellAlsAnzahlen.getReferendumFragen().forEach(((referendumVorlage, referendumFrage) -> {
      if (referendumFrage != null) {
        List<ReferendumOption> referendumOptionen = referendumFrage.getStimmenPerReferendumOption().keySet().stream().sorted(new ReferendumOptionByPositionComparator())
            .collect(Collectors.toList());
        result.put(referendumVorlage, referendumOptionen);
      }
    }));
    return result;
  }

  private List<PolitischeGruppe> getFuerGebietRelevantePolitischeGruppenSortiert(GebietDaten<Long> gebietDatenAktuellAlsAnzahlen, KoordinatenInformationen koordinatenInformationen) {
    List<PolitischeGruppe> result = new ArrayList<>(gebietDatenAktuellAlsAnzahlen.getParteiStimmen().keySet());
    if (!result.isEmpty()) {
      result.sort(new GruppeByRelativerPositionComparator());
      return result;
    }
    return koordinatenInformationen.getAllePolitischeGruppenSortiert();
  }

  private Function<PolitischeGruppe, Set<Listenkandidatur>> getListenKandidaturFuerPolitischeGruppeAufGebiet(GebietDaten<Long> aktuellAnzahlen) {
    return politischeGruppe -> {
      if (aktuellAnzahlen.getParteiStimmen().containsKey(politischeGruppe)) {
        Map<Listenkandidatur, Long> listenkandidaturStimmen = aktuellAnzahlen.getParteiStimmen().get(politischeGruppe).getListenkandidaturStimmen();
        if (listenkandidaturStimmen != null) {
          return listenkandidaturStimmen.keySet();
        }
      }
      return new HashSet<>();
    };
  }

  @Override
  public ZufaelligerStimmenHochrechnerKonfiguration getHochrechnungsalgorithmusKonfiguration() {
    return konfiguration;
  }
}
