
package de.ivu.elect.core.exchange.mapper;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.dokumente.entity.DokumentVorlage;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatus;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatusVergabe;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.fremdkandidatur.entity.Fremdkandidatur;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.GebietsartTK;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsarteigenschaft;
import de.ivu.elect.business.gebietsbaum.entity.GebietsarteigenschaftTK;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeArt;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietStatus;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietTK;
import de.ivu.elect.business.internet.entity.InternetKonfigurationExchangeObject;
import de.ivu.elect.business.kandidatenportal.entity.WvpWarnung;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Ersatzkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturQuelle;
import de.ivu.elect.business.kandidatur.entity.KandidaturStatus;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturArt;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.kandidatur.entity.Waehlbarkeit;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfiguration;
import de.ivu.elect.business.staat.entity.StaatenGruppe;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.veranstaltung.entity.Teilnahme;
import de.ivu.elect.business.veranstaltung.entity.Veranstaltung;
import de.ivu.elect.business.wahl.control.Wahldaten;
import de.ivu.elect.business.wahl.entity.WahlKonfiguration;
import de.ivu.elect.business.wahl.entity.WahlParameter;
import de.ivu.elect.business.wahlausschuss.entity.WahlausschussEntschaedigung;
import de.ivu.elect.business.wahlausschuss.entity.Wahlausschussmitglied;
import de.ivu.elect.business.wahlausschuss.entity.Wahlausschussrolle;
import de.ivu.elect.business.wahlrechtspruefung.entity.Pruefkriterium;
import de.ivu.elect.business.wahlrechtspruefung.entity.Wahlrechtspruefung;
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.WahlvorschlagStatus;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.core.exchange.internet.mapper.InternetMapper;
import de.ivu.elect.core.exchange.wahlbeteiligung.mapper.WahlbeteiligungMapper;
import de.ivu.elect.exchange.EntityMapper;
import de.ivu.elect.exchange.dokumente.mapper.DokumentVorlageMapper;
import de.ivu.elect.exchange.gebietsbaum.mapper.GebietMapper;
import de.ivu.elect.exchange.gebietsbaum.mapper.GebietPolygonMapperUtil;
import de.ivu.elect.exchange.image.mapper.BildMapper;
import de.ivu.elect.exchange.person.mapper.PersonMapper;
import de.ivu.elect.exchange.person.mapper.TeilnahmeMapper;
import de.ivu.elect.exchange.staat.mapper.StaatMapper;
import de.ivu.elect.exchange.veranstaltung.VeranstaltungMapper;
import de.ivu.elect.exchange.wahl.WahlKonfigurationMapper;
import de.ivu.elect.exchange.wahl.mapper.PersonPruefungKonfigurationMapper;
import de.ivu.elect.exchange.wahl.mapper.WahlParameterMapper;
import de.ivu.elect.interfaces.core.Bild;
import de.ivu.elect.interfaces.core.KandidaturVerknuepfung;
import de.ivu.elect.interfaces.core.Person;
import de.ivu.elect.interfaces.core.PersonReferenz;
import de.ivu.elect.interfaces.core.VerknuepfungsArt;
import de.ivu.elect.interfaces.core.VerknuepfungsGrundlage;
import de.ivu.elect.interfaces.core.VerknuepfungsStatus;
import de.ivu.elect.interfaces.core.Verwaltungsart;
import de.ivu.elect.interfaces.core.Wahl;
import de.ivu.elect.interfaces.core.Wahlgang;
import de.ivu.elect.interfaces.core.WahlvorschlagReferenz;
import de.ivu.elect.interfaces.core.zulassungspruefung.Zulassungspruefung;
import de.ivu.elect.interfaces.gebietgeometrie.GebietGeometrie;
import de.ivu.elect.interfaces.was.wahlbeteiligung.WahlbeteiligungExchange;
import de.ivu.elect.interfaces.wes.Stimmzettelerfassung;
import de.ivu.elect.interfaces.wvp.Kandidatenportal;
import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.was.exchange.aufgabe.AufgabeMapper;
import de.ivu.elect.was.exchange.stimmeingabe.StimmeingabePlausiKonfigurationMapper;
import de.ivu.elect.wus.exchange.wahlrechtspruefung.mapper.ZulassungspruefungMapper;
import de.ivu.elect.wvp.exchange.mapper.KandidatenportalMapper;


import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

public class WahlInternExternMapper {

  private static final Logger LOG = LoggerFactory.getLogger(WahlInternExternMapper.class);

  public static final String FORMAT_NAME = "file.format.wahl.was";

  @Inject
  protected transient WahlInternExternMapperUtil wahlInternExternMapperUtil;

  @Inject
  protected transient WahlKonfigurationMapper wahlKonfigurationMapper;

  @Inject
  protected transient InternetMapper internetMapper;

  @Inject
  protected transient GebietPolygonMapperUtil gebietPolygonMapperUtil;

  @Inject
  protected transient WahlbeteiligungMapper wahlbeteiligungMapper;

  @Inject
  protected transient StimmeingabePlausiKonfigurationMapper stimmeingabePlausiKonfigurationMapper;

  @Inject
  protected transient WahlParameterMapper wahlParameterMapper;

  @Inject
  protected transient BildMapper bildMapper;

  @Inject
  protected transient StaatMapper staatMapper;

  @Inject
  protected transient GebietMapper gebietMapper;

  @Inject
  protected transient PersonMapper personMapper;

  @Inject
  protected transient VeranstaltungMapper veranstaltungMapper;

  @Inject
  protected transient TeilnahmeMapper teilnahmeMapper;

  @Inject
  protected transient StimmzettelMapper stimmzettelMapper;

  @Inject
  protected transient DokumentVorlageMapper dokumentVorlageMapper;

  @Inject
  protected transient ZulassungspruefungMapper zulassungspruefungMapper;

  @Inject
  protected transient KandidatenportalMapper kandidatenportalMapper;

  @Inject
  protected transient PersonPruefungKonfigurationMapper personPruefungKonfigurationMapper;

  @Inject
  protected transient AufgabeMapper aufgabeMapper;

  public WahlInternExternMapper() {
    super();
  }

