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

import java.util.List;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.ergebnis.boundary.DHondtQuotientDAO;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisLogEintragDAO;
import de.ivu.elect.business.ergebnis.boundary.GewaehltDAO;
import de.ivu.elect.business.ergebnis.boundary.SitzeDAO;
import de.ivu.elect.business.ergebnis.boundary.SitzeFactory;
import de.ivu.elect.business.ergebnis.boundary.StimmenDAO;
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.control.StimmergebnisDatenCreator;
import de.ivu.elect.business.ergebnis.entity.DHondtQuotient;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisLogEintrag;
import de.ivu.elect.business.ergebnis.entity.ErgebnisStatus;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebniskonflikt.boundary.ErgebnisKonfliktDAO;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.internet.control.PresentationGenerieren;
import de.ivu.elect.business.sitzberechnung.entity.SitzberechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungDAO;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.WahlartSitzberechnungsalgorithmus;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.person.PersonendatenFormatter;

public class Sitzberechnung {

  protected static final Logger LOG = LoggerFactory.getLogger(Sitzberechnung.class);

  @Inject
  transient SitzberechnungsalgorithmusFinder sitzberechnungsalgorithmusFinder;

  @Inject
  transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  transient ErgebnisKonfliktDAO konfliktDAO;

  @Inject
  transient SitzeDAO sitzeDAO;

  @Inject
  transient GewaehltDAO gewaehltDAO;

  @Inject
  transient ErgebnisLogEintragDAO ergebnisLogEintragDAO;

  @Inject
  transient DHondtQuotientDAO dHondtQuotientDAO;

  @Inject
  transient StimmergebnisDatenCreator stimmergebnisDatenCreator;

  @Inject
  transient Messages messages;

  @Inject
  transient SystemLogWriter systemLogWriter;

  @Inject
  transient PersonendatenFormatter personendatenFormatter;

  @Inject
  transient SitzeFactory sitzeFactory;

  @Inject
  transient WahlParameterLoader wahlParameterLoader;

  @Inject
  transient StimmenKumulierer stimmenKumulierer;

  @Inject
  transient ErgebnisKonfliktDAO ergebnisKonfliktDAO;

  @Inject
  transient StimmenDAO stimmenDAO;

  @Inject
  private transient PresentationGenerieren presentationGenerieren;

