
package de.ivu.elect.business.sitzberechnung.control.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;

import de.ivu.elect.business.ergebnis.control.StimmergebnisDaten;
import de.ivu.elect.business.ergebnis.entity.AbstractGewaehlt;
import de.ivu.elect.business.ergebnis.entity.DHondtQuotientListenImGebiet;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktArt;
import de.ivu.elect.business.ergebnis.entity.ErgebnisLogEintragArt;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatus;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebniskonflikt.control.util.ErgebnisKonfliktLoeser;
import de.ivu.elect.business.ergebniskonflikt.entity.AbstractKandidaturenErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.GebieteAuswaehlenErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.ListenAuswaehlenErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.PolitischeGruppenAuswaehlenErgebnisKonfliktCustomData;
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.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.sitzberechnung.control.dhondt.DHondtQuotient;
import de.ivu.elect.business.sitzberechnung.control.verteilung.ListenTeilnehmer;
import de.ivu.elect.business.sitzberechnung.control.verteilung.ListenverbindungTeilnehmer;
import de.ivu.elect.business.sitzberechnung.control.verteilung.Teilnehmer;
import de.ivu.elect.business.sitzberechnung.entity.HinweisAnVariantenberechner;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahlvorschlag.control.WahlvorschlagByStimmzettelPositionComparator;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.jee.entity.customdata.CustomData;
import de.ivu.elect.jee.entity.customdata.EmptyCustomData;
import de.ivu.elect.jee.faces.Messages;

/**
 * Helper für die Sitzberechnung bei einfachen Wahlen. Hilft bei der Beschaffung der nötigen Daten und beim Sammeln der Ergebnisse und Erzeugen des {@link Sitzberechnungsergebnis}ses.
 */
public class SitzberechnungProGebietHelper {

  private static final String MESSAGE_KEY_LOSENTSCHEID_ERFORDERLICH = "sitzberechnung.algorithmus.protokoll.losentscheid.erforderlich";

  private final StimmergebnisDaten stimmergebnisDaten;

  private final AbstractWahlParameterLoader wahlParameterLoader;

  private final Sitzberechnungseingabe< ? > sitzberechnungseingabe;

  private final SitzberechnungLogger sitzberechnungLogger;

  private final Wahldurchfuehrungsgebiet gebiet;

  private final List<Sitze> sitze = new ArrayList<>();
  private final List<Gewaehlt> gewaehlte = new ArrayList<>();
  private final List<ErgebnisKonflikt> konflikte = new ArrayList<>();
  private final Map<ErgebnisKonflikt, HinweisAnVariantenberechner> hinweiseAnVariantenberechner = new HashMap<>();
  private final Map<ErgebnisKonflikt, ErgebnisKonfliktLoeser> konfliktLoeserMap = new HashMap<>();
  private final List<de.ivu.elect.business.ergebnis.entity.DHondtQuotient> dHondtQuotienten = new ArrayList<>();
  private String bemerkung = null;

  public SitzberechnungProGebietHelper(Sitzberechnungseingabe< ? > sitzberechnungseingabe, Wahldurchfuehrungsgebiet gebiet) {
    super();

    this.stimmergebnisDaten = sitzberechnungseingabe.getStimmergebnisDaten();

    this.wahlParameterLoader = sitzberechnungseingabe.getWahlParameterLoader();

    this.sitzberechnungseingabe = sitzberechnungseingabe;
    this.sitzberechnungLogger = new SitzberechnungLogger(sitzberechnungseingabe);

    this.gebiet = gebiet;
  }

  public StimmergebnisDaten getStimmergebnisDaten() {
    return stimmergebnisDaten;
  }

  public AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  public Wahldurchfuehrungsgebiet getGebiet() {
    return gebiet;
  }

  public Sitzberechnungseingabe< ? > getSitzberechnungseingabe() {
    return sitzberechnungseingabe;
  }

  public List<Sitze> getSitze() {
    return sitze;
  }

