
package de.ivu.elect.sitzberechnung.verteilung;

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

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

import de.ivu.elect.sitzberechnung.Gruppe;
import de.ivu.elect.sitzberechnung.Kandidatur;
import de.ivu.elect.sitzberechnung.Liste;
import de.ivu.elect.sitzberechnung.Listenverbindung;
import de.ivu.elect.sitzberechnung.control.Sitzberechnungsdaten;
import de.ivu.elect.sitzberechnung.entity.DefaultSitzberechnungsergebnis;
import de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt;
import de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen;
import de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenAuswaehlen;
import de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktListenAuswaehlen;

public class VerteilungErgebnisKonfliktFactory {

  private final Sitzberechnungsdaten sitzberechnungsdaten;

  private final DefaultSitzberechnungsergebnis sitzberechnungsergebnis;

  public VerteilungErgebnisKonfliktFactory(Sitzberechnungsdaten sitzberechnungsdaten, DefaultSitzberechnungsergebnis sitzberechnungsergebnis) {
    super();

    this.sitzberechnungsdaten = sitzberechnungsdaten;
    this.sitzberechnungsergebnis = sitzberechnungsergebnis;
  }

  public <E extends AbstractVerteilungErgebnis< ? >> VerteilungErgebnisKonfliktLosentscheid loseGruppenUndListenverbindungen(E ergebnis, Enum< ? > typ, List<GruppeTeilnehmer> teilnehmerInVerteilung) {
    long anzahlAuszuwaehlen = ergebnis.getAnzahlSitzeZuVerlosen();
    List<GruppeInVerteilung> auswaehlbar = filterTeilnehmerImLosverfahren(ergebnis, teilnehmerInVerteilung);

    List<Gruppe> gruppenAuswaehlbar = auswaehlbar.stream().map(GruppeInVerteilung::getGruppe).filter(Objects::nonNull).collect(toList());
    List<Listenverbindung> listenverbindungenAuswaehlbar = auswaehlbar.stream().map(GruppeInVerteilung::getListenverbindung).filter(Objects::nonNull).collect(toList());

    ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen ergebnisKonflikt = createOrFindErgebnisKonflikt(
        ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen.newInstance(typ, anzahlAuszuwaehlen, gruppenAuswaehlbar, listenverbindungenAuswaehlbar));

    List<GruppeInVerteilung> ausgewaehlt = new ArrayList<>();
    ausgewaehlt.addAll(ergebnisKonflikt.filterGruppenAusgewaehlt(auswaehlbar));
    ausgewaehlt.addAll(ergebnisKonflikt.filterListenverbindungenAusgewaehlt(auswaehlbar));

    updateTeilnehmerImLosverfahren(ergebnis, teilnehmerInVerteilung, ausgewaehlt);

    return new VerteilungErgebnisKonfliktLosentscheid(ergebnisKonflikt, auswaehlbar, ausgewaehlt);
  }

  public <E extends AbstractVerteilungErgebnis< ? >> VerteilungErgebnisKonfliktLosentscheid loseListen(E ergebnis, Enum< ? > typ, List<ListeTeilnehmer> teilnehmerInVerteilung) {
    long anzahlAuszuwaehlen = ergebnis.getAnzahlSitzeZuVerlosen();
    List<ListeInVerteilung> auswaehlbar = filterTeilnehmerImLosverfahren(ergebnis, teilnehmerInVerteilung);

    List<Liste> listenAuswaehlbar = auswaehlbar.stream().map(ListeInVerteilung::getListe).filter(Objects::nonNull).collect(toList());

    ErgebnisKonfliktListenAuswaehlen ergebnisKonflikt = createOrFindErgebnisKonflikt(ErgebnisKonfliktListenAuswaehlen.newInstance(typ, anzahlAuszuwaehlen, listenAuswaehlbar));

    List<ListeInVerteilung> ausgewaehlt = new ArrayList<>();
    ausgewaehlt.addAll(ergebnisKonflikt.filterListenAusgewaehlt(auswaehlbar));

    updateTeilnehmerImLosverfahren(ergebnis, teilnehmerInVerteilung, ausgewaehlt);

    return new VerteilungErgebnisKonfliktLosentscheid(ergebnisKonflikt, auswaehlbar, ausgewaehlt);
  }