  public Wahl mappe(Wahldaten wahldatenIntern, List<DokumentVorlage> dokumentVorlagen, List<WahlParameter> wahlParameters) {
    de.ivu.elect.business.wahl.entity.Wahl wahlIntern = wahldatenIntern.getWahl();
    Wahl wahlExtern = new Wahl();

    List<Veranstaltung> veranstaltungenIntern = wahldatenIntern.getVeranstaltungen();
    Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern = createTeilnahmeMap(veranstaltungenIntern);

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = wahldatenIntern.getEntityAufStimmzettelDaten();

    wahlExtern.setCustomData(wahlIntern.getCustomDataXml());
    wahlExtern.setFarbe(wahlIntern.getFarbe());
    wahlExtern.setName(wahlIntern.getName());
    wahlExtern.setKurzname(wahlIntern.getKurzname());
    wahlExtern.setKennzeichen(wahlIntern.getKennzeichen());
    wahlExtern.setAnzahlStimmzettel(wahlIntern.getAnzahlStimmzettel());

    wahlExtern.setWahldatum(wahlIntern.getWahldatum());
    wahlExtern.setWahlterminNummer(wahlIntern.getWahltermin().getNummer());
    wahlExtern.setWahlart(wahlIntern.getWahlart().getName());
    wahlExtern.setQuorum(wahlIntern.getQuorumProzenthuerde());
    wahlExtern.setUnterstuetzerErfassbar(wahlIntern.getUnterstuetzerErfassbar());
    wahlExtern.setAnzahlUnterstuetzerEintragungsliste(wahlIntern.getAnzahlUnterstuetzerPerEintragungsliste());
    wahlExtern.setWahlgang(mappe(wahlIntern.getWahlgang()));

    mappeGebiete(wahlExtern, wahldatenIntern.getGebiete(), wahldatenIntern);
    mappeGruppen(wahlExtern, wahldatenIntern.getGruppen());
    mappeGruppenImGebiet(wahlExtern, wahldatenIntern.getGruppenImGebiet(), wahldatenIntern::getStimmen, entityAufStimmzettelDaten);
    mappeWahlvorschlaege(wahlExtern, wahldatenIntern.getWahlvorschlaege());
    mappeKandidaturen(wahlExtern, wahldatenIntern.getKandidaturen(), teilnahmenInternPerMapPersonIntern, entityAufStimmzettelDaten);
    mappeGewaehltVorperiode(wahlExtern, wahldatenIntern.getGewaehltVorperiode());
    mappeSitzeVorperiode(wahlExtern, wahldatenIntern.getSitzeVorperiode());
    mappeFremdkandidaturen(wahlExtern, wahldatenIntern.getFremdkandidaturen(), teilnahmenInternPerMapPersonIntern);

    mapWahlvorschlagsPersonen(wahlExtern, wahldatenIntern.getWahlvorschlagsPersonen(), teilnahmenInternPerMapPersonIntern);

    mappeWahlausschussmitglieder(wahlExtern, wahldatenIntern.getWahlausschussmitglieder(), teilnahmenInternPerMapPersonIntern);
    mappeWahlausschussEntschaedigungen(wahlExtern, wahldatenIntern.getWahlausschussEntschaedigungen());

    mappeStaatenGruppen(wahlExtern, wahldatenIntern.getStaatenGruppen());
    mappeGebietsgruppierungen(wahlExtern, wahldatenIntern.getGebietsgruppierungen());

    wahlExtern.setGebietGeometrie(new GebietGeometrie());
    mappeGeometrien(wahlExtern, wahldatenIntern.getGebietPolygonPoints());

    Stimmzettelerfassung stimmzettelerfassung = new Stimmzettelerfassung();

    stimmzettelerfassung.setWahlart(wahlExtern.getWahlart());
    stimmzettelerfassung.setWahldatum(wahlExtern.getWahldatum());
    stimmzettelerfassung.setWahlterminNummer(wahlExtern.getWahlterminNummer());

    mappeStimmzettelerfassung(stimmzettelerfassung, wahldatenIntern.getStimmzettel(), wahldatenIntern.getStimmzettelKopfdaten());
    if (!stimmzettelerfassung.getStimmzettelImGebiet().isEmpty()) {
      wahlExtern.setStimmzettelerfassung(stimmzettelerfassung);
    }

    wahlExtern.setWahlKonfiguration(wahlKonfigurationMapper.mappe(getWahlKonfiguration(wahldatenIntern)));
    wahlExtern.setInternetKonfiguration(internetMapper.mappe(getInternetExchange(wahldatenIntern)));

    mappeDokumentVorlagen(wahlExtern.getDokumentVorlagen(), dokumentVorlagen);
    mappeWahlParameter(wahlExtern.getWahlparameters(), wahlParameters);

    wahlExtern.setWahlbeteiligungExchange(mappeWahlbeteiligung(wahldatenIntern));

    wahlExtern.setStimmeingabePlausiKonfigurationen(stimmeingabePlausiKonfigurationMapper.mappe(wahldatenIntern.getStimmeingabePlausiKonfigurationen()));

    mapVeranstaltungen(wahlExtern, veranstaltungenIntern);

    mapZulassungspruefung(wahlExtern, wahldatenIntern);

    mapKandidatenportal(wahlExtern, wahldatenIntern);

    mappePersonPruefungKonfigurationen(wahlExtern, wahldatenIntern);

    // ELECT-11327, Verknuepfungen erst NACH dem Mappen aller Personendaten mappen, cf. AbstractWahlExternInternMapper#mapPersonenVerknuepfungen!!!
    mapPersonenVerknuepfungen(wahlExtern, wahldatenIntern.getPersonenVerknuepfungen());

    mappeNiederschriftenKorrekturen(wahlExtern, wahldatenIntern);

    if (!wahldatenIntern.getAufgaben().isEmpty()) {
      wahlExtern.setAufgabenliste(aufgabeMapper.mappeAufgabenExtern(wahldatenIntern.getAufgaben()));
    }

    return wahlExtern;
  }

  protected void mappeNiederschriftenKorrekturen(Wahl wahlExtern, Wahldaten wahldatenIntern) {
    // nichts zu tun, siehe WAS
  }

  // ELECT-10913
  private void mapZulassungspruefung(Wahl wahlExtern, Wahldaten wahldatenIntern) {
    Zulassungspruefung zulassungspruefung = new Zulassungspruefung();

    Collection<Pruefkriterium> pruefkriterien = wahldatenIntern.getPruefkriterien();
    zulassungspruefung.setPruefkriterien(pruefkriterien.stream().map(zulassungspruefungMapper::extern).collect(toList()));

    Collection<Wahlrechtspruefung> wahlrechtspruefungen = wahldatenIntern.getWahlrechtspruefungen();
    zulassungspruefung.setWahlrechtspruefungen(wahlrechtspruefungen.stream().map(zulassungspruefungMapper::extern).collect(toList()));

    wahlExtern.setZulassungspruefung(zulassungspruefung);
  }

  // ELECT-11642
  protected void mapKandidatenportal(Wahl wahlExtern, Wahldaten wahldatenIntern) {
    Kandidatenportal kandidatenportal = new Kandidatenportal();

    Collection<WvpWarnung> wvpWarnungen = wahldatenIntern.getWvpWarnungen();
    kandidatenportal.setWvpWarnungen(wvpWarnungen.stream().map(kandidatenportalMapper::extern).collect(toList()));

    wahlExtern.setKandidatenportal(kandidatenportal);
  }

  private void mappeStimmzettelerfassung(Stimmzettelerfassung stimmzettelerfassung, List<Stimmzettel> stimmzettelListe, List<StimmzettelKopfdatum> stimmzettelKopfdaten) {
    Map<Gebiet, List<Stimmzettel>> stimmzettelNachGebieten = stimmzettelListe.stream().collect(groupingBy(Stimmzettel::getGebiet));
    Map<Gebiet, List<StimmzettelKopfdatum>> stimmzettelKopfdatenNachGebieten = stimmzettelKopfdaten.stream()
        .collect(groupingBy(StimmzettelKopfdatum::getGebiet));

    Set<Gebiet> gebiete = new HashSet<>();
    gebiete.addAll(stimmzettelNachGebieten.keySet());
    gebiete.addAll(stimmzettelKopfdatenNachGebieten.keySet());
    for (Gebiet gebiet : gebiete) {

      List<Stimmzettel> stimmzettelImGebietList = stimmzettelNachGebieten.getOrDefault(gebiet, emptyList());
      List<StimmzettelKopfdatum> stimmzettelKopfdatenImGebiet = stimmzettelKopfdatenNachGebieten.getOrDefault(gebiet, emptyList());

      stimmzettelMapper.mappeStimmzettel(stimmzettelerfassung, gebiet, stimmzettelImGebietList, stimmzettelKopfdatenImGebiet);
    }
  }

