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

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktArt;
import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktStatus;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonfliktCustomDataFormatter;
import de.ivu.elect.business.ergebniskonflikt.entity.KandidaturenAuswaehlenTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.KandidaturenReihenfolgeTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.ListenAuswaehlenTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.PolitischeGruppenAuswaehlenTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.PolitischeGruppenUndListenverbindungenAuswaehlenTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.SitzberechnungsalgorithmusOption;
import de.ivu.elect.business.ergebniskonflikt.entity.SitzberechnungsalgorithmusOptionContext;
import de.ivu.elect.business.ergebniskonflikt.entity.SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.sitzberechnung.control.util.SitzberechnungProGebietHelper;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.jee.entity.customdata.CustomData;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.was.sitzberechnung.entity.AbstractElectSitzberechnungKandidatur;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungEntities;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungGebiet;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungGruppe;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListe;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListenverbindung;

public abstract class AbstractElectErgebnisKonfliktMapper extends AbstractElectSitzberechnungMapper implements ElectErgebnisKonfliktMapper {

  private final ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter;

  protected AbstractElectErgebnisKonfliktMapper(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter) {
    super();

    this.ergebnisKonfliktCustomDataFormatter = ergebnisKonfliktCustomDataFormatter;
  }

  protected ErgebnisKonfliktCustomDataFormatter getErgebnisKonfliktCustomDataFormatter() {
    return ergebnisKonfliktCustomDataFormatter;
  }

  @Override
  public de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapExtern(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities,
      ErgebnisKonflikt ergebnisKonflikt) {
    de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt result = mapExternInternal(sitzberechnungContext, sitzberechnungEntities, ergebnisKonflikt);

    if (result instanceof de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) {
      ((de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) result).setErgebnisKonfliktPersistent(ergebnisKonflikt);
    }

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapExternInternal(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities,
      ErgebnisKonflikt ergebnisKonflikt) {
    CustomData customData = ergebnisKonflikt.getCustomData();

    if (customData instanceof PolitischeGruppenAuswaehlenTypisiertErgebnisKonfliktCustomData) {
      return mapPolitischeGruppenAuswahlKonfliktExtern(sitzberechnungContext, sitzberechnungEntities.getGruppen(), ergebnisKonflikt,
          (PolitischeGruppenAuswaehlenTypisiertErgebnisKonfliktCustomData) customData);
    }

    if (customData instanceof PolitischeGruppenUndListenverbindungenAuswaehlenTypisiertErgebnisKonfliktCustomData) {
      return mapPolitischeGruppenUndListenverbindungenAuswahlKonfliktExtern(sitzberechnungContext, sitzberechnungEntities.getGruppen(), sitzberechnungEntities.getListenverbindungen(),
          ergebnisKonflikt, (PolitischeGruppenUndListenverbindungenAuswaehlenTypisiertErgebnisKonfliktCustomData) customData);
    }

    if (customData instanceof ListenAuswaehlenTypisiertErgebnisKonfliktCustomData) {
      return mapListenAuswahlKonfliktExtern(sitzberechnungContext, sitzberechnungEntities.getListen(), ergebnisKonflikt, (ListenAuswaehlenTypisiertErgebnisKonfliktCustomData) customData);
    }

    if (customData instanceof KandidaturenAuswaehlenTypisiertErgebnisKonfliktCustomData) {
      return mapKandidaturenAuswahlKonfliktExtern(sitzberechnungContext, sitzberechnungEntities.getKandidaturen(), ergebnisKonflikt,
          (KandidaturenAuswaehlenTypisiertErgebnisKonfliktCustomData) customData);
    }

    if (customData instanceof KandidaturenReihenfolgeTypisiertErgebnisKonfliktCustomData) {
      return mapKandidaturenReihenfolgeKonfliktExtern(sitzberechnungContext, sitzberechnungEntities.getKandidaturen(), ergebnisKonflikt,
          (KandidaturenReihenfolgeTypisiertErgebnisKonfliktCustomData) customData);
    }

    if (customData instanceof SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData) {
      SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData ergebnisKonfliktCustomData = (SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData) customData;

      if (ergebnisKonfliktCustomData.getBeteiligteOptionen().stream().anyMatch(SitzeZuteilungArt.class::isInstance)) {
        return mapSitzeZuteilungArtenAuswahlKonfliktExtern(sitzberechnungContext, ergebnisKonflikt, ergebnisKonfliktCustomData);
      }
    }

    throw new UnsupportedOperationException(
        "ErgebnisKonflikt unbekannt [ergebnisKonflikt = " + ergebnisKonflikt.getClass() + ", ergebnisKonfliktArt = " + ergebnisKonflikt.getErgebnisKonfliktArt() + "]");
  }

  @Override
  public ErgebnisKonflikt mapIntern(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities,
      de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt ergebnisKonflikt) {
    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) {
      Object ergebnisKonfliktPersistent = ((de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) ergebnisKonflikt).getErgebnisKonfliktPersistent();

      if (ergebnisKonfliktPersistent instanceof ErgebnisKonflikt) {
        return (ErgebnisKonflikt) ergebnisKonfliktPersistent;
      }
    }

    return mapInternInternal(sitzberechnungContext, sitzberechnungEntities, ergebnisKonflikt);
  }

