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

import java.util.ArrayList;
import java.util.List;

import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktArt;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonfliktCustomData;
import de.ivu.elect.business.sitzberechnung.control.util.SitzberechnungProGebietHelper;
import de.ivu.elect.business.sitzberechnung.control.verteilung.AbstractTeilnehmer;
import de.ivu.elect.business.sitzberechnung.control.verteilung.AbstractVerteilungsErgebnis;
import de.ivu.elect.business.sitzberechnung.control.verteilung.TeilnehmerErgebnis;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;

/**
 * Unterklassen von AbstractVerteilungsErgebnisKonfliktBehandler führen die Konfliktbehandlung durch, die nach einer
 * (z.B.) D'Hondt- oder Niemeyer-Verteilung nötig ist, falls es ein Losverfahren um Restsitze geben muss. Diese umfasst
 * auch die Erzeugung eines Konflikts, die Suche nach einer manuellen Entscheidung, ggf. die automatische Entscheidung
 * oder falls der Konflikt ungelöst bleibt, das Auslösen einer Exception.
 * 
 *
 * 
 * @param <O>
 *          Typ der Objekte, zwischen denen verteilt und gelost wird
 * @param <T>
 *          Typ der Teilnehmer an der Verteilung, Subtyp von AbstractTeilnehmer&lt;O>
 */
public abstract class AbstractVerteilungsErgebnisKonfliktBehandler<O, T extends AbstractTeilnehmer<O>> {

  private static final int EIN_SITZ = 1;
  private static final int KEIN_SITZ = 0;

  protected final SitzberechnungProGebietHelper helper;

  protected AbstractVerteilungsErgebnisKonfliktBehandler(SitzberechnungProGebietHelper helper) {
    this.helper = helper;
  }

  /**
   * Erzeugt für die im <code>ergebnis</code> ermittelten Losteilnehmer einen Konflikt. Dieser wird gelöst. Aus dem
   * gelösten Konflikt werden die Losgewinner ausgelesen und ihnen jeweils ein Sitz aus der Konfliktbehandlung
   * zugewiesen.
   * 
   * @param ergebnis
   *          Ergebnis einer vorherigen Verteilung von Sitzen, bei der verbleibende Sitze noch per Losentscheid verteilt
   *          werden müssen
   * @param teilnehmer
   *          Die Teilnehmer an der Verteilung
   */
  public void konfliktBehandlung(AbstractVerteilungsErgebnis< ? > ergebnis, List< ? extends T> teilnehmer) {
    konfliktBehandlungIntern(ergebnis, teilnehmer, false);
  }

  /**
   * Erzeugt für die im <code>ergebnis</code> ermittelten Losteilnehmer, für die noch auf keinem anderen Weg eine
   * Entscheidung gefunden wurde, einen Konflikt. Dieser wird gelöst. Aus dem gelösten Konflikt werden die Losgewinner
   * ausgelesen und ihnen jeweils ein Sitz aus der Konfliktbehandlung zugewiesen.
   *
   * @param ergebnis
   *          Ergebnis einer vorherigen Verteilung von Sitzen, bei der verbleibende Sitze noch per Losentscheid verteilt
   *          werden müssen. Dabei können schon einige Entscheidungen um zu losende Sitze gefallen sein.
   * @param teilnehmer
   *          Die Teilnehmer an der Verteilung
   */
  public void konfliktBehandlungFortsetzen(AbstractVerteilungsErgebnis< ? > ergebnis, List< ? extends T> teilnehmer) {
    konfliktBehandlungIntern(ergebnis, teilnehmer, true);
  }

  /**
   * @param isFortsetzung
   *          ist normalerweise false. Ist nur true in Fällen, in denen der Losentscheid teilweise schon vorab gefallen
   *          ist.
   */
  private void konfliktBehandlungIntern(AbstractVerteilungsErgebnis< ? > ergebnis, List< ? extends T> teilnehmer,
      boolean isFortsetzung) {
    List<O> teilnehmerAmLosverfahren = getTeilnehmerAmLosverfahren(ergebnis, teilnehmer, isFortsetzung);
    long anzahlSitzeZuVerlosen = ergebnis.getAnzahlSitzeZuVerlosen();
    if (isFortsetzung) {
      anzahlSitzeZuVerlosen = ergebnis.getAnzahlNichtVerteilterSitze();
    }

    ErgebnisKonflikt geloesterKonflikt = erzeugeUndEntscheideKonflikt(anzahlSitzeZuVerlosen, teilnehmerAmLosverfahren);
    List<O> gelosteTeilnehmer = getGelosteTeilnehmerAusKonfliktBehandlung(teilnehmerAmLosverfahren, geloesterKonflikt);
    sitzeZuteilenAnGelosteTeilnehmer(ergebnis, teilnehmer, gelosteTeilnehmer, isFortsetzung);
  }