  public Sitze createSitze(Liste liste, long anzahl) {
    return createSitze(liste, anzahl, anzahl);
  }

  public Sitze createSitze(Liste liste, long anzahl, long anzahlAnspruch) {
    return createSitze(liste.getGruppe(), liste.getGebiet(), anzahl, anzahlAnspruch);
  }

  public Sitze createSitze(Gruppe gruppe, Gebiet gebiet, long anzahl, long anzahlAnspruch) {
    Sitze sitze = getSitzberechnungseingabe().getKonfiguration().getSitzeFactory().create();
    sitze.setGebiet(gebiet);
    sitze.setGruppe(gruppe);
    sitze.setAnzahl(anzahl);
    sitze.setAnzahlAnspruch(anzahlAnspruch);
    sitze.setErgebnis(getErgebnis());
    this.sitze.add(sitze);
    return sitze;
  }

  public List<Gewaehlt> getGewaehlte() {
    return gewaehlte;
  }

  protected CustomData createGewaehltCustomData() {
    return EmptyCustomData.INSTANCE;
  }

  public Gewaehlt createGewaehltSonstNachruecker(Kandidatur kandidatur, boolean isGewaehlt, Long rang) {
    return createGewaehlt(kandidatur, isGewaehlt ? GewaehltStatus.GEWAEHLT : GewaehltStatus.NACHRUECKER, rang);
  }

  public Gewaehlt createGewaehlt(Kandidatur kandidatur, GewaehltStatus gewaehltStatus, Long rang) {
    return createGewaehlt(createGewaehltCustomData(), kandidatur, gewaehltStatus, rang);
  }

  public Gewaehlt createGewaehlt(CustomData customData, Kandidatur kandidatur, GewaehltStatus gewaehltStatus, Long rang) {
    Gewaehlt gewaehlt = new Gewaehlt(customData);
    gewaehlt.setErgebnis(getErgebnis());
    gewaehlt.setGewaehltStatus(gewaehltStatus);
    gewaehlt.setKandidatur(kandidatur);
    gewaehlt.setRang(rang);

    gewaehlte.add(gewaehlt);
    return gewaehlt;
  }

  public Gewaehlt createGewaehltNichtZugelassen(Kandidatur kandidatur) {
    Gewaehlt gewaehlt = new Gewaehlt(createGewaehltCustomData());
    gewaehlt.setErgebnis(getErgebnis());
    gewaehlt.setGewaehltStatus(GewaehltStatus.NICHT_ZUGELASSEN);
    gewaehlt.setKandidatur(kandidatur);
    gewaehlt.setRang(AbstractGewaehlt.KEIN_RANG);

    gewaehlte.add(gewaehlt);
    return gewaehlt;
  }

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

  /**
   * Erzeugt den Konflikt zur Auswahl von Listen, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   *
   * @return den neuen Konflikt
   */
  public ErgebnisKonflikt createListenAuswahlKonflikt(ListenAuswaehlenErgebnisKonfliktCustomData customData, long anzahlZuLosen, List<Liste> listen, Enum< ? > typ) {
    customData.setBeteiligteListen(listen);
    customData.setAnzahlZuLosen(anzahlZuLosen);

    String typBeschreibung = getMessages().lokalisiere(typ, customData.getAnzahlZuLosen());
    String konfliktBeschreibung = customData.createErgebnisKonfliktBeschreibung(typBeschreibung, listen, getMessages());
    ErgebnisKonflikt konflikt = createKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_AUSWAHL_LISTEN, konfliktBeschreibung);
    logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

  /**
   * Erzeugt den Konflikt zur Auswahl von PolitischeGruppe'n, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   *
   * @return den neuen Konflikt
   */
  public ErgebnisKonflikt createPolitischeGruppenAuswahlKonflikt(PolitischeGruppenAuswaehlenErgebnisKonfliktCustomData customData, long anzahlZuLosen, List<PolitischeGruppe> politischeGruppen,
      Enum< ? > typ) {
    customData.setBeteiligtePolitischeGruppen(politischeGruppen);
    customData.setAnzahlZuLosen(anzahlZuLosen);

    String typBeschreibung = getMessages().lokalisiere(typ, customData.getAnzahlZuLosen());
    String konfliktBeschreibung = customData.createErgebnisKonfliktBeschreibung(typBeschreibung, politischeGruppen, getMessages());
    ErgebnisKonflikt konflikt = createKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_AUSWAHL_POLITISCHE_GRUPPEN, konfliktBeschreibung);
    logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

