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

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.control.GebietByPositionComparator;
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.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.KoordinatenInformationen;
import de.ivu.elect.business.hochrechnung.entity.StimmenHochrechnerEingabedaten;


import static de.ivu.elect.business.hochrechnung.control.SkalierungImErgebnisBerechner.SEHR_KLEINE_STIMMENANTEIL;

public class KoordinatenInformationenVonEingabedatenErzeuger implements KoordinatenInformationen {

  private final List<Gebiet> alleGebiete;

  private final List<Stimmenart> alleStimmenartenSortiert;

  private final List<SystemGruppe> alleSystemGruppenSortiert;

  private final List<PolitischeGruppe> allePolitischeGrueppenSortiert;

  private final Map<PolitischeGruppe, Set<Stimmenart>> inVorperiodeVorhandeneStimmenartenDerPolitischeGruppen;

  private final Map<SystemGruppe, Set<Stimmenart>> inVorperiodeVorhandeneStimmenartenDerSystemGruppen;

  public <N extends Number> KoordinatenInformationenVonEingabedatenErzeuger(StimmenHochrechnerEingabedaten<N> eingabedaten) {
    alleGebiete = extractAlleGebiete(eingabedaten).stream().sorted(new GebietByPositionComparator()).collect(Collectors.toList());
    alleStimmenartenSortiert = extractAlleStimmenarten(eingabedaten).stream().sorted().collect(Collectors.toList());

    alleSystemGruppenSortiert = extractAlleSystemGruppen(eingabedaten).stream().sorted().collect(Collectors.toList());

    allePolitischeGrueppenSortiert = extractAllePolitischeGrueppen(eingabedaten).stream().sorted(new GruppeByRelativerPositionComparator())
        .collect(Collectors.toList());

    inVorperiodeVorhandeneStimmenartenDerPolitischeGruppen = getInVorperiodeVorhandeneStimmenartenDerPolitischeGruppen(allePolitischeGrueppenSortiert, alleStimmenartenSortiert, eingabedaten);

    inVorperiodeVorhandeneStimmenartenDerSystemGruppen = getInVorperiodeVorhandeneStimmenartenDerSystemGruppen(alleSystemGruppenSortiert, alleStimmenartenSortiert, eingabedaten);
  }

  @Override
  public List<Gebiet> getAlleGebiete() {
    return alleGebiete;
  }

  @Override
  public List<SystemGruppe> getAlleSystemGruppenSortiert() {
    return alleSystemGruppenSortiert;
  }

  @Override
  public List<PolitischeGruppe> getAllePolitischeGruppenSortiert() {
    return allePolitischeGrueppenSortiert;
  }

  @Override
  public List<Stimmenart> getAlleStimmenartenSortiert() {
    return alleStimmenartenSortiert;
  }

  @Override
  public GruppeInVorperiodeVorhandenChecker getGruppeInVorperiodeVorhandenChecker() {
    return new GruppeInVorperiodeVorhandenChecker() {
      @Override
      public boolean isSystemGruppeInVorperiodeVorhanden(SystemGruppe systemGruppe, Stimmenart stimmenart) {
        return inVorperiodeVorhandeneStimmenartenDerSystemGruppen.containsKey(systemGruppe)
            && inVorperiodeVorhandeneStimmenartenDerSystemGruppen.get(systemGruppe).contains(stimmenart);
      }

      @Override
      public boolean isPolitischeGruppeInVorperiodeVorhanden(PolitischeGruppe politischeGruppe, Stimmenart stimmenart) {
        return inVorperiodeVorhandeneStimmenartenDerPolitischeGruppen.containsKey(politischeGruppe)
            && inVorperiodeVorhandeneStimmenartenDerPolitischeGruppen.get(politischeGruppe).contains(stimmenart);
      }
    };
  }

  private <N extends Number> Set<PolitischeGruppe> extractAllePolitischeGrueppen(StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Set<PolitischeGruppe> result = new HashSet<>();
    eingabedaten.getVorperiode().values().forEach(gebietdaten -> result.addAll(extractAllePolitischeGruppen(gebietdaten)));
    eingabedaten.getAktuelleWahl().values().forEach(gebietdaten -> result.addAll(extractAllePolitischeGruppen(gebietdaten)));
    return result;
  }