  private Map<Personendaten, Collection<Teilnahme>> createTeilnahmeMap(List<Veranstaltung> veranstaltungen) {
    Map<Personendaten, Collection<Teilnahme>> result = new HashMap<>();

    for (Veranstaltung veranstaltung : veranstaltungen) {
      for (Teilnahme teilnahme : veranstaltung.getTeilnahmesByVeranstaltung()) {
        Personendaten personendaten = teilnahme.getPersonendaten();

        result.putIfAbsent(personendaten, new ArrayList<>());
        result.get(personendaten).add(teilnahme);
      }
    }

    return result;
  }

  private void mapVeranstaltungen(Wahl wahlExtern, List<Veranstaltung> veranstaltungenIntern) {
    wahlExtern.getVeranstaltungList().addAll(veranstaltungMapper.mapVeranstaltung(veranstaltungenIntern));
  }

  protected Wahlgang mappe(de.ivu.elect.business.wahl.entity.Wahlgang wahlgang) {
    return Wahlgang.valueOf(wahlgang.name());

  }

  protected WahlbeteiligungExchange mappeWahlbeteiligung(Wahldaten wahldatenIntern) {
    return wahlbeteiligungMapper.mappe(wahldatenIntern.getWahlbeteiligungVerlaeufe());
  }

  private InternetKonfigurationExchangeObject getInternetExchange(Wahldaten wahldatenIntern) {
    return new InternetKonfigurationExchangeObject(wahldatenIntern.getInternetSeiten());
  }

  private void mappeDokumentVorlagen(List<de.ivu.elect.interfaces.core.DokumentVorlage> targets, List<DokumentVorlage> sources) {
    targets.addAll(dokumentVorlageMapper.extern(sources));
  }

  private void mappeWahlParameter(List<de.ivu.elect.interfaces.core.WahlParameter> targets, List<WahlParameter> sources) {
    targets.addAll(wahlParameterMapper.extern(sources));
  }

  protected void mappeFremdkandidaturen(Wahl wahlExtern, List<Fremdkandidatur> fremdkandidaturen, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    // in CORE nichts zu tun... => WUS
  }

  private WahlKonfiguration getWahlKonfiguration(Wahldaten wahldatenIntern) {

    List<Gebiet> gebiete = wahldatenIntern.getGebiete();
    Set<Gebietsart> gebietsarten = gebiete.stream().map(Gebiet::getGebietsart).collect(toSet());
    Optional<Gebiet> fehlendeGebietsarteigenschaftBeiGebiet = gebiete.stream().filter(g -> {
      g.resetCachedGebietsarteigenschaft();
      return g.getGebietsarteigenschaft() == null;
    }).findAny();
    if (fehlendeGebietsarteigenschaftBeiGebiet.isPresent()) {

      Gebiet gebiet = fehlendeGebietsarteigenschaftBeiGebiet.get();

      throw new ElectCommonException("wahl.konfiguration.fehlerhaft.gebietsart.nicht.in.wahlart.konfiguriert", gebiet.getGebietsart().getName(),
          gebiet.getNummer(),
          wahldatenIntern.getWahl().getWahlart().getName(), gebiet.getGebietsart().getTitel(), wahldatenIntern.getWahl().getWahlart().getTitel());
    }
    Set<Gebietsarteigenschaft> gebietsarteigenschaften = gebiete.stream().map(Gebiet::getGebietsarteigenschaft).collect(toSet());

    Map<GebietsarteigenschaftTK, Gebietsarteigenschaft> gebietsarteigenschaftTKGebietsarteigenschaftMap = GebietsarteigenschaftTK.mappeGebietsarteigenschaftNachTK(
        gebietsarteigenschaften);
    Map<GebietsartTK, Gebietsart> gebietsartTKGebietsartMap = GebietsartTK.mappeGebietsartNachTK(gebietsarten);

    for (Gebietsarteigenschaft gebietsarteigenschaft : wahldatenIntern.getWahl().getWahlart().getGebietsarteigenschaften()) {
      if (!gebietsarteigenschaftTKGebietsarteigenschaftMap.containsKey(gebietsarteigenschaft.getGebietsarteigenschaftTK())) {
        gebietsarteigenschaftTKGebietsarteigenschaftMap.put(gebietsarteigenschaft.getGebietsarteigenschaftTK(), gebietsarteigenschaft);
      }
      if (!gebietsartTKGebietsartMap.containsKey(gebietsarteigenschaft.getGebietsart().getGebietsartTK())) {
        gebietsartTKGebietsartMap.put(gebietsarteigenschaft.getGebietsart().getGebietsartTK(), gebietsarteigenschaft.getGebietsart());
      }
    }

    return new WahlKonfiguration(singletonList(wahldatenIntern.getWahl().getWahlart()), new ArrayList<>(gebietsartTKGebietsartMap.values()),
        new ArrayList<>(gebietsarteigenschaftTKGebietsarteigenschaftMap.values()));
  }

  private void mappeWahlausschussEntschaedigungen(Wahl wahlExtern, List<WahlausschussEntschaedigung> wahlausschussEntschaedigungen) {
    for (WahlausschussEntschaedigung wahlausschussEntschaedigung : wahlausschussEntschaedigungen) {
      de.ivu.elect.interfaces.core.WahlausschussEntschaedigung wahlausschussEntschaedigungExtern = new de.ivu.elect.interfaces.core.WahlausschussEntschaedigung();
      wahlausschussEntschaedigungExtern.setWaehrungAlsIsoCode(wahlausschussEntschaedigung.getWaehrungAlsIsoCode());
      wahlausschussEntschaedigungExtern.setBetrag(wahlausschussEntschaedigung.getBetrag());
      wahlausschussEntschaedigungExtern.setRolle(mappe(wahlausschussEntschaedigung.getWahlausschussrolle()));
      wahlExtern.getWahlausschussEntschaedigungen().add(wahlausschussEntschaedigungExtern);
    }
  }

  protected void mappeStaatenGruppen(Wahl wahlExtern, List<StaatenGruppe> staatenGruppenIntern) {
    for (StaatenGruppe staatenGruppeIntern : staatenGruppenIntern) {
      de.ivu.elect.interfaces.core.StaatenGruppe staatenGruppeExtern = new de.ivu.elect.interfaces.core.StaatenGruppe();

      staatenGruppeExtern.setStaatenGruppeTyp(de.ivu.elect.interfaces.core.StaatenGruppeTyp.valueOf(staatenGruppeIntern.getStaatenGruppeTyp().name()));
      staatenGruppeExtern.setBezeichnung(staatenGruppeIntern.getBezeichnung());
      staatenGruppeExtern.setCustomData(staatenGruppeIntern.getCustomDataXml());
      staatenGruppeExtern.getStaaten().addAll(staatMapper.extern(staatenGruppeIntern.getStaats()));

      wahlExtern.getStaatenGruppen().add(staatenGruppeExtern);
    }
  }