  public <E extends AbstractVerteilungErgebnis< ? >> VerteilungErgebnisKonfliktLosentscheid loseKandidaturen(E ergebnis, Enum< ? > typ, List<KandidaturTeilnehmer> teilnehmerInVerteilung) {
    long anzahlAuszuwaehlen = ergebnis.getAnzahlSitzeZuVerlosen();
    List<KandidaturInVerteilung> auswaehlbar = filterTeilnehmerImLosverfahren(ergebnis, teilnehmerInVerteilung);

    List<Kandidatur> kandidaturenAuswaehlbar = auswaehlbar.stream().map(KandidaturInVerteilung::getKandidatur).filter(Objects::nonNull).collect(toList());

    ErgebnisKonfliktKandidaturenAuswaehlen ergebnisKonflikt = createOrFindErgebnisKonflikt(ErgebnisKonfliktKandidaturenAuswaehlen.newInstance(typ, anzahlAuszuwaehlen, kandidaturenAuswaehlbar));

    List<KandidaturInVerteilung> ausgewaehlt = new ArrayList<>();
    ausgewaehlt.addAll(ergebnisKonflikt.filterKandidaturenAusgewaehlt(auswaehlbar));

    updateTeilnehmerImLosverfahren(ergebnis, teilnehmerInVerteilung, ausgewaehlt);

    return new VerteilungErgebnisKonfliktLosentscheid(ergebnisKonflikt, auswaehlbar, ausgewaehlt);
  }

  private <K extends ErgebnisKonflikt> K createOrFindErgebnisKonflikt(K ergebnisKonflikt) {
    K result = (K) sitzberechnungsdaten.getFachlichenGleichenKonflikt(ergebnisKonflikt).orElse(ergebnisKonflikt);
    sitzberechnungsergebnis.addKonflikt(result);

    return result;
  }

  public <O, T extends AbstractTeilnehmer<O>> List<O> filterTeilnehmerImLosverfahren(AbstractVerteilungErgebnis< ? > ergebnis, List<T> teilnehmerInVerteilung) {
    List<O> auswaehlbar = new ArrayList<>();

    for (AbstractTeilnehmer< ? > teilnehmer : teilnehmerInVerteilung) {
      TeilnehmerErgebnis teilnehmerErgebnis = ergebnis.getTeilnehmerErgebnis(teilnehmer);

      if (teilnehmerErgebnis != null && isImLosverfahren(teilnehmerErgebnis)) {
        auswaehlbar.add((O) teilnehmer.getObject());
      }
    }

    return auswaehlbar;
  }

  public <O, T extends AbstractTeilnehmer<O>> void updateTeilnehmerImLosverfahren(AbstractVerteilungErgebnis< ? > ergebnis, List<T> teilnehmerInVerteilung, List<O> ausgewaehlt) {
    for (AbstractTeilnehmer< ? > teilnehmer : teilnehmerInVerteilung) {
      TeilnehmerErgebnis teilnehmerErgebnis = ergebnis.getTeilnehmerErgebnis(teilnehmer);

      if (teilnehmerErgebnis != null && isImLosverfahren(teilnehmerErgebnis)) {
        long anzahlRestsitzeAusLosverfahren = 0L;

        if (ausgewaehlt.contains(teilnehmer.getObject())) {
          anzahlRestsitzeAusLosverfahren = 1L;
        }

        teilnehmerErgebnis.entscheideLosverfahren(anzahlRestsitzeAusLosverfahren);
      }
    }
  }

  private boolean isImLosverfahren(TeilnehmerErgebnis teilnehmerErgebnis) {
    return teilnehmerErgebnis != null && !teilnehmerErgebnis.isEntschieden() && teilnehmerErgebnis.isTeilnehmerAmLosverfahren();
  }
}