  private List<O> getTeilnehmerAmLosverfahren(AbstractVerteilungsErgebnis< ? > verteilungsErgebnis,
      List< ? extends T> dieTeilnehmer, boolean isFortsetzung) {
    List<O> teilnehmerAmLosverfahren = new ArrayList<>();
    for (T listenTeilnehmer : dieTeilnehmer) {
      TeilnehmerErgebnis teilnehmerErgebnis = verteilungsErgebnis.getTeilnehmerErgebnis(listenTeilnehmer);
      if (isTeilnehmerAmLosverfahren(teilnehmerErgebnis, isFortsetzung)) {
        teilnehmerAmLosverfahren.add(listenTeilnehmer.getObject());
      }
    }
    return teilnehmerAmLosverfahren;
  }

  /**
   * @param isFortsetzung
   *          ist normalerweise false. Ist nur true in Fällen, in denen der Losentscheid teilweise schon vorab gefallen
   *          ist.
   * @return true, wenn der Teilnehmer am Losverfahren teilnimmt. Wenn isFortsetzung == true ist, wird zusätzlich
   *         geprüft, dass die Entschedung noch nicht gefallen ist.
   */
  private boolean isTeilnehmerAmLosverfahren(TeilnehmerErgebnis teilnehmerErgebnis, boolean isFortsetzung) {
    return teilnehmerErgebnis.isTeilnehmerAmLosverfahren() && (!isFortsetzung || !teilnehmerErgebnis.isEntschieden());
  }

  /**
   * Erzeugt einen neuen Konflikt und versucht ihn zu lösen. Der gelöste Konflikt ist der neue oder ein alter Konflikt
   * aus der {@link Sitzberechnungseingabe}..
   * 
   * @return den gelösten Konflikt
   */
  private ErgebnisKonflikt erzeugeUndEntscheideKonflikt(long anzahlSitzeZuVerlosen, List<O> teilnehmerAmLosverfahren) {
    ErgebnisKonflikt neuerKonflikt = erzeugeKonflikt(teilnehmerAmLosverfahren, anzahlSitzeZuVerlosen);
    ErgebnisKonflikt geloesterKonflikt = entscheideKonflikt(neuerKonflikt);
    return geloesterKonflikt;
  }

  protected abstract ErgebnisKonflikt erzeugeKonflikt(List<O> teilnehmerAmLosverfahren, long anzahlZuLosen);

  protected ErgebnisKonflikt entscheideKonflikt(ErgebnisKonflikt konflikt) {
    return helper.loeseKonflikt(konflikt, createKonfliktLoeser(), true); // TODO 12.03.2014: ungeloesteErgebnisKonflikteWerfenException konfigurierbar machen
  }

  protected ErgebnisKonfliktLoeser createKonfliktLoeser() {
    return new ErgebnisKonfliktLoeser();
  }

  /**
   * Wählt mit Hilfe des gelösten Konflikts diejenigen Teilnehmer aus, die beim Losverfahren gewonnen haben.
   */
  protected abstract List<O> getGelosteTeilnehmerAusKonfliktBehandlung(List<O> teilnehmerAmLosverfahren,
      ErgebnisKonflikt geloesterKonflikt);

  private void sitzeZuteilenAnGelosteTeilnehmer(AbstractVerteilungsErgebnis< ? > verteilungsErgebnis,
      List< ? extends T> dieTeilnehmer, List<O> geloste, boolean isFortsetzung) {
    for (T einTeilnehmer : dieTeilnehmer) {
      TeilnehmerErgebnis teilnehmerErgebnis = verteilungsErgebnis.getTeilnehmerErgebnis(einTeilnehmer);
      if (isTeilnehmerAmLosverfahren(teilnehmerErgebnis, isFortsetzung)) {
        if (geloste.contains(einTeilnehmer.getObject())) {
          teilnehmerErgebnis.entscheideLosverfahren(EIN_SITZ);
        } else {
          teilnehmerErgebnis.entscheideLosverfahren(KEIN_SITZ);
        }
      }
    }
  }

  protected ErgebnisKonflikt erzeugeKonflikt(ErgebnisKonfliktCustomData customData, String konfliktBeschreibung,
      ErgebnisKonfliktArt ergebnisKonfliktArt) {
    ErgebnisKonflikt konflikt = helper.createKonflikt(customData, ergebnisKonfliktArt, konfliktBeschreibung);
    helper.logKonfliktAufgetreten(konflikt);

    return konflikt;
  }

}