  private void mappeGebietsgruppierungen(Wahl wahlExtern, List<Gebietsgruppierung> gebietsgruppierungenIntern) {
    for (Gebietsgruppierung gebietsgruppierungIntern : gebietsgruppierungenIntern) {
      de.ivu.elect.interfaces.core.Gebietsgruppierung gebietsgruppierungExtern = new de.ivu.elect.interfaces.core.Gebietsgruppierung();

      gebietsgruppierungExtern.setName(gebietsgruppierungIntern.getName());
      gebietsgruppierungExtern.setNummer(gebietsgruppierungIntern.getNummer());
      gebietsgruppierungExtern.setGebietsgruppierungArt(gebietsgruppierungIntern.getGebietsgruppierungArt());
      gebietsgruppierungExtern.setPosition(gebietsgruppierungIntern.getPosition());
      gebietsgruppierungExtern.setVerwaltungsart(mappe(gebietsgruppierungIntern.getVerwaltungsart()));

      for (Gebiet gebietIntern : gebietsgruppierungIntern.getGebiete()) {
        gebietsgruppierungExtern.getGebiete().add(wahlInternExternMapperUtil.createGebietReferenz(gebietIntern));
      }

      wahlExtern.getGebietsgruppierungen().add(gebietsgruppierungExtern);
    }
  }

  protected Verwaltungsart mappe(de.ivu.elect.business.gebietsbaum.entity.Verwaltungsart verwaltungsart) {
    return Verwaltungsart.valueOf(verwaltungsart.name());
  }

  // ELECT-11163
  private void mapWahlvorschlagsPersonen(Wahl wahlExtern, List<WahlvorschlagsPerson> wahlvorschlagsPersonen, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    wahlExtern.setWahlvorschlagsPersonen(new ArrayList<>());

    for (WahlvorschlagsPerson wahlvorschlagsPersonIntern : wahlvorschlagsPersonen) {
      de.ivu.elect.interfaces.core.WahlvorschlagsPerson wahlvorschlagsPersonExtern = new de.ivu.elect.interfaces.core.WahlvorschlagsPerson();

      mapPersonendaten(wahlvorschlagsPersonIntern, wahlvorschlagsPersonExtern, teilnahmenInternPerMapPersonIntern);

      wahlvorschlagsPersonExtern.setWahlvorschlagReferenz(wahlInternExternMapperUtil.mappeWahlvorschlagReferenz(wahlvorschlagsPersonIntern.getWahlvorschlag()));
      wahlvorschlagsPersonExtern.setWahlvorschlagsPersonTyp(
          de.ivu.elect.interfaces.core.WahlvorschlagsPersonTyp.valueOf(wahlvorschlagsPersonIntern.getWahlvorschlagsPersonTyp().name()));

      wahlvorschlagsPersonExtern.setDatumUnterschrift(wahlvorschlagsPersonIntern.getDatumUnterschrift());
      wahlvorschlagsPersonExtern.setEingangssequenznummer(wahlvorschlagsPersonIntern.getEingangssequenznummer());
      wahlvorschlagsPersonExtern.setErstellungErfasser(wahlvorschlagsPersonIntern.getErstellungErfasser());
      wahlvorschlagsPersonExtern.setErstellungZeitpunkt(wahlvorschlagsPersonIntern.getErstellungZeitpunkt());

      // Unterstützer werden nicht exportiert, daher werden deren komplexe Attribute auch nicht gemappt...
      // wahlvorschlagsPersonExtern.setEintragungslisteEintrag(wahlvorschlagsPersonIntern.getEintragungslisteEintrag());
      // wahlvorschlagsPersonExtern.setErstellungOrt(wahlvorschlagsPersonIntern.getErstellungOrt());

      wahlExtern.getWahlvorschlagsPersonen().add(wahlvorschlagsPersonExtern);
    }
  }

  private void mappeWahlausschussmitglieder(Wahl wahlExtern, List<Wahlausschussmitglied> wahlausschussmitglieder, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    for (Wahlausschussmitglied wahlausschussmitgliedIntern : wahlausschussmitglieder) {
      de.ivu.elect.interfaces.core.Wahlausschussmitglied wahlausschussmitgliedExtern = new de.ivu.elect.interfaces.core.Wahlausschussmitglied();
      wahlausschussmitgliedExtern.setWahlausschussrolle(mappe(wahlausschussmitgliedIntern.getWahlausschussrolle()));
      wahlausschussmitgliedExtern.setGebiet(wahlInternExternMapperUtil.createGebietReferenz(wahlausschussmitgliedIntern.getGebiet()));

      PolitischeGruppe politischeGruppe = wahlausschussmitgliedIntern.getPolitischeGruppe();

      if (politischeGruppe != null) {
        wahlausschussmitgliedExtern.setPolitischeGruppe(wahlInternExternMapperUtil.createGruppeReferenz(politischeGruppe.getSchluessel()));
      }

      mapPersonendaten(wahlausschussmitgliedIntern, wahlausschussmitgliedExtern, teilnahmenInternPerMapPersonIntern);

      Wahlausschussmitglied vertreter = wahlausschussmitgliedIntern.getVertreter();

      if (vertreter != null) {
        wahlausschussmitgliedExtern.setVerterterId(EntityMapper.createIdInDatei(vertreter));
      }

      wahlExtern.getWahlausschussmitglieder().add(wahlausschussmitgliedExtern);
    }
  }

  private void mappeTeilnahmen(Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern, Personendaten personIntern, Person personExtern) {
    teilnahmeMapper.mappeTeilnahme(personExtern, teilnahmenInternPerMapPersonIntern.getOrDefault(personIntern, emptyList()));
  }

  void mapPersonenVerknuepfungen(Wahl wahlExtern, List<PersonenVerknuepfung> personenVerknuepfungen) {
    Collection<de.ivu.elect.interfaces.core.Kandidatur> kandidaturenExtern = new HashSet<>();
    kandidaturenExtern.addAll(collecKandidaturen(wahlExtern.getDirektkandidaturen()));
    kandidaturenExtern.addAll(collecKandidaturen(wahlExtern.getListenkandidaturen()));

    Map<PersonReferenz, de.ivu.elect.interfaces.core.Kandidatur> kandidaturExternPerReferenzExtern = kandidaturenExtern.stream()
        .collect(toMap(wahlInternExternMapperUtil::mappePersonendatenZuReferenz, person -> person));

    Map<PersonReferenz, de.ivu.elect.interfaces.core.WahlvorschlagsPerson> wahlvorschlagsPersonExternPerReferenzExtern = wahlExtern.getWahlvorschlagsPersonen()
        .stream()
        .collect(toMap(wahlInternExternMapperUtil::mappePersonendatenZuReferenz, person -> person));

    Map<PersonReferenz, de.ivu.elect.interfaces.core.Wahlausschussmitglied> wahlausschussmitgliedExternPerReferenzExtern = wahlExtern.getWahlausschussmitglieder()
        .stream()
        .collect(toMap(wahlInternExternMapperUtil::mappePersonendatenZuReferenz, person -> person));

    Map<PersonReferenz, de.ivu.elect.interfaces.core.Fremdkandidatur> fremdkandidaturExternPerReferenzExtern = wahlExtern.getFremdkandidaturen().stream()
        .collect(toMap(wahlInternExternMapperUtil::mappePersonendatenZuReferenz, person -> person));

    mapPersonenVerknuepfungen(wahlExtern, personenVerknuepfungen, kandidaturExternPerReferenzExtern, wahlvorschlagsPersonExternPerReferenzExtern,
        wahlausschussmitgliedExternPerReferenzExtern,
        fremdkandidaturExternPerReferenzExtern);
  }