  private <N extends Number> Set<PolitischeGruppe> extractAllePolitischeGruppen(GebietDaten<N> gebietdaten) {
    if (gebietdaten.getParteiStimmen() != null) {
      return gebietdaten.getParteiStimmen().keySet();
    }
    return Collections.emptySet();
  }

  private <N extends Number> Set<SystemGruppe> extractAlleSystemGruppen(StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Set<SystemGruppe> result = new HashSet<>();
    eingabedaten.getVorperiode().values().forEach(gebietdaten -> result.addAll(extractAlleSystemGruppen(gebietdaten)));
    eingabedaten.getAktuelleWahl().values().forEach(gebietdaten -> result.addAll(extractAlleSystemGruppen(gebietdaten)));
    return result;
  }

  private <N extends Number> Set<SystemGruppe> extractAlleSystemGruppen(GebietDaten<N> gebietdaten) {
    Set<SystemGruppe> result = new HashSet<>();
    if (gebietdaten.getGueltige() != null) {
      result.add(SystemGruppe.GUELTIGE);
    }
    if (gebietdaten.getUngueltige() != null) {
      result.add(SystemGruppe.UNGUELTIGE);
    }
    return result;
  }

  private <N extends Number> Set<Stimmenart> extractAlleStimmenarten(StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Set<Stimmenart> result = new HashSet<>();
    eingabedaten.getVorperiode().values().forEach(gebietdaten -> result.addAll(extractAlleStimmenarten(gebietdaten)));
    eingabedaten.getAktuelleWahl().values().forEach(gebietdaten -> result.addAll(extractAlleStimmenarten(gebietdaten)));
    return result;
  }

  private <N extends Number> Set<Stimmenart> extractAlleStimmenarten(GebietDaten<N> gebietdaten) {
    Set<Stimmenart> result = new HashSet<>();
    if (gebietdaten.getGueltige() != null) {
      result.addAll(gebietdaten.getGueltige().keySet());
    }
    if (gebietdaten.getUngueltige() != null) {
      result.addAll(gebietdaten.getUngueltige().keySet());
    }
    return result;
  }

  private <N extends Number> Set<Gebiet> extractAlleGebiete(StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Set<Gebiet> result = new HashSet<>(eingabedaten.getVorperiode().keySet());
    result.addAll(eingabedaten.getAktuelleWahl().keySet());
    return result;
  }

  private <N extends Number> Map<SystemGruppe, Set<Stimmenart>> getInVorperiodeVorhandeneStimmenartenDerSystemGruppen(List<SystemGruppe> alleSystemGruppen, List<Stimmenart> alleStimmenarten, StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Map<SystemGruppe, Set<Stimmenart>> result = new HashMap<>();
    for (SystemGruppe systemGruppe: alleSystemGruppen) {
      result.put(systemGruppe, getInVorperiodeVorhandeneStimmenartenDerSystemGruppe(systemGruppe, alleStimmenarten, eingabedaten));
    }
    return result;
  }

  private <N extends Number> Set<Stimmenart> getInVorperiodeVorhandeneStimmenartenDerSystemGruppe(SystemGruppe systemGruppe, List<Stimmenart> alleStimmenarten, StimmenHochrechnerEingabedaten<N> eingabedaten) {
    return alleStimmenarten.stream()
        .filter(stimmenart -> isHatSystemGruppeInVorperiodeEinenPositivenWertInDenMeistenGebieten(systemGruppe, stimmenart, eingabedaten))
        .collect(Collectors.toSet());
  }

  private <N extends Number> boolean isHatSystemGruppeInVorperiodeEinenPositivenWertInDenMeistenGebieten(SystemGruppe systemGruppe, Stimmenart stimmenart, StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Collection<GebietDaten<N>> gebietDatenVorperiode = eingabedaten.getVorperiode().values();
    long gebietDatenVorperiodeCount = gebietDatenVorperiode.size();
    long gebietDatenVorperiodeCountWoGruppePositiveStimmenanzahlHat = gebietDatenVorperiode.stream().filter(gebietDaten -> isSystemGruppeHatPositiveStimmenImGebiet(systemGruppe, stimmenart, gebietDaten)).count();
    return isWertHatMehrheit(gebietDatenVorperiodeCountWoGruppePositiveStimmenanzahlHat, gebietDatenVorperiodeCount);
  }

