package de.ivu.elect.business.statistik.boundary;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.statistik.entity.StatistikKennzeichen;
import de.ivu.elect.business.statistik.entity.StatistikKennzeichenArt;
import de.ivu.elect.business.wahl.entity.Wahl;

public class StatistikKennzeichenService {

  private final List<StatistikKennzeichen> alleStatistikKennzeichenEinesLaufs;

  private Map<Gebiet, List<StatistikKennzeichen>> kennzeichenNachGebiet;

  public List<StatistikKennzeichen> getKennzeichenFuerGebiet(Gebiet gebiet) {
    if (kennzeichenNachGebiet == null) {
      kennzeichenNachGebiet = alleStatistikKennzeichenEinesLaufs.stream().collect(Collectors.groupingBy(StatistikKennzeichen::getGebiet));
    }
    return kennzeichenNachGebiet.getOrDefault(gebiet, Collections.emptyList());
  }

  public StatistikKennzeichenService(List<StatistikKennzeichen> alleStatistikKennzeichenEinesLaufs) {
    this.alleStatistikKennzeichenEinesLaufs = alleStatistikKennzeichenEinesLaufs;
  }

  public Long getWertFuerKennzeichenGebietUndWahl(StatistikKennzeichenArt statistikKennzeichenArt, Gebiet gebiet, @Nullable Wahl wahl) {
    if (statistikKennzeichenArt == null || gebiet == null) {
      throw new IllegalStateException("StatistikKennzeichenArt: [" + statistikKennzeichenArt + "] und Gebiet: [" + gebiet + "] duerfen nicht null sein!");
    }

    List<StatistikKennzeichen> relevanteStatistikKennzeichen = getKennzeichenFuerGebiet(gebiet).stream()
        .filter(statistikKennzeichen -> statistikKennzeichen.getKennzeichen() == statistikKennzeichenArt)
        .filter(statistikKennzeichen -> gebiet.equals(statistikKennzeichen.getGebiet()))
        .filter((statistikKennzeichen -> Objects.equals(wahl, statistikKennzeichen.getWahl())))
        .filter(statistikKennzeichen -> StringUtils.isBlank(statistikKennzeichen.getQualifier()))
        .collect(Collectors.toList());

    if (relevanteStatistikKennzeichen.size() == 1) {
      return relevanteStatistikKennzeichen.get(0).getWert();
    } else if (relevanteStatistikKennzeichen.size() == 0) {
      return 0L;
    }

    throw new IllegalStateException("Es kann kein eindeutiger Wert ausgegeben werden, da mehrere StatistikKennzeichen gefunden wurden: " + relevanteStatistikKennzeichen.toString());
  }

  public Long getSummeAusWertenFuerKennzeichenWahlUndGebiete(StatistikKennzeichenArt statistikKennzeichenArt, List<? extends Gebiet> gebiete, @Nullable Wahl wahl) {
    Long result = 0L;

    for (Gebiet gebiet : gebiete) {
      result += getWertFuerKennzeichenGebietUndWahl(statistikKennzeichenArt, gebiet, wahl);
    }

    return result;
  }

  public Map<String, Long> getWertFuerKennzeichenGebietUndWahlGruppiertNachQualifier(StatistikKennzeichenArt statistikKennzeichenArt, Gebiet gebiet, @Nullable Wahl wahl) {
    if (statistikKennzeichenArt == null || gebiet == null) {
      throw new IllegalStateException("StatistikKennzeichenArt: [" + statistikKennzeichenArt + "] und Gebiet: [" + gebiet + "] duerfen nicht null sein!");
    }

    return getKennzeichenFuerGebiet(gebiet).stream()
        .filter(statistikKennzeichen -> statistikKennzeichen.getKennzeichen() == statistikKennzeichenArt)
        .filter(statistikKennzeichen -> gebiet.equals(statistikKennzeichen.getGebiet()))
        .filter((statistikKennzeichen -> Objects.equals(wahl, statistikKennzeichen.getWahl())))
        .filter(statistikKennzeichen -> StringUtils.isNoneBlank(statistikKennzeichen.getQualifier()))
        .collect(Collectors.toMap(StatistikKennzeichen::getQualifier, StatistikKennzeichen::getWert));
  }

  public Map<String, Long> getSummeAusWertenFuerKennzeichenWahlUndGebietenGruppiertNachQualifier(StatistikKennzeichenArt statistikKennzeichenArt, List<? extends Gebiet> gebiete, @Nullable Wahl wahl) {
    Map<String, Long> result = new HashMap<>();

    for(Gebiet gebiet : gebiete) {
      Map<String, Long> werteNachQualifierFuerEinGebiet = getWertFuerKennzeichenGebietUndWahlGruppiertNachQualifier(statistikKennzeichenArt, gebiet, wahl);
      werteNachQualifierFuerEinGebiet.forEach((qualifier, wert) ->
          result.merge(qualifier, wert, addiereWerte()));
    }

    return result;
  }

  private BiFunction<Long, Long, Long> addiereWerte() {
    return (wert1, wert2) -> wert1 + wert2;
  }

}