  private Collection<de.ivu.elect.interfaces.core.Kandidatur> collecKandidaturen(Collection<? extends de.ivu.elect.interfaces.core.Kandidatur> kandidaturenExtern) {
    Collection<de.ivu.elect.interfaces.core.Kandidatur> result = new HashSet<>();

    for (de.ivu.elect.interfaces.core.Kandidatur kandidatur : kandidaturenExtern) {
      result.add(kandidatur);

      de.ivu.elect.interfaces.core.Ersatzkandidatur ersatzkandidatur = kandidatur.getErsatzkandidatur();

      if (ersatzkandidatur != null) {
        result.addAll(collecKandidaturen(asList(ersatzkandidatur)));
      }
    }

    return result;
  }

  private void mapPersonenVerknuepfungen(Wahl wahlExtern, List<PersonenVerknuepfung> personenVerknuepfungen,
                                         Map<PersonReferenz, de.ivu.elect.interfaces.core.Kandidatur> kandidaturExternPerReferenzExtern,
                                         Map<PersonReferenz, de.ivu.elect.interfaces.core.WahlvorschlagsPerson> wahlvorschlagsPersonExternPerReferenzExtern,
                                         Map<PersonReferenz, de.ivu.elect.interfaces.core.Wahlausschussmitglied> wahlausschussmitgliedExternPerReferenzExtern,
                                         Map<PersonReferenz, de.ivu.elect.interfaces.core.Fremdkandidatur> fremdkandidaturExternPerReferenzExtern) {
    Map<PersonReferenz, Person> personExternPerReferenzExtern = new HashMap<>();

    // ELECT-11327
    personExternPerReferenzExtern.putAll(kandidaturExternPerReferenzExtern);
    personExternPerReferenzExtern.putAll(wahlvorschlagsPersonExternPerReferenzExtern);
    personExternPerReferenzExtern.putAll(wahlausschussmitgliedExternPerReferenzExtern);
    personExternPerReferenzExtern.putAll(fremdkandidaturExternPerReferenzExtern);

    for (PersonenVerknuepfung personenVerknuepfung : personenVerknuepfungen) {
      if (personenVerknuepfung != null && isWahlvorschlagDublette(personenVerknuepfung)) {
        Personendaten person1 = personenVerknuepfung.getPerson1();
        Personendaten person2 = personenVerknuepfung.getPerson2();

        if (person1 != null && person2 != null) {
          PersonReferenz person1Referenz = wahlInternExternMapperUtil.mappePersonendatenZuReferenz(person1);
          PersonReferenz person2Referenz = wahlInternExternMapperUtil.mappePersonendatenZuReferenz(person2);

          if (personExternPerReferenzExtern.containsKey(person1Referenz) && personExternPerReferenzExtern.containsKey(person2Referenz)) {
            KandidaturVerknuepfung kandidaturVerknuepfung = new KandidaturVerknuepfung();

            kandidaturVerknuepfung.setVerknuepfungsArt(mappe(personenVerknuepfung.getVerknuepfungsArt()));
            kandidaturVerknuepfung.setVerknuepfungsStatus(mappe(personenVerknuepfung.getVerknuepfungsStatus()));
            kandidaturVerknuepfung.setVerknuepfungsGrundlage(mappe(personenVerknuepfung.getVerknuepfungsGrundlage()));

            kandidaturVerknuepfung.setPerson1Referenz(person1Referenz);

            if (person1.isInstanceOf(Kandidatur.class)) {
              kandidaturVerknuepfung.setKandidatur1(wahlInternExternMapperUtil.mappeKandidaturZuReferenz(castTo(person1, Kandidatur.class)));
            }

            kandidaturVerknuepfung.setPerson2Referenz(person2Referenz);

            if (person2.isInstanceOf(Kandidatur.class)) {
              kandidaturVerknuepfung.setKandidatur2(wahlInternExternMapperUtil.mappeKandidaturZuReferenz(castTo(person2, Kandidatur.class)));
            }

            wahlExtern.getKandidaturVerknuepfungen().add(kandidaturVerknuepfung);
          }
        }
      }
    }
  }

  private boolean isWahlvorschlagDublette(PersonenVerknuepfung personenVerknuepfung) {
    de.ivu.elect.business.person.entity.VerknuepfungsArt verknuepfungsArt = personenVerknuepfung.getVerknuepfungsArt();

    return verknuepfungsArt != null && verknuepfungsArt.isWahlvorschlagDublette();
  }

  void mappeKandidaturen(Wahl wahlExtern, List<Kandidatur> kandidaturenIntern, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern,
                         EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    for (Kandidatur kandidaturIntern : kandidaturenIntern) {
      mappeKandidatur(wahlExtern, kandidaturIntern, teilnahmenInternPerMapPersonIntern, entityAufStimmzettelDaten);
    }
  }

  private void mappeKandidatur(Wahl wahlExtern, Kandidatur kandidaturIntern, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern,
                               EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    Wahlvorschlag wahlvorschlagIntern = kandidaturIntern.getWahlvorschlag();

    if (wahlvorschlagIntern == null) {
      // ELECTMUC-2368 für den Fall, dass "Kandidatur-Reste" vorhanden sind, keine Fehlermeldung werfen, diese Daten sind überflüssig und müssen nicht exportiert werden
      LOG.info("Kandidatur ohne Wahlvorschlag(?): " + kandidaturIntern);
    } else {
      WahlvorschlagReferenz wahlvorschlagReferenz = createWahlvorschlagReferenz(wahlvorschlagIntern);

      if (kandidaturIntern.isDirektkandidatur()) {
        mappeDirektkandidatur(wahlExtern, castTo(kandidaturIntern, Direktkandidatur.class), wahlvorschlagReferenz, teilnahmenInternPerMapPersonIntern);
      } else if (kandidaturIntern.isListenkandidatur()) {
        // NOTE, dass nicht zugelassene oder nicht zulassungswürdige Listenkandidaturen NICHT exportiert werden!
        mappeListenkandidatur(wahlExtern, castTo(kandidaturIntern, Listenkandidatur.class), wahlvorschlagReferenz, teilnahmenInternPerMapPersonIntern,
            entityAufStimmzettelDaten);
      } else if (kandidaturIntern.isErsatzkandidatur()) {
        // return
      } else {
        throw new IllegalStateException("Unbekannte Kandidatur" + kandidaturIntern);
      }
    }
  }

  private void mappeListenkandidatur(Wahl wahlExtern, Listenkandidatur listenkandidaturIntern, WahlvorschlagReferenz wahlvorschlagReferenz,
                                     Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    de.ivu.elect.interfaces.core.Listenkandidatur listenkandidaturExtern = new de.ivu.elect.interfaces.core.Listenkandidatur();
    listenkandidaturExtern.setListenkandidaturArt(map(listenkandidaturIntern.getListenkandidaturArt()));

    ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = listenkandidaturIntern.getStimmzettelPosition(entityAufStimmzettelDaten);

    if (listenkandidaturStimmzettelPosition.hasPosition()) { // ELECT-4541
      Long position = listenkandidaturStimmzettelPosition.getPosition();

      if (position != null) {
        listenkandidaturExtern.setListenposition(position); // eigentlich nicht notwendig...
      }
    }

    listenkandidaturExtern.setListe(wahlvorschlagReferenz);
    listenkandidaturExtern.setAnzahlNennungen(listenkandidaturIntern.getAnzahlNennungen());
    listenkandidaturExtern.setRelativePosition(listenkandidaturIntern.getRelativePosition());
    listenkandidaturExtern.setVorgegebenePosition(listenkandidaturIntern.getVorgegebenePosition());
    listenkandidaturExtern.setEingereichtePosition(listenkandidaturIntern.getEingereichtePosition());

    wahlExtern.getListenkandidaturen().add(listenkandidaturExtern);

    mapKandidaturAttribute(listenkandidaturIntern, listenkandidaturExtern, teilnahmenInternPerMapPersonIntern);
  }

