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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
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.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.AbstractKandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmzettel.entity.AbstractStimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumArt;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.stimmzettel.ergebnis.StimmzettelErgebnis;

/**
 * ELECT-9008
 */
public class StimmzettelStimmenBerechner implements Serializable {

  private static final long serialVersionUID = 1L;

  public Map<Listenkandidatur, Long> werteStimmzettelAus(StimmzettelDaten stimmzettelDaten, Gebiet gebiet, List<Stimmzettel> stimmzettelListe,
                                                         List<StimmzettelKopfdatum> stimmzettelKopfdaten,
                                                         boolean unveraenderteStimmzettelAufListenkandidaturnAufteilen) {

    // mit 0 für alle Listenkandidaturen initialisiert
    Map<Listenkandidatur, Long> result = stimmzettelDaten.getListen().stream().flatMap(liste -> stimmzettelDaten.getListenkandidaturen(liste).stream())
        .collect(Collectors.toMap(listenkandidatur -> listenkandidatur, listenkandidatur -> 0L, (a, b) -> b));

    Map<String, Listenkandidatur> listenkandidaturNachNummer = new HashMap<>();
    for (Listenkandidatur listenkandidatur : result.keySet()) {
      listenkandidaturNachNummer.put(stimmzettelDaten.getListenkandidaturKennzahlFormatted(listenkandidatur), listenkandidatur);
    }


    // Stimmzettel einzeln erfasst
    for (Stimmzettel stimmzettel : stimmzettelListe) {
      if (stimmzettel.getGueltigkeit().isGueltig() && gebiet.equals(stimmzettel.getGebiet())) {
        StimmzettelErgebnis stimmzettelErgebnis = new StimmzettelErgebnis(stimmzettel.getErfassteDaten());
        Map<String, Long> stimmenFuerKandidaten = stimmzettelErgebnis.getStimmenFuerKandidaten();
        for (Map.Entry<String, Long> entry : stimmenFuerKandidaten.entrySet()) {
          Long value = entry.getValue();
          if (value != null && value > 0) {
            String nummer = entry.getKey();
            if (StringUtils.isNotBlank(nummer)) {
              Listenkandidatur listenkandidatur = listenkandidaturNachNummer.get(nummer);
              if (listenkandidatur != null) {
                result.put(listenkandidatur, result.get(listenkandidatur) + value);
              }
            }
          }
        }
      }
    }

    if (unveraenderteStimmzettelAufListenkandidaturnAufteilen) {
      // unveränderte Stimmzettel
      Map<Gruppe, List<Listenkandidatur>> listenkandidaturenNachGruppe = result.keySet().stream().collect(Collectors.groupingBy(AbstractKandidatur::getGruppe));

      for (StimmzettelKopfdatum stimmzettelKopfdatum : stimmzettelKopfdaten) {
        long anzahlUnverandert = stimmzettelKopfdatum.parseWertAsLong();
        if (gebiet.equals(stimmzettelKopfdatum.getGebiet())) {
          if (StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_UNVERAENDERT/*gruppe!=null*/.equals(stimmzettelKopfdatum.getStimmzettelKopfdatumArt())) {
            if (stimmzettelKopfdatum.getGruppe() != null) {
              for (Listenkandidatur listenkandidatur : listenkandidaturenNachGruppe.getOrDefault(stimmzettelKopfdatum.getGruppe(), Collections.emptyList())) {
                result.put(listenkandidatur,
                    result.get(listenkandidatur) + anzahlUnverandert * stimmzettelDaten.getAnzahlStimmenBeiUnveraenderterListe(listenkandidatur));
              }
            }
          }
        }
      }
    }

    return result;
  }

