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

import java.math.BigInteger;
import java.util.Date;
import java.util.List;

import de.ivu.elect.business.benutzerverwaltung.entity.Benutzer;
import de.ivu.elect.business.ergebnis.control.StimmergebnisDaten;
import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktStatus;
import de.ivu.elect.business.ergebniskonflikt.entity.AbstractErgebnisKonflikt;
import de.ivu.elect.business.ergebniskonflikt.entity.AbstractErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.sitzberechnung.control.util.SitzberechnungLogger;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;
import de.ivu.elect.jee.faces.Messages;

/**
 * Führt die immer gleichen Schritte durch, die erforderlich sind, wenn ein Sitzberechnungsalgorithmus einen
 * {@link ErgebnisKonflikt} erzeugt hat und dieser nun an Hand der {@link Sitzberechnungseingabe} zu lösen ist.
 */
public class ErgebnisKonfliktLoeser {

  private static final String MESSAGE_KEY_LOESUNG_DURCH_SYSTEM = "sitzberechnung.algorithmus.protokoll.losentscheid.loesung.durch.system";
  private static final String MESSAGE_KEY_LOESUNG_DURCH_VARIANTENBERECHNUNG = "sitzberechnung.algorithmus.protokoll.losentscheid.loesung.durch.variantenberechnung";
  private static final String MESSAGE_KEY_LOESUNG_LIEGT_VOR = "sitzberechnung.algorithmus.protokoll.losentscheid.loesung.liegt.vor";

  public static final String LOESUNG_VON_SYSTEM = Benutzer.SYSTEM.getFamilienname();
  public static final String LOESUNG_VOM_VARIANTENBERECHNER = AbstractErgebnisKonflikt.LOESUNG_AUS_VARIANTENBERECHNUNG;

  private boolean ungeloesteErgebnisKonflikteWerfenException = false;

  /**
   * Für Konflikte gibt es drei Lösungsmöglichkeiten:
   * <ul>
   * <li>Der Konflikt wurde schon gelöst, d.h. die sitzberechnungseingabe enthält einen gleichen Konflikt mit Lösung.
   * Dann muss diese Lösung übernommen werden.</li>
   * <li>Der Konflikt soll automatisch gelöst werden, z.B. bei Hochrechnungen.</li>
   * <li>Der Konflikt soll nicht automatisch gelöst werden. In diesem Fall wird keinem der Sieg zugesprochen. Das ist
   * zunächst "gerecht", führt aber zu einem "unmöglichen" Sitzergebnis, weil Sitze ggf. nicht verteilt werden.</li>
   * </ul>
   *
   * @return den automatisch gelösten oder ungelösten Konflikt oder den gefundenen aus der manuellen Konfliktlösung
   * @throws SitzverteilungAbbruchBeiErgebnisKonfliktException
   *           der Konflikt ungelöst bleibt und falls
   *           <code>isUngeloesteErgebnisKonflikteWerfenException() == true</code>
   */
  public ErgebnisKonflikt loeseKonflikt(Sitzberechnungseingabe< ? > sitzberechnungseingabe, ErgebnisKonflikt konflikt,
      SitzberechnungLogger sitzberechnungLogger) {
    List<ErgebnisKonflikt> alteKonflikte = sitzberechnungseingabe.getKonflikte();

    ErgebnisKonflikt alterKonflikt = findGleichenKonflikt(konflikt, alteKonflikte);
    if (alterKonflikt != null && alterKonflikt.isGeloest()) {
      if (alterKonflikt.isLoesungAusVariantenberechnung()) {
        sitzberechnungLogger.besonderheit(MESSAGE_KEY_LOESUNG_DURCH_VARIANTENBERECHNUNG, alterKonflikt.getLoesungBeschreibung());
      } else {
        sitzberechnungLogger.besonderheit(MESSAGE_KEY_LOESUNG_LIEGT_VOR, alterKonflikt.getLoesungVon(),
            alterKonflikt.getLoesungBeschreibung());
      }
      // Gib den alten gelösten Konflikt zurück
      return alterKonflikt;
    }

    if (sitzberechnungseingabe.getKonfiguration().isKonflikeSelbstLoesen()) {
      // Metadaten und Lösung automatisch setzen
      konflikt.setErgebnisKonfliktStatus(ErgebnisKonfliktStatus.SYSTEM_ENTSCHIEDEN);
      konflikt.setLoesungVon(LOESUNG_VON_SYSTEM);
      konflikt.setLoesungZeitpunkt(new Date());

      StimmergebnisDaten stimmergebnisDaten = sitzberechnungseingabe.getStimmergebnisDaten();
      loeseKonfliktAutomatisch(konflikt, stimmergebnisDaten, sitzberechnungseingabe.getKonfiguration().getMessages());

      sitzberechnungLogger.besonderheit(MESSAGE_KEY_LOESUNG_DURCH_SYSTEM, konflikt.getLoesungBeschreibung());
      return konflikt;
    }

    // ACHTUNG, ungelöste alte Konflikte nicht zurückgeben. Sonst kann es zu Problemen in der Variantenberechnung kommen

    // neuer Konflikt bleibt ungelöst
    konflikt.setErgebnisKonfliktStatus(ErgebnisKonfliktStatus.UNGELOEST);
    if (isUngeloesteErgebnisKonflikteWerfenException()) {
      throw new SitzverteilungAbbruchBeiErgebnisKonfliktException(konflikt);
    }

    return konflikt;
  }

