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

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.business.wahlvorschlag.entity.ReferendumOption;
import de.ivu.elect.core.exchange.stimmen.csv.AbstractStimmenMapper;

public class ReferendumErgebnistabelleCsvDTOFactory extends AbstractStimmenMapper {

  public ReferendumErgebnistabelleCsvDTOFactory() {
    super();
  }

  public ReferendumErgebnistabelleCsvDTO createReferendumErgebnistabelleCsvDTO(ReferendumErgebnistabelleZeilenSchluessel schluessel, ErgebnisDaten ergebnisDaten,
      Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemGruppenStimmenByGebietUndGruppe) {
    ReferendumErgebnistabelleCsvDTO dto = new ReferendumErgebnistabelleCsvDTO(schluessel);

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

    addReferendumErgebnistabelleCsvDTO(dto, schluessel, ergebnisDaten, systemGruppenStimmenByGebietUndGruppe);

    return dto;
  }

  protected void addReferendumErgebnistabelleCsvDTO(ReferendumErgebnistabelleCsvDTO dto, ReferendumErgebnistabelleZeilenSchluessel zeile, ErgebnisDaten ergebnisDaten,
      Map<Gebiet, Map<Gruppe, List<Stimmen>>> systemGruppeStimmenByGebietUndGruppe) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = zeile.getGebiet();

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

      Stimmabgabeart stimmabgabeart = zeile.getStimmabgabeart();
      Stimmenart stimmenart = spalte.getStimmenart();

      if (gruppe.isSystemGruppe()) {
        long stimmenSummeFuerSystemGruppe = getStimmenSummeFuerSystemGruppe(wahldurchfuehrungsgebiet, stimmabgabeart, stimmenart, gruppe, systemGruppeStimmenByGebietUndGruppe);

        Gruppe referendumVorlage = spalte.getReferendumVorlage();

        // NOTE, dass bei Referenden die Gültigen und Ungültigen als Zwischensummen erfasst werden und nicht an den SystemGruppen hängen,
        // cf. AbstractStimmeingabeReferendumVorlageStimmenModelBuilder#createStimmeingabeReferendumOptionStimmen!!!
        if (gruppe.isGruppeEqualsSystemGruppe(SystemGruppe.GUELTIGE, systemGruppeEigenschaften)) {
          stimmenSummeFuerSystemGruppe = ergebnisDaten.getErgebnisDatenReferendumOptionen().getStimmenFuerReferendumOptionenGueltig(referendumVorlage, wahldurchfuehrungsgebiet);
        } else if (gruppe.isGruppeEqualsSystemGruppe(SystemGruppe.UNGUELTIGE, systemGruppeEigenschaften)) {
          stimmenSummeFuerSystemGruppe = ergebnisDaten.getErgebnisDatenReferendumOptionen().getStimmenFuerReferendumOptionenUngueltig(referendumVorlage, wahldurchfuehrungsgebiet);
        }

        dto.addReferendumStimmen(spalte, stimmenSummeFuerSystemGruppe);
      } else {
        dto.addReferendumStimmen(spalte, getStimmenSummeFuerReferendumOption(wahldurchfuehrungsgebiet, stimmabgabeart, stimmenart, spalte.getReferendumOption(), 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 getStimmenSummeFuerReferendumOption(Gebiet gebiet, Stimmabgabeart stimmabgabeart, Stimmenart stimmenart, ReferendumOption referendumOption, ErgebnisDaten ergebnisDaten) {
    Collection<Stimmen> stimmen = new ArrayList<>();

    StimmenPool stimmenPool = ergebnisDaten.getStimmen();

    if (stimmenart.isReferendumStimmenart()) {
      for (Gebiet erfassungsgebiet : Stimmabgabeart.getUntergeordneteErfassungsgebieteNachStimmabgabeart(gebiet, stimmabgabeart, getErfassteGebiete(stimmabgabeart))) {
        Stimmen stimmenInGebiet = stimmenPool.getReferendumOptionStimmenAufErfassungsgebiet(referendumOption, 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<ReferendumErgebnistabelleSpaltenSchluessel> getRelevanteSpaltenSchluesselSortiert(Wahldurchfuehrungsgebiet wahlgebiet, ErgebnisDaten ergebnisDaten) {
    List<ReferendumErgebnistabelleSpaltenSchluessel> result = new ArrayList<>();

    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

    if (wahldurchfuehrung.isWahlMitReferendengebieten()) {
      result.addAll(getRelevanteSpaltenSchluesselSortiert(wahlgebiet, ergebnisDaten, Stimmenart.REFERENDUM_STIMME));
    }

    return result;
  }

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

    List<Gruppe> referendumVorlagen;

    if (Stimmenart.REFERENDUM_STIMME.equals(stimmenart)) {
      referendumVorlagen = ergebnisDaten.getReferendumVorlagenSortiertNachPositionAufGebiet(wahldurchfuehrungsgebiet);
    } else {
      throw new IllegalStateException("Stimmenart nicht unterstützt: " + stimmenart);
    }

    for (Gruppe referendumVorlage : referendumVorlagen) {
      result.add(new ReferendumErgebnistabelleSpaltenSchluessel(referendumVorlage, stimmenart, getSystemGruppeUngueltige(ergebnisDaten)));
      result.add(new ReferendumErgebnistabelleSpaltenSchluessel(referendumVorlage, stimmenart, getSystemGruppeGueltige(ergebnisDaten)));

      for (ReferendumOption referendumOption : ergebnisDaten.getReferendumOptionenSortiertNachPosition(referendumVorlage)) {
        result.add(new ReferendumErgebnistabelleSpaltenSchluessel(referendumVorlage, stimmenart, referendumOption));
      }
    }

    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 + "!");
  }
}