  public Map<Liste, Long> bestimmeAnzahlStimmzettelUnveraendert(StimmzettelDaten stimmzettelDaten, Gebiet gebiet, List<Stimmzettel> stimmzettelListe, List<StimmzettelKopfdatum> stimmzettelKopfdaten) {
    Map<Liste, Long> result = new HashMap<>();

    for (Liste liste : stimmzettelDaten.getListen()) {
      result.put(liste, 0L);
    }

    Map<Liste, List<String>> unveraendertGekennzeichteStimmzettelNachListe = getUnveraendertGekennzeichteStimmzettelNachListe(stimmzettelDaten, gebiet, stimmzettelListe);

    for (Map.Entry<Liste, List<String>> entry : unveraendertGekennzeichteStimmzettelNachListe.entrySet()) {
      Liste liste = entry.getKey();
      List<String> unveraenderteStimmzettel = entry.getValue();
      if (!unveraenderteStimmzettel.isEmpty()) {
        result.put(liste, result.get(liste) + unveraenderteStimmzettel.size());
      }
    }

    for (StimmzettelKopfdatum stimmzettelKopfdatum : stimmzettelKopfdaten) {
      long anzahlUnverandert = stimmzettelKopfdatum.parseWertAsLong();
      if (gebiet.equals(stimmzettelKopfdatum.getGebiet())) {
        if (StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_UNVERAENDERT/*gruppe!=null*/.equals(stimmzettelKopfdatum.getStimmzettelKopfdatumArt())) {
          if (stimmzettelKopfdatum.getGruppe() != null) {
            Liste liste = stimmzettelDaten.getListenNachGruppe().get(stimmzettelKopfdatum.getGruppe());
            if (liste != null) {
              result.put(liste, result.get(liste) + anzahlUnverandert);
            }
          }
        }
      }
    }

    return result;
  }

  public Map<Liste, List<String>> getUnveraendertGekennzeichteStimmzettelNachListe(StimmzettelDaten stimmzettelDaten, Gebiet gebiet, List<Stimmzettel> stimmzettelListe) {
    Map<Liste, List<String>> result = new HashMap<>();
    for (Liste liste : stimmzettelDaten.getListen()) {
      result.put(liste, new ArrayList<>());
    }
    for (Stimmzettel stimmzettel : stimmzettelListe) {
      if (stimmzettel.getGueltigkeit().isGueltig() && gebiet.equals(stimmzettel.getGebiet())) {
        StimmzettelErgebnis stimmzettelErgebnis = new StimmzettelErgebnis(stimmzettel.getErfassteDaten());
        String unveraendertGekennzeichnetenWahlvorschlag = stimmzettelErgebnis.getUnveraendertGekennzeichnetenWahlvorschlag();
        if (StringUtils.isNotBlank(unveraendertGekennzeichnetenWahlvorschlag)) {
          Liste liste = stimmzettelDaten.getListenNachKennzahlFormatted().get(unveraendertGekennzeichnetenWahlvorschlag);
          if (liste != null) {
            result.get(liste).add(stimmzettel.getNummer());
          }
        }
      }
    }
    return result;
  }

  public Map<Liste, Long> bestimmeAnzahlStimmzettelVeraendert(StimmzettelDaten stimmzettelDaten, Gebiet gebiet, List<Stimmzettel> stimmzettelListe) {
    Map<Liste, Long> result = new HashMap<>();
    Map<String, Liste> listenNachNummer = new HashMap<>();

    for (Liste liste : stimmzettelDaten.getListen()) {
      result.put(liste, 0L);
      listenNachNummer.put(stimmzettelDaten.getListeKennzahlFormatted(liste), liste);
    }
    for (Stimmzettel stimmzettel : stimmzettelListe) {
      if (stimmzettel.getGueltigkeit().isGueltig() && gebiet.equals(stimmzettel.getGebiet())) {
        StimmzettelErgebnis stimmzettelErgebnis = new StimmzettelErgebnis(stimmzettel.getErfassteDaten());
        String innerhalbVeraendertenWahlvorschlag = stimmzettelErgebnis.getInnerhalbVeraendertenWahlvorschlag();
        if (StringUtils.isNotBlank(innerhalbVeraendertenWahlvorschlag)) {
          Liste liste = listenNachNummer.get(innerhalbVeraendertenWahlvorschlag);
          if (liste != null) {
            result.put(liste, result.get(liste) + 1L);
          }
        }
      }
    }
    return result;
  }

