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

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

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
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.Direktkandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;


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

public class ErgebnisDatenDirektkandidaturen extends AbstractErgebnisDaten {

  private final StimmenPool stimmen;

  private final StimmenKumulierer stimmenKumulierer;

  private final Map<Gruppe, List<Direktkandidatur>> direktkandidaturenNachGruppe;

  private final Map<Gruppe, List<Direktkandidat>> direktkandidatenNachGruppe;

  private final List<Direktkandidatur> direktkandidaturen;

  private final Map<Wahldurchfuehrungsgebiet, List<Direktkandidatur>> direktkandidaturenFuerGebietUnsortiert = new HashMap<>();

  public ErgebnisDatenDirektkandidaturen(Function<Wahldurchfuehrungsgebiet, List<Wahldurchfuehrungsgebiet>> untergeordneteGebiete, StimmenPool stimmen, StimmenKumulierer stimmenKumulierer,
      Map<Gruppe, List<Direktkandidatur>> direktkandidaturenNachGruppe, List<Direktkandidatur> direktkandidaturen, Supplier<Set<Gebiet>> erfassteGebiete) {
    super(untergeordneteGebiete, erfassteGebiete);

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

    this.direktkandidaturenNachGruppe = direktkandidaturenNachGruppe;

    this.direktkandidatenNachGruppe = new HashMap<>(direktkandidaturenNachGruppe.size() * 2 + 1);
    for (Map.Entry<Gruppe, List<Direktkandidatur>> entry : direktkandidaturenNachGruppe.entrySet()) {
      for (Direktkandidatur direktkandidatur : entry.getValue()) {
        direktkandidatenNachGruppe.computeIfAbsent(entry.getKey(), x -> new ArrayList<>(entry.getValue().size())).add(direktkandidatur.getDirektkandidat());
      }
    }

    this.direktkandidaturen = direktkandidaturen;
  }

  private StimmenPool getStimmen() {
    return stimmen;
  }

  private final Map<PolitischeGruppeUndGebietUndStimmabgabeart, Map<Direktkandidatur, Long>> stimmenFuerDirektkandidaturen = new HashMap<>();

  private final BestimmeStimmen<Direktkandidatur, PolitischeGruppeUndGebietUndStimmabgabeart> stimmenFuerDirektkandidaturenBestimmeStimmen = new BestimmeStimmen<Direktkandidatur, PolitischeGruppeUndGebietUndStimmabgabeart>() {

    @Override
    public Long ermittleStimmen(Direktkandidatur direktkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
      return stimmenKumulierer.getSummeStimmenFuerDirektkandidaturAufGebietUndDarunter(direktkandidatur, wahldurchfuehrungsgebiet, getStimmen(),
          direktkandidatenNachGruppe::get);
    }

    @Override
    public Map<Direktkandidatur, Long> getErgebnisUntergebiet(PolitischeGruppeUndGebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return collectStimmenPerDirektkandidatur(gebietUndStimmabgabeart);
    }

    @Override
    public List<Direktkandidatur> getTargetAufGebiet(PolitischeGruppeUndGebietUndStimmabgabeart gebietUndStimmabgabeart) {
      return getDirektkandidaturenFuerGruppeAufGebiet(gebietUndStimmabgabeart.getPolitischeGruppe(), gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    }
  };

  public List<Direktkandidatur> getDirektkandidaturenAufStimmzettelFuerGruppeAufGebiet(Gruppe gruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getDirektkandidaturenFuerGruppeAufGebiet(gruppe, wahldurchfuehrungsgebiet).stream().filter(Direktkandidatur::isAufStimmzettel).collect(toList());
  }

  /**
   * NOTE, dass ALLE, also auch NICHT ZUGELASSENE Kandidaturen, enthalten sind!!!
   *
   * @see ErgebnisDatenDirektkandidaturen#getDirektkandidaturenAufStimmzettelFuerGruppeAufGebiet
   */
  public List<Direktkandidatur> getDirektkandidaturenFuerGruppeAufGebiet(Gruppe gruppe, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    List<Direktkandidatur> result = new ArrayList<>(direktkandidaturenNachGruppe.getOrDefault(gruppe, emptyList()));
    result.retainAll(getDirektkandidaturenFuerGebietUnsortiert(wahldurchfuehrungsgebiet));

    return result;
  }

  @Nullable
  public Direktkandidatur getDirektkandidaturGewaehltAufGebiet(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebnisart ergebnisart) {
    List<Direktkandidatur> direktkandidaturen = getDirektkandidaturenFuerGebietUnsortiert(wahldurchfuehrungsgebiet);

    for (Direktkandidatur direktkandidatur : direktkandidaturen) {
      List<Gewaehlt> gewaehlts = direktkandidatur.getGewaehlteByErgebnisart(ergebnisart);

      if (!gewaehlts.isEmpty()) {
        for (Gewaehlt gewaehlt : gewaehlts) {
          if (gewaehlt.getGewaehltStatus().isGewaehlt()) {
            return direktkandidatur;
          }
        }
      }
    }

    return null;
  }

  public long getStimmenFuerDirektkandidatur(Direktkandidatur direktkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getStimmenFuerDirektkandidatur(direktkandidatur, wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT);
  }

  public long getStimmenFuerDirektkandidatur(Direktkandidatur direktkandidatur, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Stimmabgabeart stimmabgabeart) {
    return getStimmenFuerDirektkandidatur(direktkandidatur, new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, stimmabgabeart));
  }

  public long getStimmenFuerDirektkandidatur(Direktkandidatur direktkandidatur, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    PolitischeGruppe politischeGruppe = direktkandidatur.getGruppe().castTo(PolitischeGruppe.class).getEntity();

    return notNull(collectStimmenPerDirektkandidatur(new PolitischeGruppeUndGebietUndStimmabgabeart(politischeGruppe, gebietUndStimmabgabeart)).get(direktkandidatur));
  }

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

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

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

  public Map<Direktkandidatur, Long> collectStimmenPerDirektkandidatur(PolitischeGruppeUndGebietUndStimmabgabeart politischeGruppeUndGebietUndStimmabgabeart) {
    return bestimmeStimmenOderSummiereAufUntergebieten(politischeGruppeUndGebietUndStimmabgabeart, stimmenFuerDirektkandidaturen, stimmenFuerDirektkandidaturenBestimmeStimmen);
  }

  private List<Direktkandidatur> getDirektkandidaturenFuerGebietUnsortiert(Wahldurchfuehrungsgebiet gebietobject) {
    List<Direktkandidatur> result = direktkandidaturenFuerGebietUnsortiert.get(gebietobject);

    if (result == null) {
      result = new ArrayList<>();

      direktkandidaturenFuerGebietUnsortiert.put(gebietobject, result);

      for (Direktkandidatur direktkandidatur : direktkandidaturen) {
        Gebiet gebietDesDirektkandidaten = direktkandidatur.getGebiet();
        if (gebietDesDirektkandidaten.isInGebietshierarchieUntergeordnetesGebietVon(gebietobject) || gebietobject.isInGebietshierarchieUntergeordnetesGebietVon(gebietDesDirektkandidaten)) {
          result.add(direktkandidatur);
        }
      }
    }

    return result;
  }
}