  private void mappeDirektkandidatur(Wahl wahlExtern, Direktkandidatur direktkandidaturIntern, WahlvorschlagReferenz wahlvorschlagReferenz,
                                     Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    de.ivu.elect.interfaces.core.Direktkandidatur direktkandidaturExtern = new de.ivu.elect.interfaces.core.Direktkandidatur();
    direktkandidaturExtern.setDirektkandidat(wahlvorschlagReferenz);

    wahlExtern.getDirektkandidaturen().add(direktkandidaturExtern);

    mapKandidaturAttribute(direktkandidaturIntern, direktkandidaturExtern, teilnahmenInternPerMapPersonIntern);
  }

  // Cf. AbstractWahlExternInternMapper#mapKandidatur!!!
  private void mapKandidaturAttribute(Kandidatur kandidaturIntern, de.ivu.elect.interfaces.core.Kandidatur kandidaturExtern,
                                      Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    mapPersonendaten(kandidaturIntern, kandidaturExtern, teilnahmenInternPerMapPersonIntern);

    kandidaturExtern.setBerufPraesentation(kandidaturIntern.getBerufPraesentation());

    kandidaturExtern.setGeburtsortPraesentation(kandidaturIntern.getGeburtsortPraesentation());

    kandidaturExtern.setKandidaturQuelle(mappeKandidaturQuelle(kandidaturIntern.getKandidaturQuelle()));
    kandidaturExtern.setKandidaturStatus(mappeKandidaturStatus(kandidaturIntern.getKandidaturStatus()));

    kandidaturExtern.setNamePraesentation(kandidaturIntern.getNamePraesentation());

    kandidaturExtern.setWaehlbar(mappe(kandidaturIntern.getWaehlbar()));
    kandidaturExtern.setWohnortPraesentation(kandidaturIntern.getWohnortPraesentation());

    if (!kandidaturIntern.getErsatzkandidatursByUebergeordneteKandidatur().isEmpty()) {
      if (kandidaturIntern.getErsatzkandidatursByUebergeordneteKandidatur().size() > 1) {
        throw new IllegalStateException("Nur eine Ersatzkandidatur pro Kandidatur erlaubt!");
      }

      kandidaturExtern.setErsatzkandidatur(
          mappeErsatzkandidatur(kandidaturIntern.getErsatzkandidatursByUebergeordneteKandidatur().iterator().next(), teilnahmenInternPerMapPersonIntern));
    }
  }

  protected void mapPersonendaten(Personendaten personIntern, de.ivu.elect.interfaces.core.Person personExtern, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    personMapper.extern(personIntern, personExtern);
    mappeTeilnahmen(teilnahmenInternPerMapPersonIntern, personIntern, personExtern);
  }

  private de.ivu.elect.interfaces.core.Ersatzkandidatur mappeErsatzkandidatur(Ersatzkandidatur ersatzkandidaturIntern, Map<Personendaten, Collection<Teilnahme>> teilnahmenInternPerMapPersonIntern) {
    de.ivu.elect.interfaces.core.Ersatzkandidatur ersatzkandidaturExtern = new de.ivu.elect.interfaces.core.Ersatzkandidatur();

    mapKandidaturAttribute(ersatzkandidaturIntern, ersatzkandidaturExtern, teilnahmenInternPerMapPersonIntern);

    return ersatzkandidaturExtern;
  }

  protected de.ivu.elect.interfaces.core.KandidaturStatus mappeKandidaturStatus(KandidaturStatus kandidaturStatus) {
    return de.ivu.elect.interfaces.core.KandidaturStatus.valueOf(kandidaturStatus.name());
  }

  protected de.ivu.elect.interfaces.core.KandidaturQuelle mappeKandidaturQuelle(KandidaturQuelle kandidaturQuelle) {
    return de.ivu.elect.interfaces.core.KandidaturQuelle.valueOf(kandidaturQuelle.name());
  }

  protected de.ivu.elect.interfaces.core.Wahlausschussrolle mappe(Wahlausschussrolle wahlausschussrolle) {
    return de.ivu.elect.interfaces.core.Wahlausschussrolle.valueOf(wahlausschussrolle.name());
  }

  protected de.ivu.elect.interfaces.core.Waehlbarkeit mappe(Waehlbarkeit waehlbar) {
    return de.ivu.elect.interfaces.core.Waehlbarkeit.valueOf(waehlbar.name());
  }

  private void mappeWahlvorschlaege(Wahl wahlExtern, List<Wahlvorschlag> wahlvorschlaegeIntern) {
    for (Wahlvorschlag wahlvorschlagIntern : wahlvorschlaegeIntern) {
      de.ivu.elect.interfaces.core.Wahlvorschlag wahlvorschlagExtern;

      if (wahlvorschlagIntern.isDirektkandidat()) {
        de.ivu.elect.interfaces.core.Direktkandidat direktkandidatExtern = new de.ivu.elect.interfaces.core.Direktkandidat();
        wahlExtern.getDirektkandidaten().add(direktkandidatExtern);
        wahlvorschlagExtern = direktkandidatExtern;

      } else if (wahlvorschlagIntern.isListe()) {
        de.ivu.elect.interfaces.core.Liste listeExtern = new de.ivu.elect.interfaces.core.Liste();

        Liste listeIntern = wahlvorschlagIntern.castTo(Liste.class).getEntity();

        Listenverbindung listenverbindung = listeIntern.getListenverbindung();

        if (listenverbindung != null) {
          listeExtern.setListenverbindung(listenverbindung.getBezeichnung());

          Long idInDatei = EntityMapper.createIdInDatei(listenverbindung);

          if (idInDatei != null) {
            listeExtern.setListenverbindungId(idInDatei.toString());
          }
        }

        wahlExtern.getListen().add(listeExtern);
        wahlvorschlagExtern = listeExtern;

      } else if (wahlvorschlagIntern.isReferendumOption()) {
        de.ivu.elect.interfaces.core.ReferendumOption referendumOptionExtern = new de.ivu.elect.interfaces.core.ReferendumOption();

        referendumOptionExtern.setLabel(wahlvorschlagIntern.getReferendumOption().getLabel());
        referendumOptionExtern.setKurzbezeichnung(wahlvorschlagIntern.getReferendumOption().getKurzbezeichnung());
        referendumOptionExtern.setPosition(wahlvorschlagIntern.getReferendumOption().getPosition());
        //  bei weiteren Attributen BEACHTE AUCH WahlInternExternMapperUtil.mappeReferendum

        wahlExtern.getReferendumOptionen().add(referendumOptionExtern);
        wahlvorschlagExtern = referendumOptionExtern;

      } else {
        throw new IllegalStateException("Unbekannte Wahlvorschlagsart " + wahlvorschlagIntern);
      }

      wahlvorschlagExtern.setWahlvorschlagReferenz(createWahlvorschlagReferenz(wahlvorschlagIntern));

      // abwärtskompatibilität ELECT-2413 irgendwann(tm) mal entfernen
      if (wahlvorschlagIntern.isReferendumOption()) {
        wahlvorschlagExtern.getWahlvorschlagReferenz().setPosition(wahlvorschlagIntern.getReferendumOption().getPosition());
      }

      wahlvorschlagExtern.setWahlvorschlagStatus(mappeWahlvorschlagStatus(wahlvorschlagIntern.getWahlvorschlagStatus()));

      wahlvorschlagExtern.setZeitpunktDerEinreichung(wahlvorschlagIntern.getZeitpunktDerEinreichung());

      wahlvorschlagExtern.setUnterstuetzerErfassbarAb(wahlvorschlagIntern.getUnterstuetzerErfassbarAb());
      wahlvorschlagExtern.setAnzahlUnterstuetzerExternGueltig(wahlvorschlagIntern.getAnzahlUnterstuetzerGeeignetErfasstExtern());
      wahlvorschlagExtern.setAnzahlUnterstuetzerExternUngueltig(wahlvorschlagIntern.getAnzahlUnterstuetzerNichtGeeignetErfasstExtern());

      wahlvorschlagExtern.setKennwort(wahlvorschlagIntern.getKennwort());
      wahlvorschlagExtern.setKurzname(wahlvorschlagIntern.getKurzname());

      // ELECTMUC-722
      if (wahlvorschlagExtern.getKennwort() == null) {
        wahlvorschlagExtern.setKennwort("");
      }

      if (wahlvorschlagExtern.getKurzname() == null) {
        wahlvorschlagExtern.setKurzname("");
      }
      wahlvorschlagExtern.setZusatzbezeichnung(wahlvorschlagIntern.getZusatzbezeichnung());
    }
  }