  private <N extends Number> boolean isSystemGruppeHatPositiveStimmenImGebiet(SystemGruppe systemGruppe, Stimmenart stimmenart, GebietDaten<N> gebietDaten) {
    N stimmen = getSystemGruppeStimmenImGebiet(systemGruppe, stimmenart, gebietDaten);
    return isWertVorhandenUndNichtSehrKlein(stimmen);
  }

  private <N extends Number> N getSystemGruppeStimmenImGebiet(SystemGruppe systemGruppe, Stimmenart stimmenart, GebietDaten<N> gebietDaten) {
    if (systemGruppe.equals(SystemGruppe.GUELTIGE)) {
      return gebietDaten.getGueltige().get(stimmenart);
    } else if (systemGruppe.equals(SystemGruppe.UNGUELTIGE)) {
      return gebietDaten.getUngueltige().get(stimmenart);
    }
    return null;
  }

  private <N extends Number> Map<PolitischeGruppe, Set<Stimmenart>> getInVorperiodeVorhandeneStimmenartenDerPolitischeGruppen(List<PolitischeGruppe> allePolitischeGrueppen, List<Stimmenart> alleStimmenarten, StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Map<PolitischeGruppe, Set<Stimmenart>> result = new HashMap<>();
    for (PolitischeGruppe politischeGruppe: allePolitischeGrueppen) {
      result.put(politischeGruppe, getInVorperiodeVorhandeneStimmenartenDerPolitischeGruppe(politischeGruppe, alleStimmenarten, eingabedaten));
    }
    return result;
  }

  private <N extends Number> Set<Stimmenart> getInVorperiodeVorhandeneStimmenartenDerPolitischeGruppe(PolitischeGruppe politischeGruppe, List<Stimmenart> alleStimmenarten, StimmenHochrechnerEingabedaten<N> eingabedaten) {
    return alleStimmenarten.stream()
        .filter(stimmenart -> isHatPolitischeGruppeInVorperiodeEinenPositivenWertInDenMeistenGebieten(politischeGruppe, stimmenart, eingabedaten))
        .collect(Collectors.toSet());
  }

  private <N extends Number> boolean isHatPolitischeGruppeInVorperiodeEinenPositivenWertInDenMeistenGebieten(PolitischeGruppe politischeGruppe, Stimmenart stimmenart, StimmenHochrechnerEingabedaten<N> eingabedaten) {
    Collection<GebietDaten<N>> gebietDatenVorperiode = eingabedaten.getVorperiode().values();
    long gebietDatenVorperiodeCount = gebietDatenVorperiode.size();
    long gebietDatenVorperiodeCountWoGruppePositiveStimmenanzahlHat = gebietDatenVorperiode.stream().filter(gebietDaten -> isParteiHatPositiveStimmenImGebiet(politischeGruppe, stimmenart, gebietDaten)).count();
    return isWertHatMehrheit(gebietDatenVorperiodeCountWoGruppePositiveStimmenanzahlHat, gebietDatenVorperiodeCount);
  }

  private boolean isWertHatMehrheit(long teil, long gesamt) {
    return 1.0 * teil / gesamt >= .5;
  }

  private <N extends Number> boolean isParteiHatPositiveStimmenImGebiet(PolitischeGruppe politischeGruppe, Stimmenart stimmenart, GebietDaten<N> gebietDaten) {
    N stimmen = getParteiStimmenImGebiet(politischeGruppe, stimmenart, gebietDaten);
    return isWertVorhandenUndNichtSehrKlein(stimmen);
  }

  private <N extends Number> boolean isWertVorhandenUndNichtSehrKlein(N stimmen) {
    return stimmen != null && stimmen.doubleValue() > SEHR_KLEINE_STIMMENANTEIL;
  }

  private <N extends Number> N getParteiStimmenImGebiet (PolitischeGruppe politischeGruppe, Stimmenart stimmenart, GebietDaten<N> gebietDaten) {
    if (gebietDaten == null || gebietDaten.getParteiStimmen().get(politischeGruppe) == null) {
      return null;
    }
    return gebietDaten.getParteiStimmen().get(politischeGruppe).getStimmen(stimmenart);
  }
}