  /**
   * Erzeugt den Konflikt zur Auswahl von Gebiet'n, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   *
   * @return den neuen Konflikt
   */
  public ErgebnisKonflikt createGebieteAuswahlKonflikt(GebieteAuswaehlenErgebnisKonfliktCustomData customData, Enum< ? > typ, long anzahlZuLosen, List<Wahldurchfuehrungsgebiet> gebiete,
      Object... otherParams) {
    customData.setBeteiligteGebiete(gebiete);
    customData.setAnzahlZuLosen(anzahlZuLosen);

    String beschreibungDerGebiete = customData.createErgebnisKonfliktBeschreibung(gebiete);
    Object[] params = ArrayUtils.addAll(new Object[] {anzahlZuLosen, beschreibungDerGebiete}, otherParams);

    String konfliktBeschreibung = getMessages().lokalisiere(typ, params);
    ErgebnisKonflikt konflikt = createKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_AUSWAHL_GEBIETE, konfliktBeschreibung);
    logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

  /**
   * Erzeugt den Konflikt zur Auswahl von Kandidaturen, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   *
   * @return den neuen Konflikt
   */
  @Deprecated // Die Methode createKandidatenAuswahlKonflikt() verwenden. Sie erlaubt weitere Parameter bei der Lokalisierung und erzwingt nicht den ": " im Text
  public ErgebnisKonflikt createKandidaturAuswahlKonflikt(AbstractKandidaturenErgebnisKonfliktCustomData customData, long anzahlZuLosen, List< ? extends Kandidatur> kandidaturen, Enum< ? > typ) {
    customData.setBeteiligteKandidaturen(kandidaturen);
    customData.setAnzahlZuLosen(anzahlZuLosen);

    String typBeschreibung = getMessages().lokalisiere(typ, customData.getAnzahlZuLosen());
    String konfliktBeschreibung = customData.createErgebnisKonfliktBeschreibung(typBeschreibung, kandidaturen, getMessages());
    ErgebnisKonflikt konflikt = createKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_AUSWAHL_KANDIDATEN, konfliktBeschreibung);
    logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