  public void berechneSitze(Ergebnis ergebnis, SitzberechnungsalgorithmusKonfiguration konfiguration) {
    Wahldurchfuehrung wahldurchfuehrung = wahldurchfuehrungDAO.findByPrimaryKey(ergebnis.getWahl().getPk());

    Sitzberechnungsalgorithmus sitzberechnungsalgorithmus = sitzberechnungsalgorithmusFinder.getSitzberechnungsalgorithmus(wahldurchfuehrung);

    WahlartSitzberechnungsalgorithmus wahlartSitzberechnungsalgorithmus = sitzberechnungsalgorithmus.getSitzberechnungsalgorithmusEnum();
    LOG.info("Berechne Ergebnis: Sitzberechnungsalgorithmus \"{}\" ({})", messages.lokalisiere(wahlartSitzberechnungsalgorithmus), wahlartSitzberechnungsalgorithmus);

    Sitzberechnungsergebnis sitzberechnungsergebnis;

    boolean praesentationZuruecksetzen = false;

    if (ergebnis.getErgebnisStatus().isZuBerechnen()) {
      praesentationZuruecksetzen = true; // ELECTKOM-957 ergebnisberechnung wurde angestorßen => Präsentation auf jeden Fall neu erstellen
    }

    ergebnis.setErgebnisStatus(ErgebnisStatus.BERECHNET);

    konfiguration.setMessages(messages);
    konfiguration.setSystemLogWriter(systemLogWriter);
    konfiguration.setPersonendatenFormatter(personendatenFormatter);
    konfiguration.setSitzeFactory(sitzeFactory);
    konfiguration.setMaximaleAnzahlSitzberechnungenInVariantenberechnung(wahlParameterLoader.resolveLongValue(ergebnis.getWahl(), WahlParameterWerte.MAX_ANZAHL_SITZBERECHNUNGEN_IN_VARIANTENBERECHNUNG));

    ergebnis.setSitzberechnungsalgorithmus(wahlartSitzberechnungsalgorithmus);

    LOG.info("Berechne Ergebnis: vor wahldurchfuehrungDAO.flush()");
    wahldurchfuehrungDAO.flush(); // Aufruf kann aus Ergebnisübernahme erfolgen => geänderte Stimmen, alles in DB speichern
    LOG.info("Berechne Ergebnis: nach wahldurchfuehrungDAO.flush()");

    List<ErgebnisKonflikt> bisherigeKonflikte = ergebnisKonfliktDAO.findByErgebnisOrderById(ergebnis);
    List<Sitze> bisherigeSitze = sitzeDAO.findByErgebnis(ergebnis);
    List<Gewaehlt> bisherigeGewaehlte = gewaehltDAO.findByErgebnis(ergebnis);
    List<ErgebnisLogEintrag> bisherigeLogEintraege = ergebnisLogEintragDAO.findByErgebnisOrderByPosition(ergebnis);
    List<DHondtQuotient> bisherigeDHondtQuotienten = dHondtQuotientDAO.findByErgebnis(ergebnis);

    if (sitzberechnungsalgorithmus.isBerechnetErgebnis()) {
      StimmergebnisDaten stimmergebnisDaten = stimmergebnisDatenCreator.createErgebnisDatenSitzberechnung(ergebnis);
      LOG.info("Berechne Ergebnis: nach stimmergebnisDatenCreator.createErgebnisDaten(ergebni)");

      StimmenPool stimmen = StimmenPool.createStimmenPool(stimmenDAO, ergebnis);

      Sitzberechnungseingabe sitzberechnungseingabe = new Sitzberechnungseingabe<>(stimmergebnisDaten, ergebnis, stimmen, bisherigeKonflikte, wahlParameterLoader, konfiguration, stimmenKumulierer);
      sitzberechnungsergebnis = sitzberechnungsalgorithmus.berechneSitzverteilung(sitzberechnungseingabe);
    } else {
      sitzberechnungsergebnis = Sitzberechnungsergebnis.KEIN_ERGEBNIS;
    }

    List<ErgebnisKonflikt> neueKonflikte = sitzberechnungsergebnis.getKonflikte();

    // Vorhandene Konflikte nach Berechnung löschen, wenn nicht in den neuen Konflikten enthalten
    for (ErgebnisKonflikt konflikt : bisherigeKonflikte) {
      if (!neueKonflikte.contains(konflikt)) {
        konfliktDAO.remove(konflikt);
      }
    }

    // neue Konflikte anlegen, wenn nicht in den bisherigen vorhanden
    for (ErgebnisKonflikt neuerKonflikt : neueKonflikte) {
      if (!neuerKonflikt.isGeloest()) {
        ergebnis.setErgebnisStatus(ErgebnisStatus.UNGELOESTE_KONFLIKTE_VORHANDEN);
      }
      if (!bisherigeKonflikte.contains(neuerKonflikt)) {
        neuerKonflikt.setErgebnis(ergebnis);
        konfliktDAO.persist(neuerKonflikt);
      }
    }

    // bisherige Sitze löschen
    for (Sitze sitze : bisherigeSitze) {
      sitzeDAO.remove(sitze);
    }

    // neue Sitze anlegen
    for (Sitze sitze : sitzberechnungsergebnis.getSitze()) {
      sitze.setErgebnis(ergebnis);
      sitzeDAO.persist(sitze);
    }

    // bisherige Gewählte löschen
    for (Gewaehlt gewaehlt : bisherigeGewaehlte) {
      gewaehltDAO.remove(gewaehlt);
    }

    for (Gewaehlt gewaehlt : sitzberechnungsergebnis.getGewaehlte()) {
      gewaehlt.setErgebnis(ergebnis);
      gewaehltDAO.persist(gewaehlt);
    }

    // bisherige Log-Einträge löschen
    for (ErgebnisLogEintrag logEintrag : bisherigeLogEintraege) {
      ergebnisLogEintragDAO.remove(logEintrag);
    }

    long count = 1L;
    for (ErgebnisLogEintrag logEintrag : sitzberechnungsergebnis.getErgebnisLogEintraege()) {
      logEintrag.setErgebnis(ergebnis);
      logEintrag.setPosition(count++);
      ergebnisLogEintragDAO.persist(logEintrag);
    }

    // bisherige dHondtEinträge löschen
    for (DHondtQuotient dHondtQuotient : bisherigeDHondtQuotienten) {
      dHondtQuotientDAO.remove(dHondtQuotient);
    }

    for (DHondtQuotient dHondtQuotient : sitzberechnungsergebnis.getDHondtQuotienten()) {
      dHondtQuotient.setErgebnis(ergebnis);
      dHondtQuotientDAO.persist(dHondtQuotient);
    }

    ergebnis.setBemerkung(sitzberechnungsergebnis.getBemerkung());

    handleCustomData(ergebnis, sitzberechnungsergebnis);

    if (praesentationZuruecksetzen) {
      presentationGenerieren.preasentationFuerErgebnisStarten(ergebnis);
    }
  }

  protected void handleCustomData(Ergebnis ergebnis, Sitzberechnungsergebnis sitzberechnungsergebnis) {
    // nichts zu tun
  }
}
