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

import static java.util.stream.Collectors.toMap;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.sitzberechnung.control.util.SitzberechnungProGebietHelper;
import de.ivu.elect.business.sitzberechnung.control.verteilung.AbstractReihenfolgeErgebnis;
import de.ivu.elect.business.sitzberechnung.control.verteilung.AbstractTeilnehmer;
import de.ivu.elect.business.sitzberechnung.control.verteilung.Teilnehmer;
import de.ivu.elect.business.sitzberechnung.control.verteilung.TeilnehmerErgebnis;

public abstract class AbstractReihenfolgeErgebnisKonfliktBehandler<E, T extends AbstractTeilnehmer<E>> {

  protected final SitzberechnungProGebietHelper helper;

  protected AbstractReihenfolgeErgebnisKonfliktBehandler(SitzberechnungProGebietHelper helper) {
    super();

    this.helper = helper;
  }

  public void konfliktBehandlung(AbstractReihenfolgeErgebnis ergebnis, List< ? extends T> teilnehmerInSortierung) {
    konfliktBehandlungIntern(ergebnis, teilnehmerInSortierung);
  }

  private void konfliktBehandlungIntern(AbstractReihenfolgeErgebnis ergebnis, List< ? extends T> teilnehmerInSortierung) {
    List<E> ergebnisKonfliktEntities = collectErgebnisKonfliktEntities(ergebnis, teilnehmerInSortierung);
    ErgebnisKonflikt ergebnisKonflikt = erzeugeUndEntscheideKonflikt(ergebnisKonfliktEntities);

    List<E> ergebnisKonfliktEntitiesGelost = collectErgebnisKonfliktEntitiesGelost(ergebnisKonflikt, ergebnisKonfliktEntities);
    entscheideErgebnis(ergebnis, teilnehmerInSortierung, ergebnisKonfliktEntitiesGelost);
  }

  private List<E> collectErgebnisKonfliktEntities(AbstractReihenfolgeErgebnis ergebnis, List< ? extends T> teilnehmerInSortierung) {
    List<E> result = new ArrayList<>();

    for (T teilnehmer : teilnehmerInSortierung) {
      TeilnehmerErgebnis teilnehmerErgebnis = ergebnis.getTeilnehmerErgebnis(teilnehmer);

      if (isTeilnehmerImLosverfahren(teilnehmerErgebnis)) {
        result.add(teilnehmer.getObject());
      }
    }

    return result;
  }

  private List<E> collectErgebnisKonfliktEntitiesGelost(ErgebnisKonflikt ergebnisKonflikt, List<E> ergebnisKonfliktEntities) {
    return getGelosteTeilnehmerAusKonfliktBehandlung(ergebnisKonfliktEntities, ergebnisKonflikt);
  }

  protected abstract List<E> getGelosteTeilnehmerAusKonfliktBehandlung(List<E> ergebnisKonfliktEntities, ErgebnisKonflikt geloesterKonflikt);

  private boolean isTeilnehmerImLosverfahren(TeilnehmerErgebnis teilnehmerErgebnis) {
    return teilnehmerErgebnis.isTeilnehmerAmLosverfahren() && !teilnehmerErgebnis.isEntschieden();
  }

  private ErgebnisKonflikt erzeugeUndEntscheideKonflikt(List<E> ergebnisKonfliktEntities) {
    ErgebnisKonflikt ergebnisKonflikt = erzeugeKonflikt(ergebnisKonfliktEntities);

    return helper.loeseKonflikt(ergebnisKonflikt, new ErgebnisKonfliktLoeser(), true);
  }

  protected abstract ErgebnisKonflikt erzeugeKonflikt(List<E> beteiligteKandidaturen);

  private void entscheideErgebnis(AbstractReihenfolgeErgebnis ergebnis, List< ? extends T> teilnehmerInSortierung, List<E> ergebnisKonfliktEntitiesGelost) {
    List<Teilnehmer> teilnehmerSortiert = new ArrayList<>();

    Map<E, T> teilnehmerInSortierungPerErgebnisKonfliktEntity = teilnehmerInSortierung.stream().collect(toMap(AbstractTeilnehmer::getObject, Function.identity()));

    for (E ergebnisKonfliktEntity : ergebnisKonfliktEntitiesGelost) {
      T teilnehmerGelost = teilnehmerInSortierungPerErgebnisKonfliktEntity.get(ergebnisKonfliktEntity);

      if (teilnehmerGelost != null) {
        teilnehmerSortiert.add(teilnehmerGelost);
      }
    }

    ergebnis.entscheideLosverfahren(teilnehmerSortiert);
  }
}
