
package de.ivu.elect.dokumente.ergebnistabelle.gruppen;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
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.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.core.exchange.stimmen.csv.AbstractStimmenMapper;

public class GruppenErgebnistabelleCsvDTOFactory extends AbstractStimmenMapper {

  public GruppenErgebnistabelleCsvDTOFactory() {
    super();
  }

  public GruppenErgebnistabelleCsvDTO createGruppenErgebnistabelleCsvDTO(GruppenErgebnistabelleZeilenSchluessel schluessel, ErgebnisDaten ergebnisDaten,
      Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemgruppenStimmenByGebietUndGruppe) {
    GruppenErgebnistabelleCsvDTO dto = new GruppenErgebnistabelleCsvDTO(schluessel);

    addWahlberechtigteToCsvDTO(dto, schluessel.getGebiet(), schluessel.getStimmabgabeart(), systemgruppenStimmenByGebietUndGruppe);
    addWaehlerToCsvDTO(dto, schluessel.getGebiet(), schluessel.getStimmabgabeart(), systemgruppenStimmenByGebietUndGruppe);

    addGruppenErgebnistabelleCsvDTO(dto, schluessel, ergebnisDaten, systemgruppenStimmenByGebietUndGruppe);

    return dto;
  }

  protected void addGruppenErgebnistabelleCsvDTO(GruppenErgebnistabelleCsvDTO dto, GruppenErgebnistabelleZeilenSchluessel zeile, ErgebnisDaten ergebnisDaten,
      Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemgruppenStimmenByGebietUndGruppe) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = zeile.getGebiet();

    for (GruppenErgebnistabelleSpaltenSchluessel spalte : getRelevanteSpaltenSchluesselSortiert(wahldurchfuehrungsgebiet.getWurzelgebiet(), ergebnisDaten)) {
      Gruppe gruppe = spalte.getGruppe();

      if (gruppe.isSystemGruppe()) {
        dto.addGruppenStimmen(spalte, getStimmenSummeFuerSystemGruppe(wahldurchfuehrungsgebiet, zeile.getStimmabgabeart(), spalte.getStimmenart(), gruppe, systemgruppenStimmenByGebietUndGruppe));
      } else {
        dto.addGruppenStimmen(spalte, getStimmenSummeFuerPolitischeGruppe(wahldurchfuehrungsgebiet, zeile.getStimmabgabeart(), spalte.getStimmenart(), gruppe, ergebnisDaten));
      }
    }
  }

  private long getStimmenSummeFuerSystemGruppe(Wahldurchfuehrungsgebiet gebiet, Stimmabgabeart stimmabgabeart, Stimmenart stimmenart, Gruppe gruppe,
      Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemgruppenStimmenByGebietUndGruppe) {
    List<Stimmen> stimmen = getByGebietAndSystemGruppe(gebiet, stimmabgabeart, gruppe, systemgruppenStimmenByGebietUndGruppe);

    return notNull(summiereAnzahlFuerStimmart(stimmen, stimmenart));
  }

  private long getStimmenSummeFuerPolitischeGruppe(Gebiet gebiet, Stimmabgabeart stimmabgabeart, Stimmenart stimmenart, Gruppe gruppe, ErgebnisDaten ergebnisDaten) {
    Collection<Stimmen> stimmen = new ArrayList<>();

    StimmenPool pool = ergebnisDaten.getStimmen();

    if (stimmenart.isDirektstimme()) {
      for (Gebiet erfassungsgebiet : Stimmabgabeart.getUntergeordneteErfassungsgebieteNachStimmabgabeart(gebiet, stimmabgabeart, getErfassteGebiete(stimmabgabeart))) {
        Stimmen stimmenInGebiet = pool.getDirektstimmenAufErfassungsgebiet(gruppe, erfassungsgebiet);

        if (stimmenInGebiet != null) {
          stimmen.add(stimmenInGebiet);
        }
      }
    }

    if (stimmenart.isListenstimme()) {
      for (Gebiet erfassungsgebiet : Stimmabgabeart.getUntergeordneteErfassungsgebieteNachStimmabgabeart(gebiet, stimmabgabeart, getErfassteGebiete(stimmabgabeart))) {
        Stimmen stimmenInGebiet = pool.getListenstimmenDerGruppeAufErfassungsgebiet(gruppe, erfassungsgebiet);

        if (stimmenInGebiet != null) {
          stimmen.add(stimmenInGebiet);
        }
      }
    }

    return Stimmen.summiere(stimmen);
  }

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

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

  public List<GruppenErgebnistabelleSpaltenSchluessel> getRelevanteSpaltenSchluesselSortiert(Wahldurchfuehrungsgebiet wahlgebiet, ErgebnisDaten ergebnisDaten) {
    List<GruppenErgebnistabelleSpaltenSchluessel> result = new ArrayList<>();

    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

    if (wahldurchfuehrung.isWahlMitDirektkandidatengebieten()) {
      result.addAll(getRelevanteSpaltenSchluesselSortiert(wahlgebiet, ergebnisDaten, Stimmenart.DIREKTSTIMME));
    }

    if (wahldurchfuehrung.isWahlMitListengebieten()) {
      result.addAll(getRelevanteSpaltenSchluesselSortiert(wahlgebiet, ergebnisDaten, Stimmenart.LISTENSTIMME));
    }

    return result;
  }

  public List<GruppenErgebnistabelleSpaltenSchluessel> getRelevanteSpaltenSchluesselSortiert(Wahldurchfuehrungsgebiet wahlgebiet, ErgebnisDaten ergebnisDaten, Stimmenart stimmenart) {
    List<GruppenErgebnistabelleSpaltenSchluessel> result = new ArrayList<>();

    result.add(new GruppenErgebnistabelleSpaltenSchluessel(getSystemGruppeUngueltige(ergebnisDaten), stimmenart));
    result.add(new GruppenErgebnistabelleSpaltenSchluessel(getSystemGruppeGueltige(ergebnisDaten), stimmenart));

    List<Gruppe> relevanteGruppenSortiert;

    if (Stimmenart.DIREKTSTIMME.equals(stimmenart)) {
      relevanteGruppenSortiert = ergebnisDaten.getGruppenMitDirektkandidatZugelassenSortiertNachPositionAufGebiet(wahlgebiet);
    } else if (Stimmenart.LISTENSTIMME.equals(stimmenart)) {
      relevanteGruppenSortiert = ergebnisDaten.getGruppenMitListeZugelassenSortiertNachPositionAufGebiet(wahlgebiet);
    } else {
      throw new IllegalStateException("Stimmenart nicht unterstützt: " + stimmenart);
    }

    for (Gruppe gruppe : relevanteGruppenSortiert) {
      result.add(new GruppenErgebnistabelleSpaltenSchluessel(gruppe, stimmenart));
    }

    return result;
  }

  private Gruppe getSystemGruppeUngueltige(ErgebnisDaten ergebnisDaten) {
    return findSystemGruppe(ergebnisDaten.getSystemgruppen(), SystemGruppe.UNGUELTIGE);
  }

  private Gruppe getSystemGruppeGueltige(ErgebnisDaten ergebnisDaten) {
    return findSystemGruppe(ergebnisDaten.getSystemgruppen(), SystemGruppe.GUELTIGE);
  }

  private Gruppe findSystemGruppe(List<Gruppe> gruppen, SystemGruppe systemGruppe) {
    for (Gruppe gruppe : gruppen) {
      if (systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppe, systemGruppe)) {
        return gruppe;
      }
    }

    throw new IllegalStateException("Keine SystemGruppe." + systemGruppe + "!");
  }
}