  private ErgebnisKonflikt mapInternInternal(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities,
      de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt ergebnisKonflikt) {
    Collection<ElectSitzberechnungGebiet> gebiete = sitzberechnungEntities.getGebiete();

    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenAuswaehlen) {
      return mapPolitischeGruppenAuswahlKonfliktIntern(sitzberechnungContext, gebiete, sitzberechnungEntities.getGruppen(),
          (de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenAuswaehlen) ergebnisKonflikt);
    }

    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen) {
      return mapPolitischeGruppenUndListenverbindungenAuswahlKonfliktIntern(sitzberechnungContext, gebiete, sitzberechnungEntities.getGruppen(), sitzberechnungEntities.getListenverbindungen(),
          (de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen) ergebnisKonflikt);
    }

    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktListenAuswaehlen) {
      return mapListenAuswahlKonfliktIntern(sitzberechnungContext, gebiete, sitzberechnungEntities.getListen(),
          (de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktListenAuswaehlen) ergebnisKonflikt);
    }

    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenAuswaehlen) {
      return mapKandidaturenAuswahlKonfliktIntern(sitzberechnungContext, gebiete, sitzberechnungEntities.getKandidaturen(),
          (de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenAuswaehlen) ergebnisKonflikt);
    }

    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenSortieren) {
      return mapKandidaturenReihenfolgeKonfliktIntern(sitzberechnungContext, gebiete, sitzberechnungEntities.getKandidaturen(),
          (de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenSortieren) ergebnisKonflikt);
    }

    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktSitzeZuteilungArtenAuswaehlen) {
      return mapSitzeZuteilungArtenAuswahlKonfliktIntern(sitzberechnungContext, (de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktSitzeZuteilungArtenAuswaehlen) ergebnisKonflikt, gebiete,
          sitzberechnungEntities.getGruppen(), sitzberechnungEntities.getListen(), sitzberechnungEntities.getKandidaturen());
    }

    throw new UnsupportedOperationException(
        "ErgebnisKonflikt unbekannt [ergebnisKonflikt = " + ergebnisKonflikt.getClass() + ", ergebnisKonfliktArt = " + ergebnisKonflikt.getErgebnisKonfliktArt() + "]");
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapPolitischeGruppenAuswahlKonfliktExtern(ElectSitzberechnungContext sitzberechnungContext,
      Collection<ElectSitzberechnungGruppe> gruppen, ErgebnisKonflikt ergebnisKonflikt, PolitischeGruppenAuswaehlenTypisiertErgebnisKonfliktCustomData customData) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenAuswaehlen.newInstance(keyFromPK(customData.getWahlgebietPK()), keyFromPK(customData.getWahlvorschlaggebietPK()),
        customData.getLosentscheidTyp(), mapExtern(ergebnisKonflikt.getErgebnisKonfliktStatus()), customData.getAnzahlZuLosen(), keysFromPKs(customData.getBeteiligtePolitischeGruppenPKs()),
        keysFromPKs(customData.getGelostePolitischeGruppenPKs()));
  }

  private ErgebnisKonflikt mapPolitischeGruppenAuswahlKonfliktIntern(ElectSitzberechnungContext sitzberechnungContext, Collection<ElectSitzberechnungGebiet> gebiete,
      Collection<ElectSitzberechnungGruppe> gruppen, de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenAuswaehlen ergebnisKonflikt) {
    Ergebnis ergebnis = sitzberechnungContext.getErgebnis();

    Gebiet wahlgebiet = mapEntityIntern(ergebnisKonflikt.getWahlgebietKey(), gebiete);
    Gebiet wahlvorschlaggebiet = mapEntityIntern(ergebnisKonflikt.getWahlvorschlaggebietKey(), gebiete);

    Enum< ? > losentscheidTyp = ergebnisKonflikt.getLosentscheidTyp();
    long anzahlZuLosen = ergebnisKonflikt.getAnzahlAuszuwaehlen();

    List<PolitischeGruppe> auswaehlbar = mapEntitiesIntern(ergebnisKonflikt.getGruppeKeysAuswaehlbar(), gruppen);
    List<PolitischeGruppe> ausgewaehlt = mapEntitiesIntern(ergebnisKonflikt.getGruppeKeysAusgewaehlt(), gruppen);

    ErgebnisKonfliktStatus ergebnisKonfliktStatus = mapIntern(ergebnisKonflikt.getErgebnisKonfliktStatus());

    return createPolitischeGruppenAuswahlKonfliktIntern(getErgebnisKonfliktCustomDataFormatter(), ergebnis, wahlgebiet, wahlvorschlaggebiet, losentscheidTyp, anzahlZuLosen, auswaehlbar, ausgewaehlt,
        ergebnisKonfliktStatus);
  }

  protected static ErgebnisKonflikt createPolitischeGruppenAuswahlKonfliktIntern(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Ergebnis ergebnis, Gebiet wahlgebiet,
      Gebiet wahlvorschlaggebiet, Enum< ? > losentscheidTyp, long anzahlZuLosen, List<PolitischeGruppe> auswaehlbar, List<PolitischeGruppe> ausgewaehlt,
      ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    PolitischeGruppenAuswaehlenTypisiertErgebnisKonfliktCustomData customData = new PolitischeGruppenAuswaehlenTypisiertErgebnisKonfliktCustomData(losentscheidTyp);
    customData.setWahlgebiet(wahlgebiet);
    customData.setWahlvorschlaggebiet(wahlvorschlaggebiet);
    customData.setGelostePolitischeGruppenPKs(pksFromEntitiesIntern(ausgewaehlt));

    ErgebnisKonflikt result = SitzberechnungProGebietHelper.createPolitischeGruppenAuswahlKonflikt(ergebnisKonfliktCustomDataFormatter, ergebnis, customData, anzahlZuLosen, auswaehlbar,
        losentscheidTyp);
    result.setErgebnisKonfliktStatus(ergebnisKonfliktStatus);

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapPolitischeGruppenUndListenverbindungenAuswahlKonfliktExtern(ElectSitzberechnungContext sitzberechnungContext,
      Collection<ElectSitzberechnungGruppe> gruppen, Collection<ElectSitzberechnungListenverbindung> listenverbindungen, ErgebnisKonflikt ergebnisKonflikt,
      PolitischeGruppenUndListenverbindungenAuswaehlenTypisiertErgebnisKonfliktCustomData customData) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen.newInstance(keyFromPK(customData.getWahlgebietPK()),
        keyFromPK(customData.getWahlvorschlaggebietPK()), customData.getLosentscheidTyp(), mapExtern(ergebnisKonflikt.getErgebnisKonfliktStatus()), customData.getAnzahlZuLosen(),
        keysFromPKs(customData.getPolitischeGruppePKsZurAuswahl()), keysFromPKs(customData.getPolitischeGruppePKsGelost()), keysFromPKs(customData.getListenverbindungPKsZurAuswahl()),
        keysFromPKs(customData.getListenverbindungPKsGelost()));
  }

  private ErgebnisKonflikt mapPolitischeGruppenUndListenverbindungenAuswahlKonfliktIntern(ElectSitzberechnungContext sitzberechnungContext, Collection<ElectSitzberechnungGebiet> gebiete,
      Collection<ElectSitzberechnungGruppe> gruppen, Collection<ElectSitzberechnungListenverbindung> listenverbindungen,
      de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktGruppenUndListenverbindungenAuswaehlen ergebnisKonflikt) {
    Ergebnis ergebnis = sitzberechnungContext.getErgebnis();

    Gebiet wahlgebiet = mapEntityIntern(ergebnisKonflikt.getWahlgebietKey(), gebiete);
    Gebiet wahlvorschlaggebiet = mapEntityIntern(ergebnisKonflikt.getWahlvorschlaggebietKey(), gebiete);

    Enum< ? > losentscheidTyp = ergebnisKonflikt.getLosentscheidTyp();
    long anzahlZuLosen = ergebnisKonflikt.getAnzahlAuszuwaehlen();

    List<PolitischeGruppe> gruppenAuswaehlbar = mapEntitiesIntern(ergebnisKonflikt.getGruppeKeysAuswaehlbar(), gruppen);
    List<PolitischeGruppe> gruppenAusgewaehlt = mapEntitiesIntern(ergebnisKonflikt.getGruppeKeysAusgewaehlt(), gruppen);

    List<Listenverbindung> listenverbindungenAuswaehlbar = mapEntitiesIntern(ergebnisKonflikt.getListenverbindungKeysAuswaehlbar(), listenverbindungen);
    List<Listenverbindung> listenverbindungenAusgewaehlt = mapEntitiesIntern(ergebnisKonflikt.getListenverbindungKeysAusgewaehlt(), listenverbindungen);

    ErgebnisKonfliktStatus ergebnisKonfliktStatus = mapIntern(ergebnisKonflikt.getErgebnisKonfliktStatus());

    return createPolitischeGruppenUndListenverbindungenAuswahlKonfliktIntern(getErgebnisKonfliktCustomDataFormatter(), ergebnis, wahlgebiet, wahlvorschlaggebiet, losentscheidTyp, anzahlZuLosen,
        gruppenAuswaehlbar, gruppenAusgewaehlt, listenverbindungenAuswaehlbar, listenverbindungenAusgewaehlt, ergebnisKonfliktStatus);
  }

  protected static ErgebnisKonflikt createPolitischeGruppenUndListenverbindungenAuswahlKonfliktIntern(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Ergebnis ergebnis,
      Gebiet wahlgebiet, Gebiet wahlvorschlaggebiet, Enum< ? > losentscheidTyp, long anzahlZuLosen, List<PolitischeGruppe> gruppenAuswaehlbar, List<PolitischeGruppe> gruppenAusgewaehlt,
      List<Listenverbindung> listenverbindungenAuswaehlbar, List<Listenverbindung> listenverbindungenAusgewaehlt, ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    PolitischeGruppenUndListenverbindungenAuswaehlenTypisiertErgebnisKonfliktCustomData customData = new PolitischeGruppenUndListenverbindungenAuswaehlenTypisiertErgebnisKonfliktCustomData(
        losentscheidTyp);
    customData.setWahlgebiet(wahlgebiet);
    customData.setWahlvorschlaggebiet(wahlvorschlaggebiet);
    customData.setAnzahlZuLosen(anzahlZuLosen);
    customData.setPolitischeGruppePKsZurAuswahl(pksFromEntitiesIntern(gruppenAuswaehlbar));
    customData.setPolitischeGruppePKsGelost(pksFromEntitiesIntern(gruppenAusgewaehlt));
    customData.setListenverbindungPKsZurAuswahl(pksFromEntitiesIntern(listenverbindungenAuswaehlbar));
    customData.setListenverbindungPKsGelost(pksFromEntitiesIntern(listenverbindungenAusgewaehlt));

    String typBeschreibung = ergebnisKonfliktCustomDataFormatter.getMessage(losentscheidTyp, customData.getAnzahlZuLosen());
    String konfliktBeschreibung = customData.createErgebnisKonfliktBeschreibung(ergebnisKonfliktCustomDataFormatter, typBeschreibung, gruppenAuswaehlbar, listenverbindungenAuswaehlbar);

    ErgebnisKonflikt result = SitzberechnungProGebietHelper.createKonflikt(ergebnis, customData, ErgebnisKonfliktArt.LOSENTSCHEID_AUSWAHL_POLITISCHE_GRUPPEN, konfliktBeschreibung);
    result.setErgebnisKonfliktStatus(ergebnisKonfliktStatus);

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapListenAuswahlKonfliktExtern(ElectSitzberechnungContext sitzberechnungContext, Collection<ElectSitzberechnungListe> listen,
      ErgebnisKonflikt ergebnisKonflikt, ListenAuswaehlenTypisiertErgebnisKonfliktCustomData customData) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktListenAuswaehlen.newInstance(keyFromPK(customData.getWahlgebietPK()), keyFromPK(customData.getWahlvorschlaggebietPK()),
        customData.getLosentscheidTyp(), mapExtern(ergebnisKonflikt.getErgebnisKonfliktStatus()), customData.getAnzahlZuLosen(), keysFromPKs(customData.getBeteiligteListenPKs()),
        keysFromPKs(customData.getGelosteListenPKs()));
  }

  private ErgebnisKonflikt mapListenAuswahlKonfliktIntern(ElectSitzberechnungContext sitzberechnungContext, Collection<ElectSitzberechnungGebiet> gebiete, Collection<ElectSitzberechnungListe> listen,
      de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktListenAuswaehlen ergebnisKonflikt) {
    Ergebnis ergebnis = sitzberechnungContext.getErgebnis();

    Gebiet wahlgebiet = mapEntityIntern(ergebnisKonflikt.getWahlgebietKey(), gebiete);
    Gebiet wahlvorschlaggebiet = mapEntityIntern(ergebnisKonflikt.getWahlvorschlaggebietKey(), gebiete);

    Enum< ? > losentscheidTyp = ergebnisKonflikt.getLosentscheidTyp();
    long anzahlZuLosen = ergebnisKonflikt.getAnzahlAuszuwaehlen();

    List<Liste> auswaehlbar = mapEntitiesIntern(ergebnisKonflikt.getListeKeysAuswaehlbar(), listen);
    List<Liste> ausgewaehlt = mapEntitiesIntern(ergebnisKonflikt.getListeKeysAusgewaehlt(), listen);

    ErgebnisKonfliktStatus ergebnisKonfliktStatus = mapIntern(ergebnisKonflikt.getErgebnisKonfliktStatus());

    return createListenAuswahlKonfliktIntern(getErgebnisKonfliktCustomDataFormatter(), ergebnis, wahlgebiet, wahlvorschlaggebiet, losentscheidTyp, anzahlZuLosen, auswaehlbar, ausgewaehlt,
        ergebnisKonfliktStatus);
  }

  protected static ErgebnisKonflikt createListenAuswahlKonfliktIntern(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Ergebnis ergebnis, Gebiet wahlgebiet,
      Gebiet wahlvorschlaggebiet, Enum< ? > losentscheidTyp, long anzahlZuLosen, List<Liste> auswaehlbar, List<Liste> ausgewaehlt, ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    ListenAuswaehlenTypisiertErgebnisKonfliktCustomData customData = new ListenAuswaehlenTypisiertErgebnisKonfliktCustomData(losentscheidTyp);
    customData.setWahlgebiet(wahlgebiet);
    customData.setWahlvorschlaggebiet(wahlvorschlaggebiet);
    customData.setGelosteListenPKs(pksFromEntitiesIntern(ausgewaehlt));

    ErgebnisKonflikt result = SitzberechnungProGebietHelper.createListenAuswahlKonflikt(ergebnisKonfliktCustomDataFormatter, ergebnis, customData, anzahlZuLosen, auswaehlbar, losentscheidTyp);
    result.setErgebnisKonfliktStatus(ergebnisKonfliktStatus);

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapKandidaturenAuswahlKonfliktExtern(ElectSitzberechnungContext sitzberechnungContext,
      Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturen, ErgebnisKonflikt ergebnisKonflikt, KandidaturenAuswaehlenTypisiertErgebnisKonfliktCustomData customData) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenAuswaehlen.newInstance(keyFromPK(customData.getWahlgebietPK()), keyFromPK(customData.getWahlvorschlaggebietPK()),
        keyFromPK(customData.getGruppePK()), keyFromPK(customData.getListePK()), customData.getLosentscheidTyp(), mapExtern(ergebnisKonflikt.getErgebnisKonfliktStatus()),
        customData.getAnzahlZuLosen(), keysFromPKs(customData.getBeteiligteKandidaturenPKs()), keysFromPKs(customData.getGelosteKandidaturenPKs()));
  }

  @SuppressWarnings("unchecked")
  private ErgebnisKonflikt mapKandidaturenAuswahlKonfliktIntern(ElectSitzberechnungContext sitzberechnungContext, Collection<ElectSitzberechnungGebiet> gebiete,
      Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturen, de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenAuswaehlen ergebnisKonflikt) {
    Ergebnis ergebnis = sitzberechnungContext.getErgebnis();

    Gebiet wahlgebiet = mapEntityIntern(ergebnisKonflikt.getWahlgebietKey(), gebiete);
    Gebiet wahlvorschlaggebiet = mapEntityIntern(ergebnisKonflikt.getWahlvorschlaggebietKey(), gebiete);

    GruppePK gruppePK = pkFromKey(GruppePK.class, ergebnisKonflikt.getGruppeKey());
    WahlvorschlagPK listePK = pkFromKey(WahlvorschlagPK.class, ergebnisKonflikt.getListeKey());

    Enum< ? > losentscheidTyp = ergebnisKonflikt.getLosentscheidTyp();
    long anzahlZuLosen = ergebnisKonflikt.getAnzahlAuszuwaehlen();

    Collection<AbstractElectSitzberechnungKandidatur> entitiesExtern = new ArrayList<>();
    entitiesExtern.addAll(kandidaturen);

    List<Kandidatur> auswaehlbar = mapEntitiesIntern(ergebnisKonflikt.getKandidaturKeysAuswaehlbar(), entitiesExtern);
    List<Kandidatur> ausgewaehlt = mapEntitiesIntern(ergebnisKonflikt.getKandidaturKeysAusgewaehlt(), entitiesExtern);

    ErgebnisKonfliktStatus ergebnisKonfliktStatus = mapIntern(ergebnisKonflikt.getErgebnisKonfliktStatus());

    return createKandidaturenAuswahlKonfliktIntern(getErgebnisKonfliktCustomDataFormatter(), ergebnis, wahlgebiet, wahlvorschlaggebiet, gruppePK, listePK, losentscheidTyp, anzahlZuLosen, auswaehlbar,
        ausgewaehlt, ergebnisKonfliktStatus);
  }

  protected static ErgebnisKonflikt createKandidaturenAuswahlKonfliktIntern(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Ergebnis ergebnis, Gebiet wahlgebiet,
      Gebiet wahlvorschlaggebiet, @Nullable GruppePK gruppePK, @Nullable WahlvorschlagPK listePK, Enum< ? > losentscheidTyp, long anzahlZuLosen, List<Kandidatur> auswaehlbar,
      List<Kandidatur> ausgewaehlt, ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    KandidaturenAuswaehlenTypisiertErgebnisKonfliktCustomData customData = new KandidaturenAuswaehlenTypisiertErgebnisKonfliktCustomData(losentscheidTyp);
    customData.setWahlgebiet(wahlgebiet);
    customData.setWahlvorschlaggebiet(wahlvorschlaggebiet);
    customData.setGruppePK(gruppePK);
    customData.setListePK(listePK);
    customData.setGelosteKandidaturenPKs(pksFromEntitiesIntern(ausgewaehlt));

    Object[] params = new Object[0];

    if (gruppePK != null) { // ELECTKOM-3050
      Set<Gruppe> gruppen = auswaehlbar.stream().map(Kandidatur::getGruppe).filter(Objects::nonNull).collect(toSet());

      if (gruppen.size() == 1) {
        params = new Object[] {ergebnisKonfliktCustomDataFormatter.extractPolitischeGruppeDisplayName(gruppen.iterator().next())};
      }
    } else if (listePK != null) { // ELECTKOM-3050
      Set<Wahlvorschlag> listen = auswaehlbar.stream().map(Kandidatur::getWahlvorschlag).filter(Objects::nonNull).collect(toSet());

      if (listen.size() == 1) {
        params = new Object[] {ergebnisKonfliktCustomDataFormatter.extractWahlvorschlagDisplayName(listen.iterator().next())};
      }
    }

    ErgebnisKonflikt result = SitzberechnungProGebietHelper.createKandidaturAuswahlKonflikt(ergebnisKonfliktCustomDataFormatter, ergebnis, customData, anzahlZuLosen, auswaehlbar, losentscheidTyp,
        params);
    result.setErgebnisKonfliktStatus(ergebnisKonfliktStatus);

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapKandidaturenReihenfolgeKonfliktExtern(ElectSitzberechnungContext sitzberechnungContext,
      Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturen, ErgebnisKonflikt ergebnisKonflikt, KandidaturenReihenfolgeTypisiertErgebnisKonfliktCustomData customData) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenSortieren.newInstance(keyFromPK(customData.getWahlgebietPK()), keyFromPK(customData.getWahlvorschlaggebietPK()),
        keyFromPK(customData.getGruppePK()), keyFromPK(customData.getListePK()), customData.getLosentscheidTyp(), mapExtern(ergebnisKonflikt.getErgebnisKonfliktStatus()),
        keysFromPKs(customData.getBeteiligteKandidaturenPKs()), keysFromPKs(customData.getGelosteKandidaturenPKs()));
  }

  @SuppressWarnings("unchecked")
  private ErgebnisKonflikt mapKandidaturenReihenfolgeKonfliktIntern(ElectSitzberechnungContext sitzberechnungContext, Collection<ElectSitzberechnungGebiet> gebiete,
      Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturen, de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktKandidaturenSortieren ergebnisKonflikt) {
    Ergebnis ergebnis = sitzberechnungContext.getErgebnis();

    Gebiet wahlgebiet = mapEntityIntern(ergebnisKonflikt.getWahlgebietKey(), gebiete);
    Gebiet wahlvorschlaggebiet = mapEntityIntern(ergebnisKonflikt.getWahlvorschlaggebietKey(), gebiete);

    GruppePK gruppePK = pkFromKey(GruppePK.class, ergebnisKonflikt.getGruppeKey());
    WahlvorschlagPK listePK = pkFromKey(WahlvorschlagPK.class, ergebnisKonflikt.getListeKey());

    Enum< ? > losentscheidTyp = ergebnisKonflikt.getLosentscheidTyp();

    Collection<AbstractElectSitzberechnungKandidatur> entitiesExtern = new ArrayList<>();
    entitiesExtern.addAll(kandidaturen);

    List<Kandidatur> sortierbar = mapEntitiesIntern(ergebnisKonflikt.getKandidaturKeysSortierbar(), entitiesExtern);
    List<Kandidatur> sortiert = mapEntitiesIntern(ergebnisKonflikt.getKandidaturKeysSortiert(), entitiesExtern);

    ErgebnisKonfliktStatus ergebnisKonfliktStatus = mapIntern(ergebnisKonflikt.getErgebnisKonfliktStatus());

    return createKandidaturenReihenfolgeKonfliktIntern(getErgebnisKonfliktCustomDataFormatter(), ergebnis, wahlgebiet, wahlvorschlaggebiet, gruppePK, listePK, losentscheidTyp, sortierbar, sortiert,
        ergebnisKonfliktStatus);
  }

  protected static ErgebnisKonflikt createKandidaturenReihenfolgeKonfliktIntern(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Ergebnis ergebnis, Gebiet wahlgebiet,
      Gebiet wahlvorschlaggebiet, @Nullable GruppePK gruppePK, @Nullable WahlvorschlagPK listePK, Enum< ? > losentscheidTyp, List<Kandidatur> sortierbar, List<Kandidatur> sortiert,
      ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    KandidaturenReihenfolgeTypisiertErgebnisKonfliktCustomData customData = new KandidaturenReihenfolgeTypisiertErgebnisKonfliktCustomData(losentscheidTyp);
    customData.setWahlgebiet(wahlgebiet);
    customData.setWahlvorschlaggebiet(wahlvorschlaggebiet);
    customData.setGruppePK(gruppePK);
    customData.setListePK(listePK);
    customData.setGelosteKandidaturenPKs(pksFromEntitiesIntern(sortiert));

    Object[] params = new Object[0];

    if (gruppePK != null) { // ELECTKOM-3050
      Set<Gruppe> gruppen = sortierbar.stream().map(Kandidatur::getGruppe).filter(Objects::nonNull).collect(toSet());

      if (gruppen.size() == 1) {
        params = new Object[] {ergebnisKonfliktCustomDataFormatter.extractPolitischeGruppeDisplayName(gruppen.iterator().next())};
      }
    } else if (listePK != null) { // ELECTKOM-3050
      Set<Wahlvorschlag> listen = sortierbar.stream().map(Kandidatur::getWahlvorschlag).filter(Objects::nonNull).collect(toSet());

      if (listen.size() == 1) {
        params = new Object[] {ergebnisKonfliktCustomDataFormatter.extractWahlvorschlagDisplayName(listen.iterator().next())};
      }
    }

    ErgebnisKonflikt result = SitzberechnungProGebietHelper.createKandidaturReihenfolgeKonflikt(ergebnisKonfliktCustomDataFormatter, ergebnis, customData, sortierbar, losentscheidTyp, params);
    result.setErgebnisKonfliktStatus(ergebnisKonfliktStatus);

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapSitzeZuteilungArtenAuswahlKonfliktExtern(ElectSitzberechnungContext sitzberechnungContext, ErgebnisKonflikt ergebnisKonflikt,
      SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData<SitzeZuteilungArt> customData) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktSitzeZuteilungArtenAuswaehlen.newInstance(keyFromPK(customData.getWahlgebietPK()), keyFromPK(customData.getWahlvorschlaggebietPK()),
        customData.getLosentscheidTyp(), mapExtern(ergebnisKonflikt.getErgebnisKonfliktStatus()), customData.getAnzahlZuLosen(), keysFromPKs(customData.getBeteiligteEntityPKs()),
        SitzeZuteilungArt.extern(customData.getBeteiligteOptionen()), SitzeZuteilungArt.extern(customData.getGelosteOptionen()));
  }

  private ErgebnisKonflikt mapSitzeZuteilungArtenAuswahlKonfliktIntern(ElectSitzberechnungContext sitzberechnungContext,
      de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktSitzeZuteilungArtenAuswaehlen ergebnisKonflikt, Collection<ElectSitzberechnungGebiet> gebiete, Collection<ElectSitzberechnungGruppe> gruppen,
      Collection<ElectSitzberechnungListe> listen, Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturen) {
    Ergebnis ergebnis = sitzberechnungContext.getErgebnis();

    Gebiet wahlgebiet = mapEntityIntern(ergebnisKonflikt.getWahlgebietKey(), gebiete);
    Gebiet wahlvorschlaggebiet = mapEntityIntern(ergebnisKonflikt.getWahlvorschlaggebietKey(), gebiete);

    Enum< ? > losentscheidTyp = ergebnisKonflikt.getLosentscheidTyp();
    long anzahlZuLosen = ergebnisKonflikt.getAnzahlAuszuwaehlen();

    List<String> sitzeZuteilungArtenEntityKeys = ergebnisKonflikt.getSitzeZuteilungArtenEntityKeys();
    boolean keysUnbekanntZulaessig = true; // Ein Gruppe-Key wird nicht in den Gebiet-Keys gefunden werden und soll dann (erstmal) zu keiner Exception führen...

    List<Kandidatur> kandidaturenIntern = mapEntitiesIntern(sitzeZuteilungArtenEntityKeys, new ArrayList(kandidaturen), keysUnbekanntZulaessig);
    List<Wahlvorschlag> wahlvorschlaegeIntern = new ArrayList<>(mapEntitiesIntern(sitzeZuteilungArtenEntityKeys, listen, keysUnbekanntZulaessig));
    List<Gruppe> gruppenIntern = new ArrayList<>(mapEntitiesIntern(sitzeZuteilungArtenEntityKeys, gruppen, keysUnbekanntZulaessig));
    List<Gebiet> gebieteIntern = new ArrayList<>(mapEntitiesIntern(sitzeZuteilungArtenEntityKeys, gebiete, keysUnbekanntZulaessig));

    Set<String> keysUnbekannt = new HashSet<>(sitzeZuteilungArtenEntityKeys);
    keysUnbekannt.removeAll(keysFromEntitiesIntern(kandidaturenIntern));
    keysUnbekannt.removeAll(keysFromEntitiesIntern(wahlvorschlaegeIntern));
    keysUnbekannt.removeAll(keysFromEntitiesIntern(gruppenIntern));
    keysUnbekannt.removeAll(keysFromEntitiesIntern(gebieteIntern));

    if (!keysUnbekannt.isEmpty()) {
      throw new IllegalArgumentException("Entity-Key unbekannt [keys = " + keysUnbekannt + "]");
    }

    EnumSet<SitzeZuteilungArt> verfuegbar = EnumSet.allOf(SitzeZuteilungArt.class);
    List<SitzeZuteilungArt> auswaehlbar = SitzeZuteilungArt.intern(ergebnisKonflikt.getSitzeZuteilungArtenAuswaehlbar());
    List<SitzeZuteilungArt> ausgewaehlt = SitzeZuteilungArt.intern(ergebnisKonflikt.getSitzeZuteilungArtenAusgewaehlt());

    SitzberechnungsalgorithmusOptionContext<SitzeZuteilungArt> context = new SitzberechnungsalgorithmusOptionContext<>(losentscheidTyp, gebieteIntern, gruppenIntern, wahlvorschlaegeIntern,
        kandidaturenIntern, verfuegbar);

    ErgebnisKonfliktStatus ergebnisKonfliktStatus = mapIntern(ergebnisKonflikt.getErgebnisKonfliktStatus());

    return createSitzeZuteilungArtenAuswahlKonfliktIntern(getErgebnisKonfliktCustomDataFormatter(), ergebnis, wahlgebiet, wahlvorschlaggebiet, context, anzahlZuLosen, auswaehlbar, ausgewaehlt,
        ergebnisKonfliktStatus);
  }

  protected static ErgebnisKonflikt createSitzeZuteilungArtenAuswahlKonfliktIntern(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Ergebnis ergebnis, Gebiet wahlgebiet,
      Gebiet wahlvorschlaggebiet, SitzberechnungsalgorithmusOptionContext<SitzeZuteilungArt> context, long anzahlZuLosen, List<SitzeZuteilungArt> auswaehlbar, List<SitzeZuteilungArt> ausgewaehlt,
      ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData<SitzeZuteilungArt> customData = new SitzberechnungsalgorithmusOptionenAuswaehlenTypisiertErgebnisKonfliktCustomData<>();
    customData.setWahlgebiet(wahlgebiet);
    customData.setWahlvorschlaggebiet(wahlvorschlaggebiet);
    customData.setGelosteOptionen(ausgewaehlt);

    ErgebnisKonflikt result = SitzberechnungProGebietHelper.createSitzberechnungsalgorithmusOptionenAuswahlKonflikt(ergebnis, customData, context, ergebnisKonfliktCustomDataFormatter, anzahlZuLosen,
        auswaehlbar);

    result.setErgebnisKonfliktStatus(ergebnisKonfliktStatus);

    return result;
  }

  protected de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktStatus mapExtern(ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    return de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktStatus.valueOf(ergebnisKonfliktStatus.name());
  }

  protected ErgebnisKonfliktStatus mapIntern(de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonfliktStatus ergebnisKonfliktStatus) {
    return ErgebnisKonfliktStatus.valueOf(ergebnisKonfliktStatus.name());
  }

  public enum SitzeZuteilungArt implements SitzberechnungsalgorithmusOption<SitzeZuteilungArt> {

    POSITION(de.ivu.elect.sitzberechnung.SitzeZuteilungArt.POSITION),

    ANZAHL_STIMMEN(de.ivu.elect.sitzberechnung.SitzeZuteilungArt.ANZAHL_STIMMEN),

    ;

    private final de.ivu.elect.sitzberechnung.SitzeZuteilungArt extern;

    SitzeZuteilungArt(de.ivu.elect.sitzberechnung.SitzeZuteilungArt extern) {
      this.extern = extern;
    }

    @Override
    public Long getId() {
      return extern.getId();
    }

    @Override
    public String formatName(Messages messages, Object... params) {
      return messages.lokalisiere(this, params);
    }

    public static List<de.ivu.elect.sitzberechnung.SitzeZuteilungArt> extern(Collection<SitzeZuteilungArt> intern) {
      return intern.stream().map(value -> de.ivu.elect.sitzberechnung.SitzeZuteilungArt.valueOf(value.name())).collect(toList());
    }

    public static List<SitzeZuteilungArt> intern(Collection<de.ivu.elect.sitzberechnung.SitzeZuteilungArt> extern) {
      return extern.stream().map(value -> SitzeZuteilungArt.valueOf(value.name())).collect(toList());
    }
  }
}
