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

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.validation.constraints.NotNull;

import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.internet.control.GruppeUndGebietUndStimmabgabeart;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeDocumentReferendumErgebnisDaten;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;

public class ErgebnisDatenReferendumOptionen extends AbstractErgebnisDaten implements StimmeingabeDocumentReferendumErgebnisDaten<Wahldurchfuehrungsgebiet> {

  private final StimmergebnisDaten stimmergebnisDaten;

  private final StimmenPool stimmen;

  private final StimmenKumulierer stimmenKumulierer;

  private final Map<Gruppe, List<ReferendumOption>> referendumOptionenNachGruppe;

  public ErgebnisDatenReferendumOptionen(StimmergebnisDaten stimmergebnisDaten, StimmenPool stimmen, StimmenKumulierer stimmenKumulierer,
      Map<Gruppe, List<ReferendumOption>> referendumOptionenNachGruppe) {
    super(stimmergebnisDaten::getUntergeordneteGebiete, stimmergebnisDaten::getErfassteGebiete);

    this.stimmergebnisDaten = stimmergebnisDaten;

    this.stimmen = stimmen;
    this.stimmenKumulierer = stimmenKumulierer;

    this.referendumOptionenNachGruppe = referendumOptionenNachGruppe;
  }

  private StimmenPool getStimmen() {
    return stimmen;
  }

  private final Map<GruppeUndGebietUndStimmabgabeart, Map<ReferendumOption, Long>> stimmenFuerReferendumOptionen = new HashMap<>();

  private final BestimmeStimmen<ReferendumOption, GruppeUndGebietUndStimmabgabeart> stimmenFuerReferendumOptionenBestimmeStimmen = new BestimmeStimmen<ReferendumOption, GruppeUndGebietUndStimmabgabeart>() {

    @Override
    public Long ermittleStimmen(ReferendumOption referendumOption, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeStimmenFuerReferendumOptionAufGebietUndDarunter(referendumOption, wahldurchfuehrungsgebiet, getStimmen());
    }

    @Override
    public Map<ReferendumOption, Long> getErgebnisUntergebiet(GruppeUndGebietUndStimmabgabeart referendumVorlageUndGebietUndStimmabgabeart) {
      return collectStimmenPerReferendumOption(referendumVorlageUndGebietUndStimmabgabeart);
    }

    @Override
    public List<ReferendumOption> getTargetAufGebiet(GruppeUndGebietUndStimmabgabeart referendumVorlageUndGebietUndStimmabgabeart) {
      return getReferendumOptionenFuerReferendumVorlage(referendumVorlageUndGebietUndStimmabgabeart.getGruppe());
    }
  };

  public List<ReferendumOption> getReferendumOptionenFuerReferendumVorlage(Gruppe referendumVorlage) {
    List<ReferendumOption> result = referendumOptionenNachGruppe.get(referendumVorlage);

    if (result == null) {
      result = Collections.emptyList();
    }

    return result;
  }

  public long getStimmenFuerReferendumOptionen(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerReferendumOptionen(referendumVorlage, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerReferendumOptionen(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerReferendumOptionen(referendumVorlage, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerReferendumOptionen(Gruppe referendumVorlage, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    Map<ReferendumOption, Long> stimmenPerReferendumOption = collectStimmenPerReferendumOption(referendumVorlage, gebietUndStimmabgabeart);

    Long gesamt = 0L;

    for (Long aLong : stimmenPerReferendumOption.values()) {
      gesamt += aLong;
    }

    return gesamt;
  }

  @NotNull
  @Override
  public Long getStimmenFuerReferendumOptionenUngueltig(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerReferendumOptionenUngueltig(referendumVorlage, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerReferendumOptionenUngueltig(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerReferendumOptionenUngueltig(referendumVorlage, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerReferendumOptionenUngueltig(Gruppe referendumVorlage, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return Math.max(0, getStimmenFuerReferendumOptionenAbgegeben(referendumVorlage, gebietUndStimmabgabeart) - getStimmenFuerReferendumOptionenGueltig(referendumVorlage, gebietUndStimmabgabeart));
  }

  @NotNull
  @Override
  public Long getStimmenFuerReferendumOptionenGueltig(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerReferendumOptionenGueltig(referendumVorlage, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerReferendumOptionenGueltig(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerReferendumOptionenGueltig(referendumVorlage, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerReferendumOptionenGueltig(Gruppe referendumVorlage, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    long result = 0;

    for (ReferendumOption referendumOption : getReferendumOptionenFuerReferendumVorlage(referendumVorlage)) {
      result += getStimmenFuerReferendumOption(referendumOption, gebietUndStimmabgabeart);
    }

    return result;
  }

  @NotNull
  @Override
  public Long getStimmenFuerReferendumOptionenAbgegeben(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerReferendumOptionenAbgegeben(referendumVorlage, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerReferendumOptionenAbgegeben(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerReferendumOptionenAbgegeben(referendumVorlage, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  // ELECT-6385
  public long getStimmenFuerReferendumOptionenAbgegeben(Gruppe referendumVorlage, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return stimmergebnisDaten.getErgebnisDatenWaehler().getSummeWaehlerGesamt(gebietUndStimmabgabeart);
  }

  @NotNull
  @Override
  public Long getStimmenFuerReferendumOption(ReferendumOption referendumOption, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerReferendumOption(referendumOption, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerReferendumOption(ReferendumOption referendumOption, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerReferendumOption(referendumOption, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerReferendumOption(ReferendumOption referendumOption, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return notNull(collectStimmenPerReferendumOption(new GruppeUndGebietUndStimmabgabeart(referendumOption.getGruppe(), gebietUndStimmabgabeart)).get(referendumOption));
  }

  public Map<ReferendumOption, Long> collectStimmenPerReferendumOption(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return collectStimmenPerReferendumOption(referendumVorlage, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public Map<ReferendumOption, Long> collectStimmenPerReferendumOption(Gruppe referendumVorlage, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return collectStimmenPerReferendumOption(referendumVorlage, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<ReferendumOption, Long> collectStimmenPerReferendumOption(Gruppe referendumVorlage, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return filterFuerReferendumVorlage(referendumVorlage, collectStimmenPerReferendumOption(new GruppeUndGebietUndStimmabgabeart(referendumVorlage, gebietUndStimmabgabeart)));
  }

  public Map<ReferendumOption, Long> collectStimmenPerReferendumOption(GruppeUndGebietUndStimmabgabeart referendumVorlageUndGebietUndStimmabgabeart) {
    return filterFuerReferendumVorlage(referendumVorlageUndGebietUndStimmabgabeart.getGruppe(),
        bestimmeStimmenOderSummiereAufUntergebieten(referendumVorlageUndGebietUndStimmabgabeart, stimmenFuerReferendumOptionen, stimmenFuerReferendumOptionenBestimmeStimmen));
  }

  private Map<ReferendumOption, Long> filterFuerReferendumVorlage(Gruppe referendumVorlage, Map<ReferendumOption, Long> bestimmeStimmenOderSummiereAufUntergebieten) {
    Map<ReferendumOption, Long> result = new HashMap<>();

    for (Entry<ReferendumOption, Long> entry : bestimmeStimmenOderSummiereAufUntergebieten.entrySet()) {
      ReferendumOption referendumOption = entry.getKey();
      if (referendumOption.getGruppe().equals(referendumVorlage)) {
        result.put(referendumOption, entry.getValue());
      }
    }

    return result;
  }
}
