
package de.ivu.elect.core.exchange.stimmen.csv;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Objects.nonNull;

import java.util.ArrayList;
import java.util.Arrays;
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.function.Supplier;

import javax.annotation.Nullable;
import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.boundary.StimmenWaehler;
import de.ivu.elect.business.ergebnis.control.AbstractErgebnisDatenWahlberechtigte;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenWaehler;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.control.GebietByNummerComparator;
import de.ivu.elect.business.gebietsbaum.control.GebietByPositionComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;

public abstract class AbstractStimmenMapper {

  @Inject
  protected transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  protected AbstractStimmenMapper() {
    super();
  }

  /**
   * @deprecated
   * @see AbstractStimmenMapper#mapKopfzahlen
   */
  @Deprecated
  protected void addWahlberechtigteToCsvDTO(StimmenCsvDTO dto, Gebiet gebiet, Stimmabgabeart stimmabgabeart, Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemgruppenStimmenByGebietUndGruppe) {
    List<Stimmen> stimmenDerGruppeAufDemGebiet = getByGebietAndSystemGruppe(gebiet, stimmabgabeart, SystemGruppe.WAHLBERECHTIGTE, systemgruppenStimmenByGebietUndGruppe);

    dto.setAnzahlWahlberechtigteOhneWahlschein(summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.OHNE_WAHLSCHEIN));
    dto.setAnzahlWahlberechtigteMitWahlschein(summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.MIT_WAHLSCHEIN));
    dto.setAnzahlWahlberechtigteNichtImWaehlerverzeichnis(summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS));
  }

  /**
   * @deprecated
   * @see AbstractStimmenMapper#mapKopfzahlen
   */
  @Deprecated
  protected void addWaehlerToCsvDTO(StimmenCsvDTO dto, Gebiet gebiet, Stimmabgabeart stimmabgabeart, Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemgruppenStimmenByGebietUndGruppe) {
    List<Stimmen> stimmenDerGruppeAufDemGebiet = getByGebietAndSystemGruppe(gebiet, stimmabgabeart, SystemGruppe.WAEHLER, systemgruppenStimmenByGebietUndGruppe);

    StimmenWaehler stimmenWaehler = new StimmenWaehler(gebiet, //
        () -> summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.GESAMT), // ELECT-3284, ELECTMUC-933

        () -> summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.OHNE_WAHLSCHEIN), //

        () -> summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.MIT_WAHLSCHEIN), //
        () -> summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.ALS_URNENWAHL_IN_URNE), //
        () -> summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.ALS_BRIEFWAHL_IN_URNE), //

        () -> summiereAnzahlFuerStimmart(stimmenDerGruppeAufDemGebiet, Stimmenart.MIT_VOLLMACHT) //
    );

    dto.setAnzahlWaehlerOhneWahlschein(stimmenWaehler.getAnzahlOhneWahlschein());

    dto.setAnzahlWaehlerMitWahlschein(stimmenWaehler.getAnzahlMitWahlschein());
    dto.setAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(stimmenWaehler.getAnzahlMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl());
    dto.setAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(stimmenWaehler.getAnzahlMitWahlscheinInUrnenwahlAnzahlAusBriefwahl());
  }

  protected void mapGebietAndKopfzahlen(StimmenCsvDTO dto, ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    mapGebiet(dto, auswertungsgebiet);
    mapKopfzahlen(dto, ergebnisDaten, auswertungsgebiet);
  }

  protected void mapGebiet(StimmenCsvDTO dto, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    dto.setGebiet(auswertungsgebiet);
  }

  protected void mapKopfzahlen(StimmenCsvDTO dto, ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    mapAnzahlenWahlberechtigte(dto, ergebnisDaten, auswertungsgebiet);
    mapAnzahlenWaehler(dto, ergebnisDaten, auswertungsgebiet);
  }

  private void mapAnzahlenWahlberechtigte(StimmenCsvDTO dto, ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    GebietUndStimmabgabeart auswertungsgebietUndStimmabgabeart = new GebietUndStimmabgabeart(auswertungsgebiet, Stimmabgabeart.GESAMT);
    AbstractErgebnisDatenWahlberechtigte ergebnisDatenWahlberechtigte = ergebnisDaten.getErgebnisDatenWahlberechtigte();

    dto.setAnzahlWahlberechtigteOhneWahlschein(ergebnisDatenWahlberechtigte.getSummeWahlberechtigteOhneWahlschein(auswertungsgebietUndStimmabgabeart));
    dto.setAnzahlWahlberechtigteMitWahlschein(ergebnisDatenWahlberechtigte.getSummeWahlberechtigteMitWahlschein(auswertungsgebietUndStimmabgabeart));
    dto.setAnzahlWahlberechtigteNichtImWaehlerverzeichnis(ergebnisDatenWahlberechtigte.getSummeWahlberechtigteNichtImWaehlerverzeichnis(auswertungsgebietUndStimmabgabeart));
  }

  private void mapAnzahlenWaehler(StimmenCsvDTO dto, ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet) {
    GebietUndStimmabgabeart auswertungsgebietUndStimmabgabeart = new GebietUndStimmabgabeart(auswertungsgebiet, Stimmabgabeart.GESAMT);
    ErgebnisDatenWaehler ergebnisDatenWaehler = ergebnisDaten.getErgebnisDatenWaehler();

    dto.setAnzahlWaehlerOhneWahlschein(ergebnisDatenWaehler.getSummeWaehlerOhneWahlschein(auswertungsgebietUndStimmabgabeart));
    dto.setAnzahlWaehlerMitWahlschein(ergebnisDatenWaehler.getSummeWaehlerMitWahlschein(auswertungsgebietUndStimmabgabeart));
    dto.setAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(ergebnisDatenWaehler.getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl(auswertungsgebietUndStimmabgabeart));
    dto.setAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(ergebnisDatenWaehler.getSummeWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl(auswertungsgebietUndStimmabgabeart));
  }

  @Nullable
  protected Long summiereAnzahlFuerStimmart(List<Stimmen> stimmenDerGruppeAufDemGebiet, Stimmenart stimmenart) {
    Long anzahl = null;

    for (Stimmen stimmen : stimmenDerGruppeAufDemGebiet) {
      if (stimmenart.equals(stimmen.getStimmenart())) {
        if (anzahl == null) {
          anzahl = stimmen.getAnzahl();
        } else {
          anzahl += notNull(stimmen.getAnzahl());
        }
      }
    }

    return anzahl;
  }

  protected Long notNull(Long anzahl) {
    if (anzahl == null) {
      return 0L;
    }

    return anzahl;
  }

  protected Map<Gebiet, Map<Gruppe, List<Stimmen>>> aggregateStimmenByGebietUndGruppe(List<Stimmen> stimmenEndgueltig) {
    Map<Gebiet, Map<Gruppe, List<Stimmen>>> result = new HashMap<>();

    for (Stimmen stimmen : stimmenEndgueltig) {
      Map<Gruppe, List<Stimmen>> map = getByGebiet(result, stimmen);

      List<Stimmen> list = getByGruppe(stimmen, map);

      list.add(stimmen);
    }

    return result;
  }

  protected Gebiet[] getSortedGebiete(Map<Gebiet, ? > map) {
    Gebiet[] result = map.keySet().toArray(new Gebiet[0]);
    Arrays.sort(result, GebietByNummerComparator.ASCENDING);

    return result;
  }

  protected List<Stimmen> getByGebietAndSystemGruppe(Gebiet gebiet, Stimmabgabeart stimmabgabeart, Gruppe systemGruppe, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenByGebietUndGruppe) {
    List<Stimmen> result = new ArrayList<>();

    for (Gebiet erfassungsgebiet : Stimmabgabeart.getUntergeordneteErfassungsgebieteNachStimmabgabeart(gebiet, stimmabgabeart, getErfassteGebiete(stimmabgabeart))) {
      if (stimmenByGebietUndGruppe.containsKey(erfassungsgebiet)) {
        Map<Gruppe, List<Stimmen>> stimmenByGruppe = stimmenByGebietUndGruppe.get(erfassungsgebiet);

        if (stimmenByGruppe.containsKey(systemGruppe)) {
          result.addAll(stimmenByGruppe.get(systemGruppe));
        }
      }
    }

    return result;
  }

  protected Supplier<Set<Gebiet>> getErfassteGebiete(Stimmabgabeart stimmabgabeart) {
    if (stimmabgabeart.isBrief() || stimmabgabeart.isUrne() || stimmabgabeart.isGesamt()) {
      return HashSet::new;
    }

    throw new IllegalStateException("Die erfassten Gebiete müssen an dieser Stelle ermittelt werden: " + stimmabgabeart);
  }

  protected List<Stimmen> getByGebietAndSystemGruppe(Gebiet gebiet, Stimmabgabeart stimmabgabeart, SystemGruppe systemGruppe, Map<Gebiet, Map<Gruppe, List<Stimmen>>> stimmenByGebietUndGruppe) {
    List<Stimmen> result = new ArrayList<>();

    for (Gebiet erfassungsgebiet : Stimmabgabeart.getUntergeordneteErfassungsgebieteNachStimmabgabeart(gebiet, stimmabgabeart, getErfassteGebiete(stimmabgabeart))) {
      if (stimmenByGebietUndGruppe.containsKey(erfassungsgebiet)) {
        result.addAll(getBySystemGruppe(systemGruppe, stimmenByGebietUndGruppe.get(erfassungsgebiet)));
      }
    }

    return result;
  }

  private List<Stimmen> getBySystemGruppe(SystemGruppe systemGruppe, Map<Gruppe, List<Stimmen>> systemgruppenStimmenByGruppe) {
    for (Map.Entry<Gruppe, List<Stimmen>> gruppeStimmenEntry : systemgruppenStimmenByGruppe.entrySet()) {
      if (systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppeStimmenEntry.getKey(), systemGruppe)) {
        return gruppeStimmenEntry.getValue();
      }
    }

    return Collections.emptyList();
  }

  private List<Stimmen> getByGruppe(Stimmen stimmen, Map<Gruppe, List<Stimmen>> map) {
    if (!map.containsKey(stimmen.getGruppe())) {
      map.put(stimmen.getGruppe(), new ArrayList<Stimmen>());
    }

    return map.get(stimmen.getGruppe());
  }

  private Map<Gruppe, List<Stimmen>> getByGebiet(Map<Gebiet, Map<Gruppe, List<Stimmen>>> result, Stimmen stimmen) {
    if (!result.containsKey(stimmen.getGebiet())) {
      result.put(stimmen.getGebiet(), new HashMap<Gruppe, List<Stimmen>>());
    }

    return result.get(stimmen.getGebiet());
  }

  protected List<PolitischeGruppe> getRelevanteGruppenSortiert(ErgebnisDaten ergebnisDaten) {
    return ergebnisDaten.getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(ergebnisDaten.getWurzelgebiet());
  }

  protected List<Wahldurchfuehrungsgebiet> getRelevanteWahlgebieteSortiert(ErgebnisDaten ergebnisDaten) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>(getRelevanteWahlgebiete(ergebnisDaten));
    Collections.sort(result, new GebietByPositionComparator());

    return result;
  }

  protected Collection<Wahldurchfuehrungsgebiet> getRelevanteWahlgebiete(ErgebnisDaten ergebnisDaten) {
    Wahldurchfuehrungsgebiet wurzelgebiet = ergebnisDaten.getWurzelgebiet();

    if (nonNull(wurzelgebiet)) {
      Wahlart wahlart = ergebnisDaten.getWahl().getWahlart();

      if (isGebieteVorhandenUndNichtWurzelgebiet(wurzelgebiet, wahlart.getGebietsartenDerWahlgebiete())) {
        return ergebnisDaten.findGebieteByGebietsarten(wahlart.getGebietsartenDerWahlgebiete());
      }

      if (isGebieteVorhandenUndNichtWurzelgebiet(wurzelgebiet, wahlart.getGebietsartenDerReferendengebiete())) {
        return ergebnisDaten.findGebieteByGebietsarten(wahlart.getGebietsartenDerReferendengebiete());
      }

      if (isGebieteVorhandenUndNichtWurzelgebiet(wurzelgebiet, wahlart.getGebietsartenDerListengebiete())) {
        return ergebnisDaten.findGebieteByGebietsarten(wahlart.getGebietsartenDerListengebiete());
      }

      if (isGebieteVorhandenUndNichtWurzelgebiet(wurzelgebiet, wahlart.getGebietsartenDerDirektkandidatengebiete())) {
        return ergebnisDaten.findGebieteByGebietsarten(wahlart.getGebietsartenDerDirektkandidatengebiete());
      }

      return asList(wurzelgebiet);
    }

    return emptyList();
  }

  private boolean isGebieteVorhandenUndNichtWurzelgebiet(Gebiet wurzelgebiet, Collection<Gebietsart> gebietsarten) {
    if (!gebietsarten.isEmpty()) {
      return !gebietsarten.contains(wurzelgebiet.getGebietsart());
    }

    return false;
  }

  @Nullable
  protected WahlvorschlagStimmzettelPosition berechneStimmzettelPosition(Gruppe gruppe, Wahldurchfuehrungsgebiet gebiet, ErgebnisDaten ergebnisDaten,
      EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    GruppeImGebiet gruppeImGebiet = ergebnisDaten.getGruppeImGebiet(gruppe, gebiet);

    if (gruppeImGebiet != null) {
      return gruppeImGebiet.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten);
    }

    return null;
  }
}
