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

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
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.PolitischeGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.internet.control.PolitischeGruppeUndGebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

public class ErgebnisDatenListenkandidaturen extends AbstractErgebnisDaten {

  private final StimmergebnisDaten stimmergebnisDaten;

  private final StimmenPool stimmen;

  private final StimmenKumulierer stimmenKumulierer;

  private final Map<Gruppe, List<Listenkandidatur>> listenkandidaturenNachGruppe;

  public ErgebnisDatenListenkandidaturen(StimmergebnisDaten stimmergebnisDaten, StimmenPool stimmen, StimmenKumulierer stimmenKumulierer,
      Map<Gruppe, List<Listenkandidatur>> listenkandidaturenNachGruppe, Supplier<Set<Gebiet>> erfassteGebiete) {
    super(stimmergebnisDaten::getUntergeordneteGebiete, erfassteGebiete);

    this.stimmergebnisDaten = stimmergebnisDaten;

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

    this.listenkandidaturenNachGruppe = listenkandidaturenNachGruppe;
  }

  private StimmenPool getStimmen() {
    return stimmen;
  }

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Listenkandidatur, Long>> stimmenFuerListenkandidaturen = new HashMap<>();

  private final BestimmeStimmen<Listenkandidatur, PolitischeGruppeUndGebietUndStimmabgabeart> stimmenFuerListenkandidaturenBestimmeStimmen = new BestimmeStimmen<Listenkandidatur, PolitischeGruppeUndGebietUndStimmabgabeart>() {

    @Override
    public Long ermittleStimmen(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeStimmenFuerListenkandidaturAufGebietUndDarunter(listenkandidatur, wahldurchfuehrungsgebiet, getStimmen());
    }

    @Override
    public Map<Listenkandidatur, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return collectStimmenPerListenkandidatur(politischeGruppeUndGebietUndStimmabgabeart);
    }

