
package de.ivu.elect.business.sitzberechnung.entity;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.control.StimmenKumulierer;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.control.StimmergebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;

public class Sitzberechnungseingabe<Konfiguration extends SitzberechnungsalgorithmusKonfiguration> {

  private final StimmergebnisDaten stimmergebnisDaten;

  private final AbstractWahlParameterLoader wahlParameterLoader;

  private final Ergebnis ergebnis;

  private final List<ErgebnisKonflikt> konflikte;

  private final Konfiguration konfiguration;

  private final DirektkandidaturStimmen direktkandidaturStimmen;

  public Sitzberechnungseingabe(StimmergebnisDaten stimmergebnisDaten, Ergebnis ergebnis, StimmenPool stimmen, List<ErgebnisKonflikt> konflikte, AbstractWahlParameterLoader wahlParameterLoader,
      Konfiguration konfiguration, StimmenKumulierer stimmenKumulierer) {
    this(stimmergebnisDaten, ergebnis, konflikte, wahlParameterLoader, konfiguration, new DirektkandidaturStimmen(stimmen, stimmenKumulierer,
        g->stimmergebnisDaten.getDirektkandidatenNachGruppe().getOrDefault(g, Collections.emptyList())));
  }

  public Sitzberechnungseingabe(Sitzberechnungseingabe<Konfiguration> original, List<ErgebnisKonflikt> andereKonflikte) {
    this(original.getStimmergebnisDaten(), original.getErgebnis(), andereKonflikte, original.getWahlParameterLoader(), original.getKonfiguration(), original.getDirektkandidaturStimmen());
  }

  Sitzberechnungseingabe(StimmergebnisDaten stimmergebnisDaten, Ergebnis ergebnis, List<ErgebnisKonflikt> konflikte, AbstractWahlParameterLoader wahlParameterLoader, Konfiguration konfiguration,
      DirektkandidaturStimmen direktkandidaturStimmen) {
    super();

    this.stimmergebnisDaten = stimmergebnisDaten;

    this.wahlParameterLoader = wahlParameterLoader;

    this.ergebnis = ergebnis;
    this.konflikte = konflikte;
    this.konfiguration = konfiguration;
    this.direktkandidaturStimmen = direktkandidaturStimmen;
  }

  public StimmergebnisDaten getStimmergebnisDaten() {
    return stimmergebnisDaten;
  }

  public AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  public Ergebnis getErgebnis() {
    return ergebnis;
  }

  public List<ErgebnisKonflikt> getKonflikte() {
    return konflikte;
  }

  public Konfiguration getKonfiguration() {
    return konfiguration;
  }

  public Long getAnzahlStimmenFuerDirektkandidaturKumuliertBisGebietAufDemKandidaturAntritt(Direktkandidatur direktkandidatur) {
    return getDirektkandidaturStimmen().getAnzahlStimmenFuerDirektkandidaturKumuliertBisGebietAufDemKandidaturAntritt(direktkandidatur);
  }

  public DirektkandidaturStimmen getDirektkandidaturStimmen() {
    return direktkandidaturStimmen;
  }

  /**
   * Zweck dieser inneren Klasse ist, dass man sie mocken kann und der gemockte Code auch bei einer mit <code>new
   * Sitzberechnungseingabe(sitzberechnungseingabe, andereKonflikte)</code> kopierten Sitzberechnungseingabe weiterhin verwendet wird.
   */
  public static class DirektkandidaturStimmen {

    private final StimmenPool stimmen;

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

    private final StimmenKumulierer stimmenKumulierer;
    private final Function<Gruppe, List<Direktkandidat>> direktKandidatenDerGruppe;

    DirektkandidaturStimmen(StimmenPool stimmen, StimmenKumulierer stimmenKumulierer, @Nullable Function<Gruppe, List<Direktkandidat>> direktKandidatenDerGruppe) {
      super();

      this.stimmen = stimmen;
      this.stimmenKumulierer = stimmenKumulierer;
      this.direktKandidatenDerGruppe = direktKandidatenDerGruppe;
    }

    public Long getAnzahlStimmenFuerDirektkandidaturKumuliertBisGebietAufDemKandidaturAntritt(Direktkandidatur direktkandidatur) {
      Long result = anzahlStimmenFuerDirektkandidaturKumuliertBisGebietAufDemKandidaturAntritt.get(direktkandidatur);

      if (result == null) {
        result = stimmenKumulierer.getSummeStimmenFuerDirektkandidaturAufGebietUndDarunter(direktkandidatur, direktkandidatur.getGebiet(), stimmen, direktKandidatenDerGruppe);
        anzahlStimmenFuerDirektkandidaturKumuliertBisGebietAufDemKandidaturAntritt.put(direktkandidatur, result);
      }

      return result;
    }
  }
}