  protected de.ivu.elect.interfaces.core.WahlvorschlagStatus mappeWahlvorschlagStatus(WahlvorschlagStatus wahlvorschlagStatus) {
    return de.ivu.elect.interfaces.core.WahlvorschlagStatus.valueOf(wahlvorschlagStatus.name());
  }

  private WahlvorschlagReferenz createWahlvorschlagReferenz(Wahlvorschlag wahlvorschlag) {
    return wahlInternExternMapperUtil.mappeWahlvorschlagReferenz(wahlvorschlag);
  }

  private void mappeGruppenImGebiet(Wahl wahlExtern, List<GruppeImGebiet> gruppenImGebietIntern, Function<Ergebnisart, List<Stimmen>> stimmen, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    Map<GruppeImGebietTK, Map<Ergebnisart, List<Stimmen>>> stimmenNachGruppeImGebiet = mappeStimmenNachGruppeImGebiet(stimmen);
    for (GruppeImGebiet gruppeImGebietIntern : gruppenImGebietIntern) {
      de.ivu.elect.interfaces.core.GruppeImGebiet gruppeImGebietExtern = new de.ivu.elect.interfaces.core.GruppeImGebiet();
      gruppeImGebietExtern.setGruppeImGebietReferenz(wahlInternExternMapperUtil.createGruppeImGebietReferenz(gruppeImGebietIntern));
      gruppeImGebietExtern.setName(gruppeImGebietIntern.getName());
      gruppeImGebietExtern.setKurzname(gruppeImGebietIntern.getKurzname());
      gruppeImGebietExtern.setZusatzbezeichnung(gruppeImGebietIntern.getZusatzbezeichnung());

      // legacy?
      gruppeImGebietExtern.setPosition(getGruppeImGebietStimmzettelPositionAbgeleitet(gruppeImGebietIntern,
          entityAufStimmzettelDaten)); // Position von übergeordneter Gruppe, falls nicht gesetzt => Was hat weniger zu tun...

      gruppeImGebietExtern.setRelativePosition(gruppeImGebietIntern.getRelativePosition());
      gruppeImGebietExtern.setVorgegebenePosition(gruppeImGebietIntern.getVorgegebenePosition());
      gruppeImGebietExtern.setEingereichtePosition(gruppeImGebietIntern.getEingereichtePosition());

      // gruppeImGebietExtern.setStatus(mappe(gruppeImGebietIntern.getStatusAbgeleitet()));
      gruppeImGebietExtern.setStimmergebnis(
          mappeVorperiodenStimmen(gruppeImGebietExtern, stimmenNachGruppeImGebiet.getOrDefault(gruppeImGebietIntern.getGruppeImGebietTK(), emptyMap())));
      gruppeImGebietExtern.setCustomData(gruppeImGebietIntern.getCustomDataXml());
      wahlExtern.getGruppenImGebiet().add(gruppeImGebietExtern);

    }
  }

  @Nullable
  protected Long getGruppeImGebietStimmzettelPositionAbgeleitet(GruppeImGebiet gruppeImGebiet, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    // NOTE, dass diese Methode für das WUS ÜBERSCHRIEBEN wird und die STIMMZETTELPOSITIONEN exportiert!
    return gruppeImGebiet.getRelativePosition();
  }

  private List<de.ivu.elect.interfaces.core.Stimmen> mappeVorperiodenStimmen(de.ivu.elect.interfaces.core.GruppeImGebiet gruppeImGebietExtern, Map<Ergebnisart, List<Stimmen>> stimmenNachErgebnisArt) {
    List<de.ivu.elect.interfaces.core.Stimmen> result = new ArrayList<>();

    for (Map.Entry<Ergebnisart, List<Stimmen>> entry : stimmenNachErgebnisArt.entrySet()) {
      Ergebnisart ergebnisart = entry.getKey();
      List<Stimmen> stimmenListe = entry.getValue();

      for (Stimmen stimmen : stimmenListe) {
        de.ivu.elect.interfaces.core.Stimmen stimmenExtern = new de.ivu.elect.interfaces.core.Stimmen();
        stimmenExtern.setAnzahl(stimmen.getAnzahl());
        stimmenExtern.setErgebnisart(map(ergebnisart));
        if (ergebnisart.isVorperiode()) {
          stimmenExtern.setAnzahlVorperiode(stimmen.getAnzahl());
        }
        stimmenExtern.setStimmenart(wahlInternExternMapperUtil.mappeStimmenart(stimmen.getStimmenart()));
        result.add(stimmenExtern);
      }

    }

    return result;
  }

  private Map<GruppeImGebietTK, Map<Ergebnisart, List<Stimmen>>> mappeStimmenNachGruppeImGebiet(Function<Ergebnisart, List<Stimmen>> stimmenFct) {
    Map<GruppeImGebietTK, Map<Ergebnisart, List<Stimmen>>> result = new HashMap<>();
    for (Ergebnisart ergebnisart : getErgebnisarten()) {
      for (Stimmen stimmen : stimmenFct.apply(ergebnisart)) {
        result.computeIfAbsent(stimmen.getGruppeImGebietTK(), x -> new EnumMap<>(Ergebnisart.class)).computeIfAbsent(ergebnisart, x -> new ArrayList<>())
            .add(stimmen);
      }
    }
    return result;
  }

  private Collection<Ergebnisart> getErgebnisarten() {
    return Stream.of(Ergebnisart.values()).filter(ea -> !Ergebnisart.TESTDATEN.equals(ea)).collect(toList());
  }