    @Override
    public List<Listenkandidatur> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return getListenkandidaturenFuerGruppeAufGebiet(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe(), politischeGruppeUndGebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    }
  };

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Listenkandidatur, Long>> stimmenFuerListenkandidaturenNichtErgebnisrelevant = new HashMap<>();

  private final BestimmeStimmen<Listenkandidatur, PolitischeGruppeUndGebietUndStimmabgabeart> stimmenFuerListenkandidaturenBestimmeStimmenNichtErgebnisrelevant = new BestimmeStimmen<Listenkandidatur, PolitischeGruppeUndGebietUndStimmabgabeart>() {

    @Override
    public Long ermittleStimmen(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeStimmenFuerListenkandidaturNichtErgebnisrelevantAufGebietUndDarunter(listenkandidatur, wahldurchfuehrungsgebiet, getStimmen());
    }

    @Override
    public Map<Listenkandidatur, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return collectStimmenPerListenkandidaturNichtErgebnisrelevant(politischeGruppeUndGebietUndStimmabgabeart);
    }

    @Override
    public List<Listenkandidatur> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
      return getListenkandidaturenFuerGruppeAufGebiet(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe(), politischeGruppeUndGebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    }
  };

  public List<Listenkandidatur> getListenkandidaturenAufStimmzettelFuerGruppeAufGebiet(Gruppe gruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getListenkandidaturenFuerGruppeAufGebiet(gruppe, wahldurchfuehrungsgebiet).stream().filter(Listenkandidatur::isAufStimmzettel).collect(toList());
  }

  /**
   * NOTE, dass ALLE, also auch NICHT ZUGELASSENE Kandidaturen, enthalten sind!!!
   *
   * @see ErgebnisDatenListenkandidaturen#getListenkandidaturenAufStimmzettelFuerGruppeAufGebiet
   */
  public List<Listenkandidatur> getListenkandidaturenFuerGruppeAufGebiet(Gruppe gruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    List<Listenkandidatur> result = new ArrayList<>();

    for (Listenkandidatur listenkandidatur : listenkandidaturenNachGruppe.getOrDefault(gruppe, emptyList())) {
      Gebiet kandidaturGebiet = listenkandidatur.getWahlvorschlag().getGebiet();

      if (wahldurchfuehrungsgebiet.equals(kandidaturGebiet) || kandidaturGebiet.isUntergebietVon(wahldurchfuehrungsgebiet) || wahldurchfuehrungsgebiet.isUntergebietVon(kandidaturGebiet)) {
        result.add(listenkandidatur);
      }
    }

    return result;
  }

  /**
   * @return Anzahl der Stimmen für die gesamte Liste, das heißt, die verbleibenden Listenstimmen nach Abzug aller Stimmen für einzelne Listenkandidaturen, erfasst bspw. als Zwischensumme bei der
   *         bayrischen LTW auf der Erfassungsmaske für Listenkandidaturen.
   */
  public long getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  /**
   * @return Anzahl der Stimmen für die gesamte Liste, das heißt, die verbleibenden Listenstimmen nach Abzug aller Stimmen für einzelne Listenkandidaturen, erfasst bspw. als Zwischensumme bei der
   *         bayrischen LTW auf der Erfassungsmaske für Listenkandidaturen.
   */
  public long getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(new PolitischeGruppeUndGebietUndStimmabgabeart(politischeGruppe, gebietUndStimmabgabeart));
  }

  /**
   * @return Anzahl der Stimmen für die gesamte Liste, das heißt, die verbleibenden Listenstimmen nach Abzug aller Stimmen für einzelne Listenkandidaturen, erfasst bspw. als Zwischensumme bei der
   *         bayrischen LTW auf der Erfassungsmaske für Listenkandidaturen.
   */
  public long getStimmenFuerListeAbzueglichStimmenFuerAlleListenkandidaturen(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
    Long anzahlListenstimmen = stimmergebnisDaten.getZweitstimmenAktuell(politischeGruppeUndGebietUndStimmabgabeart.getPolitischeGruppe(), politischeGruppeUndGebietUndStimmabgabeart);

    if (anzahlListenstimmen != null) {
      return anzahlListenstimmen - getStimmenFuerListenkandidaturen(politischeGruppeUndGebietUndStimmabgabeart);
    }

    return 0L;
  }

  public long getStimmenFuerListenkandidaturen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerListenkandidaturen(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerListenkandidaturen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerListenkandidaturen(new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerListenkandidaturen(GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return stimmergebnisDaten.getListenInGebietshierarchie(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet()).stream() //
        .map(liste -> getStimmenFuerListenkandidaturen(liste, gebietUndStimmabgabeart)) //
        .filter(Objects::nonNull).mapToLong(Long::longValue).sum();
  }

  public long getStimmenFuerListenkandidaturen(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerListenkandidaturen(liste, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerListenkandidaturen(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerListenkandidaturen(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerListenkandidaturen(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    PolitischeGruppe politischeGruppe = liste.getGruppe().castTo(PolitischeGruppe.class).getEntity();

    return notNull(getStimmenFuerListenkandidaturen(new PolitischeGruppeUndGebietUndStimmabgabeart(politischeGruppe, gebietUndStimmabgabeart)));
  }

  public long getStimmenFuerListenkandidaturen(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
    Map<Listenkandidatur, Long> stimmenPerListenkandidatur = collectStimmenPerListenkandidatur(politischeGruppeUndGebietUndStimmabgabeart);

    Long gesamt = 0L;

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

    return gesamt;
  }

  public long getStimmenFuerListenkandidatur(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerListenkandidatur(listenkandidatur, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerListenkandidatur(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerListenkandidatur(listenkandidatur, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerListenkandidatur(Listenkandidatur listenkandidatur, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return notNull(collectStimmenPerListenkandidatur(listenkandidatur.getListe(), gebietUndStimmabgabeart).get(listenkandidatur));
  }

  public long getStimmenFuerListenkandidaturNichtErgebnisrelevant(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerListenkandidaturNichtErgebnisrelevant(listenkandidatur, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerListenkandidaturNichtErgebnisrelevant(Listenkandidatur listenkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerListenkandidaturNichtErgebnisrelevant(listenkandidatur, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerListenkandidaturNichtErgebnisrelevant(Listenkandidatur listenkandidatur, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    PolitischeGruppe politischeGruppe = listenkandidatur.getGruppe().castTo(PolitischeGruppe.class).getEntity();

    return notNull(collectStimmenPerListenkandidaturNichtErgebnisrelevant(politischeGruppe, gebietUndStimmabgabeart).get(listenkandidatur));
  }

  /**
   * @param liste
   *          die Liste, deren Stimmen gesucht werden
   * @param gebiet
   *          es werden die Stimmen auf diesem Gebiet einschließlich der Untergebiete kumuliert
   * @return für alle Listenkandidaturen der Liste die Anzahl Stimmen im Gebiet als Long (auch bei 0 Stimmen)
   */
  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return collectStimmenPerListenkandidatur(liste, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(Liste liste, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return collectStimmenPerListenkandidatur(liste, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(Liste liste, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    Map<Listenkandidatur, Long> result = new HashMap<>();

    PolitischeGruppe politischeGruppe = liste.getGruppe().castTo(PolitischeGruppe.class).getEntity();
    Map<Listenkandidatur, Long> stimmenPerListenkandidatur = collectStimmenPerListenkandidatur(politischeGruppe, gebietUndStimmabgabeart);

    for (Listenkandidatur listenkandidatur : liste.getListenkandidaturen()) {
      result.put(listenkandidatur, notNull(stimmenPerListenkandidatur.get(listenkandidatur)));
    }

    return result;
  }

  /**
   * NOTE: Das Ergebnis kann auch Listenkandidaturen umfassen, die auf dem wahldurchfuehrungsgebiet nicht antreten.
   */
  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return collectStimmenPerListenkandidatur(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return collectStimmenPerListenkandidatur(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return collectStimmenPerListenkandidatur(new PolitischeGruppeUndGebietUndStimmabgabeart(politischeGruppe, gebietUndStimmabgabeart));
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidatur(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
    return bestimmeStimmenOderSummiereAufUntergebieten(politischeGruppeUndGebietUndStimmabgabeart, stimmenFuerListenkandidaturen, stimmenFuerListenkandidaturenBestimmeStimmen);
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidaturNichtErgebnisrelevant(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return collectStimmenPerListenkandidaturNichtErgebnisrelevant(politischeGruppe, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidaturNichtErgebnisrelevant(PolitischeGruppe politischeGruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      Stimmabgabeart stimmabgabeart) {
    return collectStimmenPerListenkandidaturNichtErgebnisrelevant(politischeGruppe, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidaturNichtErgebnisrelevant(PolitischeGruppe politischeGruppe, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    return collectStimmenPerListenkandidaturNichtErgebnisrelevant(new PolitischeGruppeUndGebietUndStimmabgabeart(politischeGruppe, gebietUndStimmabgabeart));
  }

  public Map<Listenkandidatur, Long> collectStimmenPerListenkandidaturNichtErgebnisrelevant(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
    return bestimmeStimmenOderSummiereAufUntergebieten(politischeGruppeUndGebietUndStimmabgabeart, stimmenFuerListenkandidaturenNichtErgebnisrelevant,
        stimmenFuerListenkandidaturenBestimmeStimmenNichtErgebnisrelevant);
  }
}