  /**
   * Erzeugt den Konflikt zur Auswahl eines Direktkandidaten, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   * <p/>
   * Die Konfliktbeschreibung besteht aus dem Lokalisierungs-String für den typ. Dabei wird als Parameter 0 die Anzahl zu Losen verwendet. Als Parameter 1 wird die konkatenierte Beschreibung der
   * Kandidaturen verwendet. Weitere Parameter (ab Index 2) können ergänzt werden.
   *
   * @return den neuen Konflikt
   */
  public ErgebnisKonflikt createDirektkandidatAuswahlKonflikt(AbstractKandidaturenErgebnisKonfliktCustomData customData, Enum< ? > typ, List< ? extends Kandidatur> kandidaturen,
      Object... otherParams) {
    return createKandidaturenAuswahlKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_DIREKTKANDIDAT, typ, 1L, kandidaturen, otherParams);
  }

  /**
   * Erzeugt den Konflikt zur Auswahl von Kandidaten, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   * <p/>
   * Die Konfliktbeschreibung besteht aus dem Lokalisierungs-String für den typ. Dabei wird als Parameter 0 die Anzahl zu Losen verwendet. Als Parameter 1 wird die konkatenierte Beschreibung der
   * Kandidaturen verwendet. Weitere Parameter (ab Index 2) können ergänzt werden.
   *
   * @return den neuen Konflikt
   */
  public ErgebnisKonflikt createKandidatenAuswahlKonflikt(AbstractKandidaturenErgebnisKonfliktCustomData customData, Enum< ? > typ, long anzahlZuLosen, List< ? extends Kandidatur> kandidaturen,
      Object... otherParams) {
    return createKandidaturenAuswahlKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_AUSWAHL_KANDIDATEN, typ, anzahlZuLosen, kandidaturen, otherParams);
  }

  private ErgebnisKonflikt createKandidaturenAuswahlKonflikt(AbstractKandidaturenErgebnisKonfliktCustomData customData, ErgebnisKonfliktArt art, Enum< ? > typ, long anzahlZuLosen,
      List< ? extends Kandidatur> kandidaturen, Object... otherParams) {
    customData.setBeteiligteKandidaturen(kandidaturen);
    customData.setAnzahlZuLosen(anzahlZuLosen);

    String beschreibungDerKandidaturen = customData.createErgebnisKonfliktBeschreibung(kandidaturen, getMessages());
    Object[] params = ArrayUtils.addAll(new Object[] {anzahlZuLosen, beschreibungDerKandidaturen}, otherParams);

    String konfliktBeschreibung = getMessages().lokalisiere(typ, params);
    ErgebnisKonflikt konflikt = createKonflikt(customData, art, konfliktBeschreibung);
    logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

  /**
   * Erzeugt den Konflikt zur Festlegung der Reihenfolge von Kandidaturen, setzt die Konfliktbeschreibung, schreibt einen Log-Eintrag dazu.
   *
   * @return den neuen Konflikt
   */
  public ErgebnisKonflikt createKandidaturReihenfolgeKonflikt(AbstractKandidaturenErgebnisKonfliktCustomData customData, List< ? extends Kandidatur> kandidaturen, Enum< ? > typ) {
    customData.setBeteiligteKandidaturen(kandidaturen);

    String typBeschreibung = getMessages().lokalisiere(typ, customData.getAnzahlZuLosen());
    String konfliktBeschreibung = customData.createErgebnisKonfliktBeschreibung(typBeschreibung, kandidaturen, getMessages());
    ErgebnisKonflikt konflikt = createKonflikt(customData, ErgebnisKonfliktArt.LOSENTSCHEID_REIHENFOLGE_KANDIDATEN, konfliktBeschreibung);
    logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

  public ErgebnisKonflikt createKonflikt(ErgebnisKonfliktCustomData customData, ErgebnisKonfliktArt ergebnisKonfliktArt, String konfliktBeschreibung) {
    ErgebnisKonflikt result = new ErgebnisKonflikt(customData);
    result.setErgebnis(getErgebnis());
    result.setErgebnisKonfliktArt(ergebnisKonfliktArt);
    result.setKonfliktBeschreibung(konfliktBeschreibung);
    return result;
  }

  public void putHinweisZyklischeLoesungenZeigenAlleAuswirkungen(ErgebnisKonflikt konflikt) {
    hinweiseAnVariantenberechner.put(konflikt, HinweisAnVariantenberechner.ZYKLISCHE_LOESUNGEN_ZEIGEN_ALLE_AUSWIRKUNGEN);
  }

  /**
   * Versucht den Konflikt mit Hilfe des KonfliktLoesers zu lösen. Das Ergebnis kann sein:
   *
   * <ul>
   * <li>der übergebene Konflikt, entweder automatisch gelöst oder ungelöst</li>
   * <li>ein äquivalender Konflikt, der Teil der Sitzberechnungseingabe war, der eine manuelle Konfliktlösung beinhaltet</li>
   * <li>eine SitzverteilungAbbruchBeiErgebnisKonfliktException, wenn der Konflikt ungelöst bleibt und ungeloesteErgebnisKonflikteWerfenException == true</li>
   * </ul>
   *
   * @return den übergebenen Konflikt gelöst oder ungelöst oder einen äquivalenden alten Konflikt
   * @throws SitzverteilungAbbruchBeiErgebnisKonfliktException
   */
  public ErgebnisKonflikt loeseKonflikt(ErgebnisKonflikt konflikt, ErgebnisKonfliktLoeser konfliktLoeser, boolean ungeloesteErgebnisKonflikteWerfenException) {
    konfliktLoeser.setUngeloesteErgebnisKonflikteWerfenException(ungeloesteErgebnisKonflikteWerfenException);
    ErgebnisKonflikt geloesterKonflikt = null;
    try {
      geloesterKonflikt = konfliktLoeser.loeseKonflikt(getSitzberechnungseingabe(), konflikt, getLogger());
      this.konflikte.add(geloesterKonflikt);
      this.konfliktLoeserMap.put(geloesterKonflikt, konfliktLoeser);
    } finally {
      if (geloesterKonflikt == null) {
        this.konfliktLoeserMap.put(konflikt, konfliktLoeser);
      }
    }
    return geloesterKonflikt;
  }

  public String getBemerkung() {
    return bemerkung;
  }

  public void setBemerkung(String bemerkung) {
    this.bemerkung = bemerkung;
  }

  /**
   * @return die Listen in dem Gebiet, sortiert nach Stimmzettelposition auf dem gebiet
   */
  public List<Liste> getZugelasseneListen() {
    List<Liste> result = stimmergebnisDaten.getZugelasseneListenZuGebiet(gebiet);
    return sortWahlvorschlaegeByStimmzettelPosition(result, gebiet);
  }

  /**
   * Sortiert die Liste und gibt sie wieder zurück
   *
   * @param result
   *          die zu sortierende Liste
   * @return den Parameter result, nachdem er sortiert wurde
   */
  public List<Liste> sortWahlvorschlaegeByStimmzettelPosition(List<Liste> result, Wahldurchfuehrungsgebiet pGebiet) {
    WahlvorschlagByStimmzettelPositionComparator comparator = new WahlvorschlagByStimmzettelPositionComparator(pGebiet, stimmergebnisDaten.getEntityAufStimmzettelDaten());
    Collections.sort(result, comparator);

    return result;
  }

  /**
   * @return die Listen in dem Gebiet, die keiner Listenverbindung angehören
   */
  public List<Liste> getZugelasseneListenOhneListenverbindung() {
    List<Liste> zugelasseneListen = stimmergebnisDaten.getZugelasseneListenZuGebietOhneListenverbindung(gebiet);
    return sortWahlvorschlaegeByStimmzettelPosition(zugelasseneListen, gebiet);
  }

  /**
   * @return die Listenverbindungen in dem Gebiet
   */
  public Collection<Listenverbindung> getListenverbindungen() {
    return stimmergebnisDaten.getListenverbindungenFuerGebiet(gebiet);
  }

  public Map<PolitischeGruppe, Long> politischeGruppe2AnzahlStimmen() {
    return stimmergebnisDaten.getZweitstimmenAktuellGesamt(gebiet);
  }

  /**
   * @param liste
   *          die Liste, deren Stimmen gesucht werden
   * @return für alle Listenkandidaturen der Liste die Anzahl Stimmen im Gebiet als Long (auch bei 0 Stimmen)
   */
  public Map<Listenkandidatur, Long> getStimmenFuerListenkandidaturen(Liste liste) {
    return stimmergebnisDaten.getErgebnisDatenListenkandidaturen().collectStimmenPerListenkandidatur(liste, gebiet);
  }

  public Long getStimmenFuerDirektkandidaturen(Direktkandidatur direktkandidatur) {
    return stimmergebnisDaten.getErgebnisDatenDirektkandidaturen().getStimmenFuerDirektkandidatur(direktkandidatur, gebiet);
  }

  public List<Direktkandidatur> getDirektkandidaturen() {
    return stimmergebnisDaten.getDirektkandidaturenNachGebiet(gebiet);
  }

  public Messages getMessages() {
    return sitzberechnungseingabe.getKonfiguration().getMessages();
  }

  public void log(ErgebnisLogEintragArt ergebnisLogEintragArt, String messageKey, Object... objects) {
    getLogger().log(ergebnisLogEintragArt, messageKey, objects);
  }

  public void logLeereZeile() {
    log(ErgebnisLogEintragArt.LEERE_ZEILE, "sitzberechnung.algorithmus.protokoll.leere.zeile");
  }

  public void info(String messageKey, Object... objects) {
    getLogger().log(ErgebnisLogEintragArt.INFO, messageKey, objects);
  }

  public String displayPerson(Personendaten person) {
    return getSitzberechnungseingabe().getKonfiguration().getPersonendatenFormatter().getVornameUndFamiliennameFuerAnzeige(person);
  }

  public void createBesonderheit(String messageKey, Object... objects) {
    getLogger().besonderheit(messageKey, objects);
  }

  public void logKonfliktAufgetreten(ErgebnisKonflikt konflikt) {
    createBesonderheit(MESSAGE_KEY_LOSENTSCHEID_ERFORDERLICH, konflikt.getKonfliktBeschreibung());
  }

  /**
   * @return <code>true</code>, wenn das Ergebnis noch vorläufig ist
   */
  public boolean isVorlaeufig() {
    return getErgebnis().isVorlaeufig();
  }

  public Ergebnis getErgebnis() {
    return sitzberechnungseingabe.getErgebnis();
  }

  @Deprecated // use de.ivu.elect.util.NotNull.notNull instead
  public Long notNull(Long aLong) {
    if (aLong == null) {
      return 0L;
    }
    return aLong;
  }

  /**
   * @return ein Ergebnis ohne Sitze, Gewaehlte und Konflikte, aber ggf. mit Log-Einträgen
   */
  public Sitzberechnungsergebnis createLeeresSitzberechnungsergebnis() {
    return new Sitzberechnungsergebnis(new ArrayList<Sitze>(), new ArrayList<Gewaehlt>(), new ArrayList<ErgebnisKonflikt>(), getLogger().getErgebnisLogEintraege(), null);
  }

  /**
   * @param zusaetzlicheKonflikte
   *          Konflikte, die zu den schon aufgelaufenen {@link ErgebnisKonflikt}en hinzukommen
   * @return ein Sitzberechnungsergebnis ohne {@link Sitze} und {@link Gewaehlt}e nur mit den bisher aufgelaufenen {@link ErgebnisKonflikt}en und den zusaetzlicheKonflikte
   */
  public Sitzberechnungsergebnis createSitzberechnungsergebnisNurKonflikte(Collection<ErgebnisKonflikt> zusaetzlicheKonflikte) {
    List<ErgebnisKonflikt> konflikte = new ArrayList<>(getKonflikte());
    konflikte.addAll(zusaetzlicheKonflikte);
    return new Sitzberechnungsergebnis(new ArrayList<Sitze>(), new ArrayList<Gewaehlt>(), konflikte, hinweiseAnVariantenberechner, konfliktLoeserMap, getLogger().getErgebnisLogEintraege(), null);
  }

  public Sitzberechnungsergebnis getSitzberechnungsergebnis() {
    return new Sitzberechnungsergebnis(sitze, gewaehlte, konflikte, hinweiseAnVariantenberechner, konfliktLoeserMap, getLogger().getErgebnisLogEintraege(),
        dHondtQuotienten, null, bemerkung, null);
  }

  /**
   * @return die Anzahl der in diesem Gebiet zu vergebenden Sitze
   */
  public Long getAnzahlSitze() {
    return gebiet.getAnzahlSitze();
  }

  public boolean hatKeineSitze() {
    return getAnzahlSitze() == null || getAnzahlSitze() == 0;
  }

  public Sitzberechnungsergebnis abbruchDaKeineSitze() {
    log(ErgebnisLogEintragArt.FEHLER, "sitzberechnung.algorithmus.protokoll.anzahl.sitze.ist.0");
    log(ErgebnisLogEintragArt.INFO, "sitzberechnung.algorithmus.protokoll.sitzberechnung.abgebrochen");
    return createLeeresSitzberechnungsergebnis();
  }

  public void createDHondtQuotientAusVerteilungAufPolitischeGruppen(DHondtQuotient dHondtQuotient, PolitischeGruppe politischeGruppe) {
    de.ivu.elect.business.ergebnis.entity.DHondtQuotientPolitischeGruppe entity = new de.ivu.elect.business.ergebnis.entity.DHondtQuotientPolitischeGruppe();
    entity.setPolitischeGruppe(politischeGruppe);

    addDHondtQuotient(dHondtQuotient, entity);
  }

  public void createDHondtQuotientAusVerteilungInGruppeAufListe(DHondtQuotient dHondtQuotient, PolitischeGruppe politischeGruppe, Liste liste) {
    de.ivu.elect.business.ergebnis.entity.DHondtQuotientListenInGruppe entity = new de.ivu.elect.business.ergebnis.entity.DHondtQuotientListenInGruppe();
    entity.setPolitischeGruppe(politischeGruppe);
    entity.setListe(liste);

    addDHondtQuotient(dHondtQuotient, entity);
  }

  public void createDHondtQuotientAusVerteilungInListenverbindungAufListe(DHondtQuotient dHondtQuotient, Listenverbindung listenverbindung, Liste liste) {
    // TODO ELECT-8820
    getLogger().log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.protokoll.d.hondt.quotient.liste.in.listenverbindung.nicht.unterstuetzt", listenverbindung.getBezeichnung(),
        liste.getKurznameOderKennwort(), dHondtQuotient.getFraction());
  }

  public void createDHondtQuotientAusVerteilungInGebietAufTeilnehmer(DHondtQuotient dHondtQuotient, Teilnehmer teilnehmer, Wahldurchfuehrungsgebiet gebiet) {
    if (teilnehmer instanceof ListenTeilnehmer) {
      createDHondtQuotientAusVerteilungInGebietAufListe(dHondtQuotient, ((ListenTeilnehmer) teilnehmer).getListe(), gebiet);
    }

    if (teilnehmer instanceof ListenverbindungTeilnehmer) {
      createDHondtQuotientAusVerteilungInGebietAufListenverbindung(dHondtQuotient, ((ListenverbindungTeilnehmer) teilnehmer).getListenverbindung(), gebiet);
    }
  }

  private void createDHondtQuotientAusVerteilungInGebietAufListe(DHondtQuotient dHondtQuotient, Liste liste, Wahldurchfuehrungsgebiet gebiet) {
    DHondtQuotientListenImGebiet entity = new DHondtQuotientListenImGebiet();
    entity.setListe(liste);
    entity.setWahldurchfuehrungsgebiet(gebiet);

    addDHondtQuotient(dHondtQuotient, entity);
  }

  private void createDHondtQuotientAusVerteilungInGebietAufListenverbindung(DHondtQuotient dHondtQuotient, Listenverbindung listenverbindung, Wahldurchfuehrungsgebiet gebiet) {
    // TODO ELECT-8820
    getLogger().log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.protokoll.d.hondt.quotient.listenverbindung.im.gebiet.nicht.unterstuetzt", gebiet.getName(),
        listenverbindung.getBezeichnung(), dHondtQuotient.getFraction());
  }

  private void addDHondtQuotient(DHondtQuotient dHondtQuotient, de.ivu.elect.business.ergebnis.entity.DHondtQuotient entity) {
    entity.setErgebnis(getErgebnis());

    entity.setZaehler(dHondtQuotient.getZaehler());
    entity.setNenner(dHondtQuotient.getNenner());
    entity.setRang(dHondtQuotient.getRang());
    entity.setSitzgewinn(dHondtQuotient.isSitzgewinn());
    entity.setTeilnehmerAmLosverfahren(dHondtQuotient.isTeilnehmerAmLosverfahren());

    this.dHondtQuotienten.add(entity);
  }

  public SitzberechnungLogger getLogger() {
    return sitzberechnungLogger;
  }
}