  public Map<PolitischeGruppe, Long> getErststimmenAktuellGesamt() {
    return Collections.emptyMap(); // bis jetzt nicht unterstützt
  }

  public Map<PolitischeGruppe, Long> getZweitstimmenAktuellGesamt(StimmzettelDaten stimmzettelDaten, Function<Listenkandidatur, Long> stimmenFuerListenkandidaturStimmenFct) {
    Map<PolitischeGruppe, Long> result = new HashMap<>();

    for (Liste liste : stimmzettelDaten.getListen()) {
      Long stimmenDerGruppe = null;
      for (Listenkandidatur listenkandidatur : stimmzettelDaten.getListenkandidaturen(liste)) {
        Long stimmenFuerListenkandidatur = stimmenFuerListenkandidaturStimmenFct.apply(listenkandidatur);
        if (stimmenFuerListenkandidatur != null) {

          if (stimmenDerGruppe == null) {
            stimmenDerGruppe = 0L;
          }
          stimmenDerGruppe += stimmenFuerListenkandidatur;
        }
      }
      result.put(liste.getGruppe(), stimmenDerGruppe);
    }
    return result;
  }

  public Long getWaehlerOhneWahlschein(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    Long waehlerGesamt = getWaehlerGesamt(wertAusKopfdatenFct);
    if (waehlerGesamt == null) {
      return null;
    }
    if (waehlerGesamt == 0) {
      return 0L;
    }
    Long waehlerMitWahlschein = getWaehlerMitWahlschein(wertAusKopfdatenFct);
    if (waehlerMitWahlschein == null) {
      waehlerMitWahlschein = 0L;
    }
    return Math.max(waehlerGesamt - waehlerMitWahlschein, 0L);
  }

  public Long getWaehlerMitWahlschein(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    return wertAusKopfdatenFct.apply(StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_MIT_WAHLSCHEIN);
  }

  public Long getWaehlerGesamt(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    return wertAusKopfdatenFct.apply(StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_GESAMT);
  }

  public Long getWahlberechtigteGesamt(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    return wertAusKopfdatenFct.apply(StimmzettelKopfdatumArt.ANZAHL_WAHLBERECHTIGTE_GESAMT);
  }

  public Long getWahlberechtigteOhneWahlschein(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    return wertAusKopfdatenFct.apply(StimmzettelKopfdatumArt.ANZAHL_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN);
  }

  public Long getWahlberechtigteMitWahlschein(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    return wertAusKopfdatenFct.apply(StimmzettelKopfdatumArt.ANZAHL_WAHLBERECHTIGTE_MIT_WAHLSCHEIN);
  }

  public Long getWahlberechtigteNichtImWaehlerverzeichnis(Function<StimmzettelKopfdatumArt, Long> wertAusKopfdatenFct) {
    return null; // nicht erfasst...?
  }

  @Nullable
  public Long getStimmzettelUngueltig(List<Stimmzettel> stimmzettelListe, List<StimmzettelKopfdatum> stimmzettelKopfdaten) {

    Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>> kopfdatenNachArt = stimmzettelKopfdaten.stream()
        .collect(Collectors.groupingBy(AbstractStimmzettelKopfdatum::getStimmzettelKopfdatumArt, () -> new EnumMap<>(StimmzettelKopfdatumArt.class), Collectors.toList()));


    String valueAsString = StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_UNGUELTIG_GESAMT.berechneWert(kopfdatenNachArt, stimmzettelListe);
    if (StringUtils.isBlank(valueAsString)) {
      return null;
    }

    return Long.parseLong(valueAsString);

  }

}