  /**
   * Standardverhalten ist keine Exception. Kann von Unterklassen überschrieben werden.
   *
   * @return <code>true</code>, wenn ungelöste ErgebnisKonflikte zu einer
   *         SitzverteilungAbbruchBeiErgebnisKonfliktException führen sollen.
   */
  public boolean isUngeloesteErgebnisKonflikteWerfenException() {
    return ungeloesteErgebnisKonflikteWerfenException;
  }

  public void setUngeloesteErgebnisKonflikteWerfenException(boolean ungeloesteErgebnisKonflikteWerfenException) {
    this.ungeloesteErgebnisKonflikteWerfenException = ungeloesteErgebnisKonflikteWerfenException;
  }

  /**
   * @param alteKonflikte
   * @return einen Konflikt aus <code>alteKonflikte</code>, der fachlich gleich <code>konflikt</code> ist,
   *         <code>null</code>, falls dort keiner gefunden wird.
   */
  private ErgebnisKonflikt findGleichenKonflikt(ErgebnisKonflikt konflikt, List<ErgebnisKonflikt> alteKonflikte) {
    for (ErgebnisKonflikt alterKonflikt : alteKonflikte) {
      if (konflikt.isFachlichGleicherKonflikt(alterKonflikt)) {
        return alterKonflikt;
      }
    }
    return null;
  }

  private void loeseKonfliktAutomatisch(ErgebnisKonflikt konflikt, StimmergebnisDaten stimmergebnisDaten, Messages messages) {
    getCustomData(konflikt).loeseKonfliktAutomatisch();
    setzeLoesungsBeschreibung(konflikt, stimmergebnisDaten, messages);
  }

  private void setzeLoesungsBeschreibung(ErgebnisKonflikt konflikt, StimmergebnisDaten stimmergebnisDaten, Messages messages) {
    AbstractErgebnisKonfliktCustomData customData = getCustomData(konflikt);
    String loesungBeschreibung = customData.createLoesungBeschreibung(stimmergebnisDaten.getWahlvorschlaege(),
        stimmergebnisDaten.getKandidaturen(), stimmergebnisDaten.getListenverbindungen(), stimmergebnisDaten.getPolitischeGruppen(),
        stimmergebnisDaten.getGebiete(), messages);
    konflikt.setLoesungBeschreibung(loesungBeschreibung);
  }

  /**
   * Löst den Konflikt automatisch, z.B. indem immer der erste gewinnt. Wird z.B. bei Hochrechnungen verwendet.
   * <p/>
   * POST: konflikt.getErgebnisKonfliktStatus() == ErgebnisKonfliktStatus.SYSTEM_ENTSCHIEDEN
   */
  public void loeseKonfliktFuerVariantenberechnung(ErgebnisKonflikt konflikt, StimmergebnisDaten stimmergebnisDaten, Messages messages) {
    setMetadatenLoesungFuerVariantenberechnung(konflikt);
    loeseKonfliktAutomatisch(konflikt, stimmergebnisDaten, messages);
  }

  public void loeseKonfliktZyklischFuerVariantenberechnung(ErgebnisKonflikt konflikt, int indexDerLoesung,
      StimmergebnisDaten stimmergebnisDaten, Messages messages) {
    setMetadatenLoesungFuerVariantenberechnung(konflikt);
    getCustomData(konflikt).loeseKonfliktAutomatischZyklisch(indexDerLoesung);
    setzeLoesungsBeschreibung(konflikt, stimmergebnisDaten, messages);
  }

  public void loeseKonfliktFuerVariantenberechnung(ErgebnisKonflikt konflikt, int indexDerLoesung, StimmergebnisDaten stimmergebnisDaten, Messages messages) {
    setMetadatenLoesungFuerVariantenberechnung(konflikt);
    getCustomData(konflikt).loeseKonfliktAutomatisch(indexDerLoesung);
    setzeLoesungsBeschreibung(konflikt, stimmergebnisDaten, messages);
  }

  private void setMetadatenLoesungFuerVariantenberechnung(ErgebnisKonflikt konflikt) {
    konflikt.setErgebnisKonfliktStatus(ErgebnisKonfliktStatus.SYSTEM_ENTSCHIEDEN);
    konflikt.setLoesungVon(LOESUNG_VOM_VARIANTENBERECHNER);
    konflikt.setLoesungZeitpunkt(new Date());
  }

  private AbstractErgebnisKonfliktCustomData getCustomData(ErgebnisKonflikt konflikt) {
    return konflikt.getCustomData(AbstractErgebnisKonfliktCustomData.class);
  }

  public BigInteger getAnzahlLoesungen(ErgebnisKonflikt konflikt) {
    return getCustomData(konflikt).getAnzahlLoesungen();
  }

}