  private void mappeGewaehltVorperiode(Wahl wahlExtern, List<Gewaehlt> gewaehltVorperiodeIntern) {
    for (Gewaehlt gewaehltIntern : gewaehltVorperiodeIntern) {
      de.ivu.elect.interfaces.core.Gewaehlt gewaehltExtern = new de.ivu.elect.interfaces.core.Gewaehlt();

      gewaehltExtern.setRang(gewaehltIntern.getRang());
      gewaehltExtern.setGewaehltStatus(mappe(gewaehltIntern.getGewaehltStatus()));
      gewaehltExtern.setGewaehltStatusVergabe(mappe(gewaehltIntern.getGewaehltStatusVergabe())); // ELECT-16036
      gewaehltExtern.setCustomData(gewaehltIntern.getCustomDataXml());
      gewaehltExtern.setKandidaturReferenz(wahlInternExternMapperUtil.mappeKandidaturZuReferenz(gewaehltIntern.getKandidatur()));

      wahlExtern.getGewaehltVorperiode().add(gewaehltExtern);
    }
  }

  protected de.ivu.elect.interfaces.core.GewaehltStatus mappe(GewaehltStatus intern) {
    return de.ivu.elect.interfaces.core.GewaehltStatus.valueOf(intern.name());
  }

  protected de.ivu.elect.interfaces.core.GewaehltStatusVergabe mappe(GewaehltStatusVergabe intern) {
    if(intern == null) {
      return de.ivu.elect.interfaces.core.GewaehltStatusVergabe.UNBESTIMMT;
    }

    return de.ivu.elect.interfaces.core.GewaehltStatusVergabe.valueOf(intern.name());
  }

  private void mappeSitzeVorperiode(Wahl wahlExtern, List<Sitze> sitzeVorperiodeIntern) {
    for (Sitze sitzeIntern : sitzeVorperiodeIntern) {
      de.ivu.elect.interfaces.core.Sitze sitzeExtern = new de.ivu.elect.interfaces.core.Sitze();

      sitzeExtern.setAnzahl(sitzeIntern.getAnzahl());
      sitzeExtern.setAnzahlAnspruch(sitzeIntern.getAnzahlAnspruch());
      sitzeExtern.setCustomData(sitzeIntern.getCustomDataXml());
      sitzeExtern.setGruppeImGebietReferenz(wahlInternExternMapperUtil.getGruppeImGebietReferenz(sitzeIntern.getGruppe(), sitzeIntern.getGebiet()));

      wahlExtern.getSitzeVorperiode().add(sitzeExtern);
    }
  }

  protected de.ivu.elect.interfaces.core.GruppeImGebietStatus mappe(GruppeImGebietStatus status) {
    return de.ivu.elect.interfaces.core.GruppeImGebietStatus.valueOf(status.name());
  }

  private void mappeGruppen(Wahl wahlExtern, List<Gruppe> gruppenIntern) {
    for (Gruppe gruppeIntern : gruppenIntern) {
      de.ivu.elect.interfaces.core.Gruppe gruppeExtern;

      if (gruppeIntern.isInstanceOf(PolitischeGruppe.class)) {
        PolitischeGruppe politischeGruppeIntern = gruppeIntern.castTo(PolitischeGruppe.class).getEntity();

        de.ivu.elect.interfaces.core.PolitischeGruppe politischeGruppeExtern = new de.ivu.elect.interfaces.core.PolitischeGruppe();
        politischeGruppeExtern.setMinderheit(politischeGruppeIntern.getMinderheit());
        politischeGruppeExtern.setPrivilegiert(politischeGruppeIntern.getPrivilegiert());

        wahlExtern.getPolitischeGruppen().add(politischeGruppeExtern);

        gruppeExtern = politischeGruppeExtern;
      } else {
        gruppeExtern = new de.ivu.elect.interfaces.core.Gruppe();

        wahlExtern.getGruppen().add(gruppeExtern);
      }

      gruppeExtern.setGruppeReferenz(wahlInternExternMapperUtil.createGruppeReferenz(gruppeIntern.getSchluessel()));
      gruppeExtern.setName(gruppeIntern.getName());
      gruppeExtern.setKurzname(gruppeIntern.getKurzname());
      gruppeExtern.setZusatzbezeichnung(gruppeIntern.getZusatzbezeichnung());
      gruppeExtern.setGruppeArt(mappe(gruppeIntern.getGruppeArt()));
      gruppeExtern.setLogo(extern(gruppeIntern.getLogo()));
      gruppeExtern.setFarbe(gruppeIntern.getFarbe());
      gruppeExtern.setFarbeExtern(gruppeIntern.getFarbeExtern());
      gruppeExtern.setInPraesentation(gruppeIntern.getInPraesentation());
      gruppeExtern.setCustomData(gruppeIntern.getCustomDataXml());
      gruppeExtern.setRelativePosition(gruppeIntern.getRelativePosition());
      gruppeExtern.setVorgegebenePosition(gruppeIntern.getVorgegebenePosition());
    }

  }

  private Bild extern(de.ivu.elect.business.image.entity.Bild source) {
    return bildMapper.extern(source);
  }

  protected de.ivu.elect.interfaces.core.GruppeArt mappe(GruppeArt gruppeArt) {
    return de.ivu.elect.interfaces.core.GruppeArt.valueOf(gruppeArt.name());
  }

  private void mappeGebiete(Wahl wahlExtern, List<Gebiet> gebieteIntern, Wahldaten wahldatenIntern) {
    for (Gebiet gebietIntern : gebieteIntern) {
      wahlExtern.getGebiete().add(mappeGebiet(gebietIntern, wahldatenIntern));
    }
  }

  protected de.ivu.elect.interfaces.core.Gebiet mappeGebiet(Gebiet gebietIntern, Wahldaten wahldatenIntern) {
    return gebietMapper.mapGebiet(gebietIntern);
  }

  private void mappeGeometrien(Wahl wahlExtern, List<GebietPolygonPoint> gebietPolygonPoints) {
    wahlExtern.getGebietGeometrie().getGebietPolygonPoints().addAll(gebietPolygonPoints.stream().map(gebietPolygonMapperUtil::mappe).collect(toList()));
  }

  protected VerknuepfungsStatus mappe(de.ivu.elect.business.person.entity.VerknuepfungsStatus verknuepfungsStatus) {
    return VerknuepfungsStatus.valueOf(verknuepfungsStatus.name());
  }

  protected VerknuepfungsGrundlage mappe(de.ivu.elect.business.person.entity.VerknuepfungsGrundlage verknuepfungsGrundlage) {
    return VerknuepfungsGrundlage.valueOf(verknuepfungsGrundlage.name());
  }

  protected VerknuepfungsArt mappe(de.ivu.elect.business.person.entity.VerknuepfungsArt verknuepfungsArt) {
    return VerknuepfungsArt.valueOf(verknuepfungsArt.name());
  }

  protected de.ivu.elect.interfaces.core.ListenkandidaturArt map(ListenkandidaturArt listenkandidaturArt) {
    return de.ivu.elect.interfaces.core.ListenkandidaturArt.valueOf(listenkandidaturArt.name());
  }

  protected de.ivu.elect.interfaces.core.Ergebnisart map(Ergebnisart ergebnisart) {
    return de.ivu.elect.interfaces.core.Ergebnisart.valueOf(ergebnisart.name());
  }

  protected void mappePersonPruefungKonfigurationen(Wahl wahlExtern, Wahldaten wahldatenIntern) {
    wahlExtern.setPersonPruefungKonfigurationen(
        ((List<PersonPruefungKonfiguration>) wahldatenIntern.getPersonPruefungKonfigurationen()).stream()
            .map(personPruefungKonfiguration -> personPruefungKonfigurationMapper.mappe(personPruefungKonfiguration)).collect(toList()));
  }
}
